diff options
Diffstat (limited to 'src')
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"><b>_Bookmarks</b></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"><b>_Name</b></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"><b>_Location</b></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"><b>Default View</b></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"><b>Icon View Defaults</b></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"><b>Compact View Defaults</b></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"><b>List View Defaults</b></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"><b>Tree View Defaults</b></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"><b>Behavior</b></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"><b>Executable Text Files</b></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"><b>Trash</b></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"><b>Icon Captions</b></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"><b>Date</b></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"><b>List Columns</b></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"><b>Text Files</b></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"><b>Other Previewable Files</b></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"><b>Sound Files</b></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"><b>Folders</b></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"><b>Media Handling</b></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"><b>Other Media</b></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. + * - [email protected] + */ + + 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 (©_move_done_data->directory_view); + + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect (directory_view, "add_file", + G_CALLBACK (pre_copy_move_add_file_callback), copy_move_done_data); + + return copy_move_done_data; +} + +/* This function is used to pull out any debuting uris that were added + * and (as a side effect) remove them from the debuting uri hash table. + */ +static gboolean +copy_move_done_partition_func (gpointer data, gpointer callback_data) +{ + GFile *location; + gboolean result; + + location = caja_file_get_location (CAJA_FILE (data)); + result = g_hash_table_remove ((GHashTable *) callback_data, location); + g_object_unref (location); + + return result; +} + +static gboolean +remove_not_really_moved_files (gpointer key, + gpointer value, + gpointer callback_data) +{ + GList **added_files; + GFile *loc; + + loc = key; + + if (GPOINTER_TO_INT (value)) { + return FALSE; + } + + added_files = callback_data; + *added_files = g_list_prepend (*added_files, + caja_file_get (loc)); + return TRUE; +} + + +/* When this function is invoked, the file operation is over, but all + * the icons may not have been added to the directory view yet, so + * we can't select them yet. + * + * We're passed a hash table of the uri's to look out for, we hook + * up a signal handler to await their arrival. + */ +static void +copy_move_done_callback (GHashTable *debuting_files, gpointer data) +{ + FMDirectoryView *directory_view; + CopyMoveDoneData *copy_move_done_data; + DebutingFilesData *debuting_files_data; + + copy_move_done_data = (CopyMoveDoneData *) data; + directory_view = copy_move_done_data->directory_view; + + if (directory_view != NULL) { + g_assert (FM_IS_DIRECTORY_VIEW (directory_view)); + + debuting_files_data = g_new (DebutingFilesData, 1); + debuting_files_data->debuting_files = g_hash_table_ref (debuting_files); + debuting_files_data->added_files = eel_g_list_partition + (copy_move_done_data->added_files, + copy_move_done_partition_func, + debuting_files, + ©_move_done_data->added_files); + + /* We're passed the same data used by pre_copy_move_add_file_callback, so disconnecting + * it will free data. We've already siphoned off the added_files we need, and stashed the + * directory_view pointer. + */ + g_signal_handlers_disconnect_by_func (directory_view, + G_CALLBACK (pre_copy_move_add_file_callback), + data); + + /* Any items in the debuting_files hash table that have + * "FALSE" as their value aren't really being copied + * or moved, so we can't wait for an add_file signal + * to come in for those. + */ + g_hash_table_foreach_remove (debuting_files, + remove_not_really_moved_files, + &debuting_files_data->added_files); + + if (g_hash_table_size (debuting_files) == 0) { + /* on the off-chance that all the icons have already been added */ + if (debuting_files_data->added_files != NULL) { + fm_directory_view_set_selection (directory_view, + debuting_files_data->added_files); + fm_directory_view_reveal_selection (directory_view); + } + debuting_files_data_free (debuting_files_data); + } else { + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect_data (GTK_OBJECT (directory_view), + "add_file", + G_CALLBACK (debuting_files_add_file_callback), + debuting_files_data, + (GClosureNotify) debuting_files_data_free, + G_CONNECT_AFTER); + } + } + + copy_move_done_data_free (copy_move_done_data); +} + +static gboolean +real_file_still_belongs (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + if (view->details->model != directory && + g_list_find (view->details->subdirectory_list, directory) == NULL) { + return FALSE; + } + + return caja_directory_contains_file (directory, file); +} + +static gboolean +still_should_show_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + return fm_directory_view_should_show_file (view, file) + && EEL_INVOKE_METHOD (FM_DIRECTORY_VIEW_CLASS, view, file_still_belongs, (view, file, directory)); +} + +static gboolean +ready_to_load (CajaFile *file) +{ + return caja_file_check_if_ready (file, + CAJA_FILE_ATTRIBUTES_FOR_ICON); +} + +static int +compare_files_cover (gconstpointer a, gconstpointer b, gpointer callback_data) +{ + const FileAndDirectory *fad1, *fad2; + FMDirectoryView *view; + + view = callback_data; + fad1 = a; fad2 = b; + + if (fad1->directory < fad2->directory) { + return -1; + } else if (fad1->directory > fad2->directory) { + return 1; + } else { + return EEL_INVOKE_METHOD (FM_DIRECTORY_VIEW_CLASS, view, compare_files, + (view, fad1->file, fad2->file)); + } +} +static void +sort_files (FMDirectoryView *view, GList **list) +{ + *list = g_list_sort_with_data (*list, compare_files_cover, view); + +} + +/* Go through all the new added and changed files. + * Put any that are not ready to load in the non_ready_files hash table. + * Add all the rest to the old_added_files and old_changed_files lists. + * Sort the old_*_files lists if anything was added to them. + */ +static void +process_new_files (FMDirectoryView *view) +{ + GList *new_added_files, *new_changed_files, *old_added_files, *old_changed_files; + GHashTable *non_ready_files; + GList *node, *next; + FileAndDirectory *pending; + gboolean in_non_ready; + + new_added_files = view->details->new_added_files; + view->details->new_added_files = NULL; + new_changed_files = view->details->new_changed_files; + view->details->new_changed_files = NULL; + + non_ready_files = view->details->non_ready_files; + + old_added_files = view->details->old_added_files; + old_changed_files = view->details->old_changed_files; + + /* Newly added files go into the old_added_files list if they're + * ready, and into the hash table if they're not. + */ + for (node = new_added_files; node != NULL; node = next) { + next = node->next; + pending = (FileAndDirectory *)node->data; + in_non_ready = g_hash_table_lookup (non_ready_files, pending) != NULL; + if (fm_directory_view_should_show_file (view, pending->file)) { + if (ready_to_load (pending->file)) { + if (in_non_ready) { + g_hash_table_remove (non_ready_files, pending); + } + new_added_files = g_list_delete_link (new_added_files, node); + old_added_files = g_list_prepend (old_added_files, pending); + } else { + if (!in_non_ready) { + new_added_files = g_list_delete_link (new_added_files, node); + g_hash_table_insert (non_ready_files, pending, pending); + } + } + } + } + file_and_directory_list_free (new_added_files); + + /* Newly changed files go into the old_added_files list if they're ready + * and were seen non-ready in the past, into the old_changed_files list + * if they are read and were not seen non-ready in the past, and into + * the hash table if they're not ready. + */ + for (node = new_changed_files; node != NULL; node = next) { + next = node->next; + pending = (FileAndDirectory *)node->data; + if (!still_should_show_file (view, pending->file, pending->directory) || ready_to_load (pending->file)) { + if (g_hash_table_lookup (non_ready_files, pending) != NULL) { + g_hash_table_remove (non_ready_files, pending); + if (still_should_show_file (view, pending->file, pending->directory)) { + new_changed_files = g_list_delete_link (new_changed_files, node); + old_added_files = g_list_prepend (old_added_files, pending); + } + } else if (fm_directory_view_should_show_file (view, pending->file)) { + new_changed_files = g_list_delete_link (new_changed_files, node); + old_changed_files = g_list_prepend (old_changed_files, pending); + } + } + } + file_and_directory_list_free (new_changed_files); + + /* If any files were added to old_added_files, then resort it. */ + if (old_added_files != view->details->old_added_files) { + view->details->old_added_files = old_added_files; + sort_files (view, &view->details->old_added_files); + } + + /* Resort old_changed_files too, since file attributes + * relevant to sorting could have changed. + */ + if (old_changed_files != view->details->old_changed_files) { + view->details->old_changed_files = old_changed_files; + sort_files (view, &view->details->old_changed_files); + } + +} + +static void +process_old_files (FMDirectoryView *view) +{ + GList *files_added, *files_changed, *node; + FileAndDirectory *pending; + GList *selection, *files; + gboolean send_selection_change; + + files_added = view->details->old_added_files; + files_changed = view->details->old_changed_files; + + send_selection_change = FALSE; + + if (files_added != NULL || files_changed != NULL) { + g_signal_emit (view, signals[BEGIN_FILE_CHANGES], 0); + + for (node = files_added; node != NULL; node = node->next) { + pending = node->data; + g_signal_emit (view, + signals[ADD_FILE], 0, pending->file, pending->directory); + } + + for (node = files_changed; node != NULL; node = node->next) { + pending = node->data; + g_signal_emit (view, + signals[still_should_show_file (view, pending->file, pending->directory) + ? FILE_CHANGED : REMOVE_FILE], 0, + pending->file, pending->directory); + } + + g_signal_emit (view, signals[END_FILE_CHANGES], 0); + + if (files_changed != NULL) { + selection = fm_directory_view_get_selection (view); + files = file_and_directory_list_to_files (files_changed); + send_selection_change = eel_g_lists_sort_and_check_for_intersection + (&files, &selection); + caja_file_list_free (files); + caja_file_list_free (selection); + } + + file_and_directory_list_free (view->details->old_added_files); + view->details->old_added_files = NULL; + + file_and_directory_list_free (view->details->old_changed_files); + view->details->old_changed_files = NULL; + } + + if (send_selection_change) { + /* Send a selection change since some file names could + * have changed. + */ + fm_directory_view_send_selection_change (view); + } +} + +static void +display_pending_files (FMDirectoryView *view) +{ + + /* Don't dispatch any updates while the view is frozen. */ + if (view->details->updates_frozen) { + return; + } + + process_new_files (view); + process_old_files (view); + + if (view->details->model != NULL + && caja_directory_are_all_files_seen (view->details->model) + && g_hash_table_size (view->details->non_ready_files) == 0) { + done_loading (view, TRUE); + } +} + +void +fm_directory_view_freeze_updates (FMDirectoryView *view) +{ + view->details->updates_frozen = TRUE; + view->details->updates_queued = 0; + view->details->needs_reload = FALSE; +} + +void +fm_directory_view_unfreeze_updates (FMDirectoryView *view) +{ + view->details->updates_frozen = FALSE; + + if (view->details->needs_reload) { + view->details->needs_reload = FALSE; + if (view->details->model != NULL) { + load_directory (view, view->details->model); + } + } else { + schedule_idle_display_of_pending_files (view); + } +} + +static gboolean +display_selection_info_idle_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + g_object_ref (G_OBJECT (view)); + + view->details->display_selection_idle_id = 0; + fm_directory_view_display_selection_info (view); + if (view->details->send_selection_change_to_shell) { + fm_directory_view_send_selection_change (view); + } + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static void +remove_update_menus_timeout_callback (FMDirectoryView *view) +{ + if (view->details->update_menus_timeout_id != 0) { + g_source_remove (view->details->update_menus_timeout_id); + view->details->update_menus_timeout_id = 0; + } +} + +static void +update_menus_if_pending (FMDirectoryView *view) +{ + if (!view->details->menu_states_untrustworthy) { + return; + } + + remove_update_menus_timeout_callback (view); + fm_directory_view_update_menus (view); +} + +static gboolean +update_menus_timeout_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + g_object_ref (G_OBJECT (view)); + + view->details->update_menus_timeout_id = 0; + fm_directory_view_update_menus (view); + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static gboolean +display_pending_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + g_object_ref (G_OBJECT (view)); + + view->details->display_pending_source_id = 0; + + display_pending_files (view); + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static void +schedule_idle_display_of_pending_files (FMDirectoryView *view) +{ + /* Get rid of a pending source as it might be a timeout */ + unschedule_display_of_pending_files (view); + + /* We want higher priority than the idle that handles the relayout + to avoid a resort on each add. But we still want to allow repaints + and other hight prio events while we have pending files to show. */ + view->details->display_pending_source_id = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, + display_pending_callback, view, NULL); +} + +static void +schedule_timeout_display_of_pending_files (FMDirectoryView *view, guint interval) +{ + /* No need to schedule an update if there's already one pending. */ + if (view->details->display_pending_source_id != 0) { + return; + } + + view->details->display_pending_source_id = + g_timeout_add (interval, display_pending_callback, view); +} + +static void +unschedule_display_of_pending_files (FMDirectoryView *view) +{ + /* Get rid of source if it's active. */ + if (view->details->display_pending_source_id != 0) { + g_source_remove (view->details->display_pending_source_id); + view->details->display_pending_source_id = 0; + } +} + +static void +queue_pending_files (FMDirectoryView *view, + CajaDirectory *directory, + GList *files, + GList **pending_list) +{ + if (files == NULL) { + return; + } + + /* Don't queue any more updates if we need to reload anyway */ + if (view->details->needs_reload) { + return; + } + + if (view->details->updates_frozen) { + view->details->updates_queued += g_list_length (files); + /* Mark the directory for reload when there are too much queued + * changes to prevent the pending list from growing infinitely. + */ + if (view->details->updates_queued > MAX_QUEUED_UPDATES) { + view->details->needs_reload = TRUE; + return; + } + } + + + + *pending_list = g_list_concat (file_and_directory_list_from_files (directory, files), + *pending_list); + + if (! view->details->loading || caja_directory_are_all_files_seen (directory)) { + schedule_timeout_display_of_pending_files (view, view->details->update_interval); + } +} + +static void +remove_changes_timeout_callback (FMDirectoryView *view) +{ + if (view->details->changes_timeout_id != 0) { + g_source_remove (view->details->changes_timeout_id); + view->details->changes_timeout_id = 0; + } +} + +static void +reset_update_interval (FMDirectoryView *view) +{ + view->details->update_interval = UPDATE_INTERVAL_MIN; + remove_changes_timeout_callback (view); + /* Reschedule a pending timeout to idle */ + if (view->details->display_pending_source_id != 0) { + schedule_idle_display_of_pending_files (view); + } +} + +static gboolean +changes_timeout_callback (gpointer data) +{ + gint64 now; + gint64 time_delta; + gboolean ret; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + g_object_ref (G_OBJECT (view)); + + now = eel_get_system_time(); + time_delta = now - view->details->last_queued; + + if (time_delta < UPDATE_INTERVAL_RESET*1000) { + if (view->details->update_interval < UPDATE_INTERVAL_MAX && + view->details->loading) { + /* Increase */ + view->details->update_interval += UPDATE_INTERVAL_INC; + } + ret = TRUE; + } else { + /* Reset */ + reset_update_interval (view); + ret = FALSE; + } + + g_object_unref (G_OBJECT (view)); + + return ret; +} + +static void +schedule_changes (FMDirectoryView *view) +{ + /* Remember when the change was queued */ + view->details->last_queued = eel_get_system_time(); + + /* No need to schedule if there are already changes pending or during loading */ + if (view->details->changes_timeout_id != 0 || + view->details->loading) { + return; + } + + view->details->changes_timeout_id = + g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, view); +} + +static void +files_added_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + GtkWindow *window; + char *uri; + + view = FM_DIRECTORY_VIEW (callback_data); + + window = fm_directory_view_get_containing_window (view); + uri = fm_directory_view_get_uri (view); + caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_ASYNC, files, + "files added in window %p: %s", + window, + uri ? uri : "(no directory)"); + g_free (uri); + + schedule_changes (view); + + queue_pending_files (view, directory, files, &view->details->new_added_files); + + /* The number of items could have changed */ + schedule_update_status (view); +} + +static void +files_changed_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + GtkWindow *window; + char *uri; + + view = FM_DIRECTORY_VIEW (callback_data); + + window = fm_directory_view_get_containing_window (view); + uri = fm_directory_view_get_uri (view); + caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_ASYNC, files, + "files changed in window %p: %s", + window, + uri ? uri : "(no directory)"); + g_free (uri); + + schedule_changes (view); + + queue_pending_files (view, directory, files, &view->details->new_changed_files); + + /* The free space or the number of items could have changed */ + schedule_update_status (view); + + /* A change in MIME type could affect the Open with menu, for + * one thing, so we need to update menus when files change. + */ + schedule_update_menus (view); +} + +static void +done_loading_callback (CajaDirectory *directory, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + process_new_files (view); + if (g_hash_table_size (view->details->non_ready_files) == 0) { + /* Unschedule a pending update and schedule a new one with the minimal + * update interval. This gives the view a short chance at gathering the + * (cached) deep counts. + */ + unschedule_display_of_pending_files (view); + schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); + } +} + +static void +load_error_callback (CajaDirectory *directory, + GError *error, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + /* FIXME: By doing a stop, we discard some pending files. Is + * that OK? + */ + fm_directory_view_stop (view); + + /* Emit a signal to tell subclasses that a load error has + * occurred, so they can handle it in the UI. + */ + g_signal_emit (view, + signals[LOAD_ERROR], 0, error); +} + +static void +real_load_error (FMDirectoryView *view, GError *error) +{ + /* Report only one error per failed directory load (from the UI + * point of view, not from the CajaDirectory point of view). + * Otherwise you can get multiple identical errors caused by + * unrelated code that just happens to try to iterate this + * directory. + */ + if (!view->details->reported_load_error) { + fm_report_error_loading_directory + (fm_directory_view_get_directory_as_file (view), + error, + fm_directory_view_get_containing_window (view)); + } + view->details->reported_load_error = TRUE; +} + +void +fm_directory_view_add_subdirectory (FMDirectoryView *view, + CajaDirectory*directory) +{ + CajaFileAttributes attributes; + + g_assert (!g_list_find (view->details->subdirectory_list, directory)); + + caja_directory_ref (directory); + + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_EXTENSION_INFO; + + caja_directory_file_monitor_add (directory, + &view->details->model, + view->details->show_hidden_files, + view->details->show_backup_files, + attributes, + files_added_callback, view); + + g_signal_connect + (directory, "files_added", + G_CALLBACK (files_added_callback), view); + g_signal_connect + (directory, "files_changed", + G_CALLBACK (files_changed_callback), view); + + view->details->subdirectory_list = g_list_prepend ( + view->details->subdirectory_list, directory); +} + +void +fm_directory_view_remove_subdirectory (FMDirectoryView *view, + CajaDirectory*directory) +{ + g_assert (g_list_find (view->details->subdirectory_list, directory)); + + view->details->subdirectory_list = g_list_remove ( + view->details->subdirectory_list, directory); + + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (files_added_callback), + view); + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (files_changed_callback), + view); + + caja_directory_file_monitor_remove (directory, &view->details->model); + + caja_directory_unref (directory); +} + +/** + * fm_directory_view_clear: + * + * Emit the signal to clear the contents of the view. Subclasses must + * override the signal handler for this signal. This is normally called + * only by FMDirectoryView. + * @view: FMDirectoryView to empty. + * + **/ +void +fm_directory_view_clear (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + g_signal_emit (view, signals[CLEAR], 0); +} + +/** + * fm_directory_view_begin_loading: + * + * Emit the signal to prepare for loading the contents of a new location. + * Subclasses might want to override the signal handler for this signal. + * This is normally called only by FMDirectoryView. + * @view: FMDirectoryView that is switching to view a new location. + * + **/ +void +fm_directory_view_begin_loading (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + g_signal_emit (view, signals[BEGIN_LOADING], 0); +} + +/** + * fm_directory_view_end_loading: + * + * Emit the signal after loading the contents of a new location. + * Subclasses might want to override the signal handler for this signal. + * This is normally called only by FMDirectoryView. + * @view: FMDirectoryView that is switching to view a new location. + * + **/ +void +fm_directory_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + g_signal_emit (view, signals[END_LOADING], 0, all_files_seen); +} + +/** + * fm_directory_view_get_loading: + * @view: an #FMDirectoryView. + * + * Return value: #gboolean inicating whether @view is currently loaded. + * + **/ +gboolean +fm_directory_view_get_loading (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return view->details->loading; +} + +/** + * fm_directory_view_bump_zoom_level: + * + * bump the current zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (!fm_directory_view_supports_zooming (view)) { + return; + } + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + bump_zoom_level, (view, zoom_increment)); +} + +/** + * fm_directory_view_zoom_to_level: + * + * Set the current zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (!fm_directory_view_supports_zooming (view)) { + return; + } + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + zoom_to_level, (view, zoom_level)); +} + + +CajaZoomLevel +fm_directory_view_get_zoom_level (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + + if (!fm_directory_view_supports_zooming (view)) { + return CAJA_ZOOM_LEVEL_STANDARD; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_zoom_level, (view)); +} + +/** + * fm_directory_view_restore_default_zoom_level: + * + * restore to the default zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_restore_default_zoom_level (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (!fm_directory_view_supports_zooming (view)) { + return; + } + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + restore_default_zoom_level, (view)); +} + +/** + * fm_directory_view_can_zoom_in: + * + * Determine whether the view can be zoomed any closer. + * @view: The zoomable FMDirectoryView. + * + * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise. + * + **/ +gboolean +fm_directory_view_can_zoom_in (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + if (!fm_directory_view_supports_zooming (view)) { + return FALSE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + can_zoom_in, (view)); +} + +/** + * fm_directory_view_can_rename_file + * + * Determine whether a file can be renamed. + * @file: A CajaFile + * + * Return value: TRUE if @file can be renamed, FALSE otherwise. + * + **/ +static gboolean +fm_directory_view_can_rename_file (FMDirectoryView *view, CajaFile *file) +{ + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + can_rename_file, (view, file)); +} + +/** + * fm_directory_view_can_zoom_out: + * + * Determine whether the view can be zoomed any further away. + * @view: The zoomable FMDirectoryView. + * + * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise. + * + **/ +gboolean +fm_directory_view_can_zoom_out (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + if (!fm_directory_view_supports_zooming (view)) { + return FALSE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + can_zoom_out, (view)); +} + +GtkWidget * +fm_directory_view_get_background_widget (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_background_widget, (view)); +} + +EelBackground * +fm_directory_view_get_background (FMDirectoryView *view) +{ + return eel_get_widget_background (fm_directory_view_get_background_widget (view)); +} + +static void +real_set_is_active (FMDirectoryView *view, + gboolean is_active) +{ + EelBackground *bg; + + bg = fm_directory_view_get_background (view); + eel_background_set_active (bg, is_active); +} + +static void +fm_directory_view_set_is_active (FMDirectoryView *view, + gboolean is_active) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, + set_is_active, (view, is_active)); +} + +/** + * fm_directory_view_get_selection: + * + * Get a list of CajaFile pointers that represents the + * currently-selected items in this view. Subclasses must override + * the signal handler for the 'get_selection' signal. Callers are + * responsible for g_free-ing the list (but not its data). + * @view: FMDirectoryView whose selected items are of interest. + * + * Return value: GList of CajaFile pointers representing the selection. + * + **/ +GList * +fm_directory_view_get_selection (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_selection, (view)); +} + +void +fm_directory_view_invert_selection (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + invert_selection, (view)); +} + +GList * +fm_directory_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_selection_for_file_transfer, (view)); +} + +guint +fm_directory_view_get_item_count (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), 0); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_item_count, (view)); +} + +GtkUIManager * +fm_directory_view_get_ui_manager (FMDirectoryView *view) +{ + if (view->details->window == NULL) { + return NULL; + } + return caja_window_info_get_ui_manager (view->details->window); +} + +/** + * fm_directory_view_get_model: + * + * Get the model for this FMDirectoryView. + * @view: FMDirectoryView of interest. + * + * Return value: CajaDirectory for this view. + * + **/ +CajaDirectory * +fm_directory_view_get_model (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return view->details->model; +} + +GdkAtom +fm_directory_view_get_copied_files_atom (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), GDK_NONE); + + return copied_files_atom; +} + +static void +prepend_uri_one (gpointer data, gpointer callback_data) +{ + CajaFile *file; + GList **result; + + g_assert (CAJA_IS_FILE (data)); + g_assert (callback_data != NULL); + + result = (GList **) callback_data; + file = (CajaFile *) data; + *result = g_list_prepend (*result, caja_file_get_uri (file)); +} + +static void +offset_drop_points (GArray *relative_item_points, + int x_offset, int y_offset) +{ + guint index; + + if (relative_item_points == NULL) { + return; + } + + for (index = 0; index < relative_item_points->len; index++) { + g_array_index (relative_item_points, GdkPoint, index).x += x_offset; + g_array_index (relative_item_points, GdkPoint, index).y += y_offset; + } +} + +static void +fm_directory_view_create_links_for_files (FMDirectoryView *view, GList *files, + GArray *relative_item_points) +{ + GList *uris; + char *dir_uri; + CopyMoveDoneData *copy_move_done_data; + g_assert (relative_item_points->len == 0 + || g_list_length (files) == relative_item_points->len); + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (files != NULL); + + /* create a list of URIs */ + uris = NULL; + g_list_foreach (files, prepend_uri_one, &uris); + uris = g_list_reverse (uris); + + g_assert (g_list_length (uris) == g_list_length (files)); + + /* offset the drop locations a bit so that we don't pile + * up the icons on top of each other + */ + offset_drop_points (relative_item_points, + DUPLICATE_HORIZONTAL_ICON_OFFSET, + DUPLICATE_VERTICAL_ICON_OFFSET); + + copy_move_done_data = pre_copy_move (view); + dir_uri = fm_directory_view_get_backing_uri (view); + caja_file_operations_copy_move (uris, relative_item_points, dir_uri, GDK_ACTION_LINK, + GTK_WIDGET (view), copy_move_done_callback, copy_move_done_data); + g_free (dir_uri); + eel_g_list_free_deep (uris); +} + +static void +fm_directory_view_duplicate_selection (FMDirectoryView *view, GList *files, + GArray *relative_item_points) +{ + GList *uris; + CopyMoveDoneData *copy_move_done_data; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (files != NULL); + g_assert (g_list_length (files) == relative_item_points->len + || relative_item_points->len == 0); + + /* create a list of URIs */ + uris = NULL; + g_list_foreach (files, prepend_uri_one, &uris); + uris = g_list_reverse (uris); + + g_assert (g_list_length (uris) == g_list_length (files)); + + /* offset the drop locations a bit so that we don't pile + * up the icons on top of each other + */ + offset_drop_points (relative_item_points, + DUPLICATE_HORIZONTAL_ICON_OFFSET, + DUPLICATE_VERTICAL_ICON_OFFSET); + + copy_move_done_data = pre_copy_move (view); + caja_file_operations_copy_move (uris, relative_item_points, NULL, GDK_ACTION_COPY, + GTK_WIDGET (view), copy_move_done_callback, copy_move_done_data); + eel_g_list_free_deep (uris); +} + +/* special_link_in_selection + * + * Return TRUE if one of our special links is in the selection. + * Special links include the following: + * CAJA_DESKTOP_LINK_TRASH, CAJA_DESKTOP_LINK_HOME, CAJA_DESKTOP_LINK_MOUNT + */ + +static gboolean +special_link_in_selection (FMDirectoryView *view) +{ + gboolean saw_link; + GList *selection, *node; + CajaFile *file; + + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + saw_link = FALSE; + + selection = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); + + for (node = selection; node != NULL; node = node->next) { + file = CAJA_FILE (node->data); + + saw_link = CAJA_IS_DESKTOP_ICON_FILE (file); + + if (saw_link) { + break; + } + } + + caja_file_list_free (selection); + + return saw_link; +} + +/* desktop_or_home_dir_in_selection + * + * Return TRUE if either the desktop or the home directory is in the selection. + */ + +static gboolean +desktop_or_home_dir_in_selection (FMDirectoryView *view) +{ + gboolean saw_desktop_or_home_dir; + GList *selection, *node; + CajaFile *file; + + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + saw_desktop_or_home_dir = FALSE; + + selection = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); + + for (node = selection; node != NULL; node = node->next) { + file = CAJA_FILE (node->data); + + saw_desktop_or_home_dir = + caja_file_is_home (file) + || caja_file_is_desktop_directory (file); + + if (saw_desktop_or_home_dir) { + break; + } + } + + caja_file_list_free (selection); + + return saw_desktop_or_home_dir; +} + +static void +trash_or_delete_done_cb (GHashTable *debuting_uris, + gboolean user_cancel, + FMDirectoryView *view) +{ + if (user_cancel) { + view->details->selection_was_removed = FALSE; + } +} + +static void +trash_or_delete_files (GtkWindow *parent_window, + const GList *files, + gboolean delete_if_all_already_in_trash, + FMDirectoryView *view) +{ + GList *locations; + const GList *node; + + locations = NULL; + for (node = files; node != NULL; node = node->next) { + locations = g_list_prepend (locations, + caja_file_get_location ((CajaFile *) node->data)); + } + + locations = g_list_reverse (locations); + + caja_file_operations_trash_or_delete (locations, + parent_window, + (CajaDeleteCallback) trash_or_delete_done_cb, + view); + eel_g_object_list_free (locations); +} + +static gboolean +can_rename_file (FMDirectoryView *view, CajaFile *file) +{ + return caja_file_can_rename (file); +} + +static void +start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ + if (file != NULL) { + fm_directory_view_select_file (view, file); + } +} + +typedef struct { + FMDirectoryView *view; + CajaFile *new_file; +} RenameData; + +static gboolean +delayed_rename_file_hack_callback (RenameData *data) +{ + FMDirectoryView *view; + CajaFile *new_file; + + view = data->view; + new_file = data->new_file; + + if (view->details->window != NULL && + view->details->active) { + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file, FALSE)); + fm_directory_view_reveal_selection (view); + } + + return FALSE; +} + +static void +delayed_rename_file_hack_removed (RenameData *data) +{ + g_object_unref (data->view); + caja_file_unref (data->new_file); + g_free (data); +} + + +static void +rename_file (FMDirectoryView *view, CajaFile *new_file) +{ + RenameData *data; + + /* HACK!!!! + This is a work around bug in listview. After the rename is + enabled we will get file changes due to info about the new + file being read, which will cause the model to change. When + the model changes GtkTreeView clears the editing. This hack just + delays editing for some time to try to avoid this problem. + A major problem is that the selection of the row causes us + to load the slow mimetype for the file, which leads to a + file_changed. So, before we delay we select the row. + */ + if (FM_IS_LIST_VIEW (view)) { + fm_directory_view_select_file (view, new_file); + + data = g_new (RenameData, 1); + data->view = g_object_ref (view); + data->new_file = caja_file_ref (new_file); + if (view->details->delayed_rename_file_id != 0) { + g_source_remove (view->details->delayed_rename_file_id); + } + view->details->delayed_rename_file_id = + g_timeout_add_full (G_PRIORITY_DEFAULT, + 100, (GSourceFunc)delayed_rename_file_hack_callback, + data, (GDestroyNotify) delayed_rename_file_hack_removed); + + return; + } + + /* no need to select because start_renaming_file selects + * fm_directory_view_select_file (view, new_file); + */ + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file, FALSE)); + fm_directory_view_reveal_selection (view); +} + +static void +reveal_newly_added_folder (FMDirectoryView *view, CajaFile *new_file, + CajaDirectory *directory, GFile *target_location) +{ + GFile *location; + + location = caja_file_get_location (new_file); + if (g_file_equal (location, target_location)) { + g_signal_handlers_disconnect_by_func (view, + G_CALLBACK (reveal_newly_added_folder), + (void *) target_location); + rename_file (view, new_file); + } + g_object_unref (location); +} + +typedef struct { + FMDirectoryView *directory_view; + GHashTable *added_locations; +} NewFolderData; + + +static void +track_newly_added_locations (FMDirectoryView *view, CajaFile *new_file, + CajaDirectory *directory, gpointer user_data) +{ + NewFolderData *data; + + data = user_data; + + g_hash_table_insert (data->added_locations, caja_file_get_location (new_file), NULL); +} + +static void +new_folder_done (GFile *new_folder, gpointer user_data) +{ + FMDirectoryView *directory_view; + CajaFile *file; + char screen_string[32]; + GdkScreen *screen; + NewFolderData *data; + + data = (NewFolderData *)user_data; + + directory_view = data->directory_view; + + if (directory_view == NULL) { + goto fail; + } + + g_signal_handlers_disconnect_by_func (directory_view, + G_CALLBACK (track_newly_added_locations), + (void *) data); + + if (new_folder == NULL) { + goto fail; + } + + screen = gtk_widget_get_screen (GTK_WIDGET (directory_view)); + g_snprintf (screen_string, sizeof (screen_string), "%d", gdk_screen_get_number (screen)); + + + file = caja_file_get (new_folder); + caja_file_set_metadata + (file, CAJA_METADATA_KEY_SCREEN, + NULL, + screen_string); + + if (g_hash_table_lookup_extended (data->added_locations, new_folder, NULL, NULL)) { + /* The file was already added */ + rename_file (directory_view, file); + } else { + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (reveal_newly_added_folder), + g_object_ref (new_folder), + (GClosureNotify)g_object_unref, + G_CONNECT_AFTER); + } + caja_file_unref (file); + + fail: + g_hash_table_destroy (data->added_locations); + eel_remove_weak_pointer (&data->directory_view); + g_free (data); +} + + +static NewFolderData * +new_folder_data_new (FMDirectoryView *directory_view) +{ + NewFolderData *data; + + data = g_new (NewFolderData, 1); + data->directory_view = directory_view; + data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, + g_object_unref, NULL); + eel_add_weak_pointer (&data->directory_view); + + return data; +} + +static GdkPoint * +context_menu_to_file_operation_position (FMDirectoryView *directory_view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (directory_view), NULL); + + if (fm_directory_view_using_manual_layout (directory_view) + && directory_view->details->context_menu_position.x >= 0 + && directory_view->details->context_menu_position.y >= 0) { + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, directory_view, + widget_to_file_operation_position, + (directory_view, &directory_view->details->context_menu_position)); + return &directory_view->details->context_menu_position; + } else { + return NULL; + } +} + +static void +update_context_menu_position_from_event (FMDirectoryView *view, + GdkEventButton *event) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (event != NULL) { + view->details->context_menu_position.x = event->x; + view->details->context_menu_position.y = event->y; + } else { + view->details->context_menu_position.x = -1; + view->details->context_menu_position.y = -1; + } +} + +void +fm_directory_view_new_folder (FMDirectoryView *directory_view) +{ + char *parent_uri; + NewFolderData *data; + GdkPoint *pos; + + data = new_folder_data_new (directory_view); + + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (track_newly_added_locations), + data, + (GClosureNotify)NULL, + G_CONNECT_AFTER); + + pos = context_menu_to_file_operation_position (directory_view); + + parent_uri = fm_directory_view_get_backing_uri (directory_view); + caja_file_operations_new_folder (GTK_WIDGET (directory_view), + pos, parent_uri, + new_folder_done, data); + + g_free (parent_uri); +} + +static NewFolderData * +setup_new_folder_data (FMDirectoryView *directory_view) +{ + NewFolderData *data; + + data = new_folder_data_new (directory_view); + + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (track_newly_added_locations), + data, + (GClosureNotify)NULL, + G_CONNECT_AFTER); + + return data; +} + +static void +fm_directory_view_new_file_with_initial_contents (FMDirectoryView *directory_view, + const char *parent_uri, + const char *filename, + const char *initial_contents, + int length, + GdkPoint *pos) +{ + NewFolderData *data; + + g_assert (parent_uri != NULL); + + data = setup_new_folder_data (directory_view); + + if (pos == NULL) { + pos = context_menu_to_file_operation_position (directory_view); + } + + caja_file_operations_new_file (GTK_WIDGET (directory_view), + pos, parent_uri, filename, + initial_contents, length, + new_folder_done, data); +} + +void +fm_directory_view_new_file (FMDirectoryView *directory_view, + const char *parent_uri, + CajaFile *source) +{ + GdkPoint *pos; + NewFolderData *data; + char *source_uri; + char *container_uri; + + container_uri = NULL; + if (parent_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (directory_view); + g_assert (container_uri != NULL); + } + + if (source == NULL) { + fm_directory_view_new_file_with_initial_contents (directory_view, + parent_uri != NULL ? parent_uri : container_uri, + NULL, + NULL, + 0, + NULL); + g_free (container_uri); + return; + } + + g_return_if_fail (caja_file_is_local (source)); + + pos = context_menu_to_file_operation_position (directory_view); + + data = setup_new_folder_data (directory_view); + + source_uri = caja_file_get_uri (source); + + caja_file_operations_new_file_from_template (GTK_WIDGET (directory_view), + pos, + parent_uri != NULL ? parent_uri : container_uri, + NULL, + source_uri, + new_folder_done, data); + + g_free (source_uri); + g_free (container_uri); +} + +/* handle the open command */ + +static void +open_one_in_new_window (gpointer data, gpointer callback_data) +{ + g_assert (CAJA_IS_FILE (data)); + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_activate_file (FM_DIRECTORY_VIEW (callback_data), + CAJA_FILE (data), + CAJA_WINDOW_OPEN_IN_NAVIGATION, + 0); +} + +static void +open_one_in_folder_window (gpointer data, gpointer callback_data) +{ + g_assert (CAJA_IS_FILE (data)); + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_activate_file (FM_DIRECTORY_VIEW (callback_data), + CAJA_FILE (data), + CAJA_WINDOW_OPEN_IN_SPATIAL, + 0); +} + +CajaFile * +fm_directory_view_get_directory_as_file (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + return view->details->directory_as_file; +} + +static void +open_with_launch_application_callback (GtkAction *action, + gpointer callback_data) +{ + ApplicationLaunchParameters *launch_parameters; + + launch_parameters = (ApplicationLaunchParameters *) callback_data; + caja_launch_application + (launch_parameters->application, + launch_parameters->files, + fm_directory_view_get_containing_window (launch_parameters->directory_view)); +} + +static char * +escape_action_name (const char *action_name, + const char *prefix) +{ + GString *s; + + if (action_name == NULL) { + return NULL; + } + + s = g_string_new (prefix); + + while (*action_name != 0) { + switch (*action_name) { + case '\\': + g_string_append (s, "\\\\"); + break; + case '/': + g_string_append (s, "\\s"); + break; + case '&': + g_string_append (s, "\\a"); + break; + case '"': + g_string_append (s, "\\q"); + break; + default: + g_string_append_c (s, *action_name); + } + + action_name ++; + } + return g_string_free (s, FALSE); +} + +static char * +escape_action_path (const char *action_path) +{ + GString *s; + + if (action_path == NULL) { + return NULL; + } + + s = g_string_sized_new (strlen (action_path) + 2); + + while (*action_path != 0) { + switch (*action_path) { + case '\\': + g_string_append (s, "\\\\"); + break; + case '&': + g_string_append (s, "\\a"); + break; + case '"': + g_string_append (s, "\\q"); + break; + default: + g_string_append_c (s, *action_path); + } + + action_path ++; + } + return g_string_free (s, FALSE); +} + + +static void +add_submenu (GtkUIManager *ui_manager, + GtkActionGroup *action_group, + guint merge_id, + const char *parent_path, + const char *uri, + const char *label, + GdkPixbuf *pixbuf, + gboolean add_action) +{ + char *escaped_label; + char *action_name; + char *submenu_name; + char *escaped_submenu_name; + GtkAction *action; + + if (parent_path != NULL) { + action_name = escape_action_name (uri, "submenu_"); + submenu_name = g_path_get_basename (uri); + escaped_submenu_name = escape_action_path (submenu_name); + escaped_label = eel_str_double_underscores (label); + + if (add_action) { + action = gtk_action_new (action_name, + escaped_label, + NULL, + NULL); + if (pixbuf != NULL) { + g_object_set_data_full (G_OBJECT (action), "menu-icon", + g_object_ref (pixbuf), + g_object_unref); + } + + g_object_set (action, "hide-if-empty", FALSE, NULL); + + gtk_action_group_add_action (action_group, + action); + g_object_unref (action); + } + + gtk_ui_manager_add_ui (ui_manager, + merge_id, + parent_path, + escaped_submenu_name, + action_name, + GTK_UI_MANAGER_MENU, + FALSE); + g_free (action_name); + g_free (escaped_label); + g_free (submenu_name); + g_free (escaped_submenu_name); + } +} + +static void +add_application_to_open_with_menu (FMDirectoryView *view, + GAppInfo *application, + GList *files, + int index, + const char *menu_placeholder, + const char *popup_placeholder, + const gboolean submenu) +{ + ApplicationLaunchParameters *launch_parameters; + char *tip; + char *label; + char *action_name; + char *escaped_app; + char *path; + GtkAction *action; + GIcon *app_icon; + GtkWidget *menuitem; + + launch_parameters = application_launch_parameters_new + (application, files, view); + escaped_app = eel_str_double_underscores (g_app_info_get_display_name (application)); + if (submenu) + label = g_strdup_printf ("%s", escaped_app); + else + label = g_strdup_printf (_("Open With %s"), escaped_app); + + tip = g_strdup_printf (ngettext ("Use \"%s\" to open the selected item", + "Use \"%s\" to open the selected items", + g_list_length (files)), + escaped_app); + g_free (escaped_app); + + action_name = g_strdup_printf ("open_with_%d", index); + + action = gtk_action_new (action_name, + label, + tip, + NULL); + + app_icon = g_app_info_get_icon (application); + if (app_icon != NULL) { + g_object_ref (app_icon); + } else { + app_icon = g_themed_icon_new ("application-x-executable"); + } + + gtk_action_set_gicon (action, app_icon); + g_object_unref (app_icon); + + g_signal_connect_data (action, "activate", + G_CALLBACK (open_with_launch_application_callback), + launch_parameters, + (GClosureNotify)application_launch_parameters_free, 0); + + gtk_action_group_add_action (view->details->open_with_action_group, + action); + g_object_unref (action); + + gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), + view->details->open_with_merge_id, + menu_placeholder, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + path = g_strdup_printf ("%s/%s", menu_placeholder, action_name); + menuitem = gtk_ui_manager_get_widget ( + caja_window_info_get_ui_manager (view->details->window), + path); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + g_free (path); + + gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), + view->details->open_with_merge_id, + popup_placeholder, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + path = g_strdup_printf ("%s/%s", popup_placeholder, action_name); + menuitem = gtk_ui_manager_get_widget ( + caja_window_info_get_ui_manager (view->details->window), + path); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + + g_free (path); + g_free (action_name); + g_free (label); + g_free (tip); +} + +static void +get_x_content_async_callback (char **content, + gpointer user_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (user_data); + + if (view->details->window != NULL) { + schedule_update_menus (view); + } + g_object_unref (view); +} + +static void +add_x_content_apps (FMDirectoryView *view, CajaFile *file, GList **applications) +{ + GMount *mount; + char **x_content_types; + unsigned int n; + + g_return_if_fail (applications != NULL); + + mount = caja_file_get_mount (file); + + if (mount == NULL) { + return; + } + + x_content_types = caja_autorun_get_cached_x_content_types_for_mount (mount); + if (x_content_types != NULL) { + for (n = 0; x_content_types[n] != NULL; n++) { + char *x_content_type = x_content_types[n]; + GList *app_info_for_x_content_type; + + app_info_for_x_content_type = g_app_info_get_all_for_type (x_content_type); + *applications = g_list_concat (*applications, app_info_for_x_content_type); + } + g_strfreev (x_content_types); + } else { + caja_autorun_get_x_content_types_for_mount_async (mount, + get_x_content_async_callback, + NULL, + g_object_ref (view)); + + } + + g_object_unref (mount); +} + +static void +reset_open_with_menu (FMDirectoryView *view, GList *selection) +{ + GList *applications, *node; + CajaFile *file; + gboolean submenu_visible, filter_default; + int num_applications; + int index; + gboolean other_applications_visible; + gboolean open_with_chooser_visible; + GtkUIManager *ui_manager; + GtkAction *action; + GAppInfo *default_app; + + /* Clear any previous inserted items in the applications and viewers placeholders */ + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + caja_ui_unmerge_ui (ui_manager, + &view->details->open_with_merge_id, + &view->details->open_with_action_group); + + caja_ui_prepare_merge_ui (ui_manager, + "OpenWithGroup", + &view->details->open_with_merge_id, + &view->details->open_with_action_group); + + num_applications = 0; + + other_applications_visible = (selection != NULL); + filter_default = (selection != NULL); + + for (node = selection; node != NULL; node = node->next) { + + file = CAJA_FILE (node->data); + + other_applications_visible &= + (!caja_mime_file_opens_in_view (file) || + caja_file_is_directory (file)); + } + + default_app = NULL; + if (filter_default) { + default_app = caja_mime_get_default_application_for_files (selection); + } + + applications = NULL; + if (other_applications_visible) { + applications = caja_mime_get_applications_for_files (selection); + } + + if (g_list_length (selection) == 1) { + add_x_content_apps (view, CAJA_FILE (selection->data), &applications); + } + + + num_applications = g_list_length (applications); + + if (file_list_all_are_folders (selection)) { + submenu_visible = (num_applications > 2); + } else { + submenu_visible = (num_applications > 3); + } + + for (node = applications, index = 0; node != NULL; node = node->next, index++) { + GAppInfo *application; + char *menu_path; + char *popup_path; + + application = node->data; + + if (default_app != NULL && g_app_info_equal (default_app, application)) { + continue; + } + + if (submenu_visible) { + menu_path = FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER; + popup_path = FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER; + } else { + menu_path = FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_PLACEHOLDER; + popup_path = FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_PLACEHOLDER; + } + + gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), + view->details->open_with_merge_id, + menu_path, + "separator", + NULL, + GTK_UI_MANAGER_SEPARATOR, + FALSE); + + add_application_to_open_with_menu (view, + node->data, + selection, + index, + menu_path, popup_path, submenu_visible); + } + eel_g_object_list_free (applications); + if (default_app != NULL) { + g_object_unref (default_app); + } + + open_with_chooser_visible = other_applications_visible && + g_list_length (selection) == 1; + + if (submenu_visible) { + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OTHER_APPLICATION1); + gtk_action_set_visible (action, open_with_chooser_visible); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OTHER_APPLICATION2); + gtk_action_set_visible (action, FALSE); + } else { + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OTHER_APPLICATION1); + gtk_action_set_visible (action, FALSE); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OTHER_APPLICATION2); + gtk_action_set_visible (action, open_with_chooser_visible); + } +} + +static GList * +get_all_extension_menu_items (GtkWidget *window, + GList *selection) +{ + GList *items; + GList *providers; + GList *l; + + providers = caja_module_get_extensions_for_type (CAJA_TYPE_MENU_PROVIDER); + items = NULL; + + for (l = providers; l != NULL; l = l->next) { + CajaMenuProvider *provider; + GList *file_items; + + provider = CAJA_MENU_PROVIDER (l->data); + file_items = caja_menu_provider_get_file_items (provider, + window, + selection); + items = g_list_concat (items, file_items); + } + + caja_module_extension_list_free (providers); + + return items; +} + +typedef struct +{ + CajaMenuItem *item; + FMDirectoryView *view; + GList *selection; + GtkAction *action; +} ExtensionActionCallbackData; + + +static void +extension_action_callback_data_free (ExtensionActionCallbackData *data) +{ + g_object_unref (data->item); + caja_file_list_free (data->selection); + + g_free (data); +} + +static gboolean +search_in_menu_items (GList* items, const char *item_name) +{ + GList* list; + + for (list = items; list != NULL; list = list->next) { + CajaMenu* menu; + char *name; + + g_object_get (list->data, "name", &name, NULL); + if (strcmp (name, item_name) == 0) { + g_free (name); + return TRUE; + } + g_free (name); + + menu = NULL; + g_object_get (list->data, "menu", &menu, NULL); + if (menu != NULL) { + gboolean ret; + GList* submenus; + + submenus = caja_menu_get_items (menu); + ret = search_in_menu_items (submenus, item_name); + caja_menu_item_list_free (submenus); + g_object_unref (menu); + if (ret) { + return TRUE; + } + } + } + return FALSE; +} + +static void +extension_action_callback (GtkAction *action, + gpointer callback_data) +{ + ExtensionActionCallbackData *data; + char *item_name; + gboolean is_valid; + GList *l; + GList *items; + + data = callback_data; + + /* Make sure the selected menu item is valid for the final sniffed + * mime type */ + g_object_get (data->item, "name", &item_name, NULL); + items = get_all_extension_menu_items (gtk_widget_get_toplevel (GTK_WIDGET (data->view)), + data->selection); + + is_valid = search_in_menu_items (items, item_name); + + for (l = items; l != NULL; l = l->next) { + g_object_unref (l->data); + } + g_list_free (items); + + g_free (item_name); + + if (is_valid) { + caja_menu_item_activate (data->item); + } +} + +static GdkPixbuf * +get_menu_icon (const char *icon_name) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf; + int size; + + size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + if (g_path_is_absolute (icon_name)) { + info = caja_icon_info_lookup_from_path (icon_name, size); + } else { + info = caja_icon_info_lookup_from_name (icon_name, size); + } + pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); + g_object_unref (info); + + return pixbuf; +} + +static GdkPixbuf * +get_menu_icon_for_file (CajaFile *file) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf; + int size; + + size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + info = caja_file_get_icon (file, size, 0); + pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); + g_object_unref (info); + + return pixbuf; +} + +static GtkAction * +add_extension_action_for_files (FMDirectoryView *view, + CajaMenuItem *item, + GList *files) +{ + char *name, *label, *tip, *icon; + gboolean sensitive, priority; + GtkAction *action; + GdkPixbuf *pixbuf; + ExtensionActionCallbackData *data; + + g_object_get (G_OBJECT (item), + "name", &name, "label", &label, + "tip", &tip, "icon", &icon, + "sensitive", &sensitive, + "priority", &priority, + NULL); + + action = gtk_action_new (name, + label, + tip, + icon); + + if (icon != NULL) { + pixbuf = get_menu_icon (icon); + if (pixbuf != NULL) { + g_object_set_data_full (G_OBJECT (action), "menu-icon", + pixbuf, + g_object_unref); + } + } + + gtk_action_set_sensitive (action, sensitive); + g_object_set (action, "is-important", priority, NULL); + + data = g_new0 (ExtensionActionCallbackData, 1); + data->item = g_object_ref (item); + data->view = view; + data->selection = caja_file_list_copy (files); + data->action = action; + + g_signal_connect_data (action, "activate", + G_CALLBACK (extension_action_callback), + data, + (GClosureNotify)extension_action_callback_data_free, 0); + + gtk_action_group_add_action (view->details->extensions_menu_action_group, + GTK_ACTION (action)); + g_object_unref (action); + + g_free (name); + g_free (label); + g_free (tip); + g_free (icon); + + return action; +} + +static void +add_extension_menu_items (FMDirectoryView *view, + GList *files, + GList *menu_items, + const char *subdirectory) +{ + GtkUIManager *ui_manager; + GList *l; + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + + for (l = menu_items; l; l = l->next) { + CajaMenuItem *item; + CajaMenu *menu; + GtkAction *action; + char *path; + + item = CAJA_MENU_ITEM (l->data); + + g_object_get (item, "menu", &menu, NULL); + + action = add_extension_action_for_files (view, item, files); + + path = g_build_path ("/", FM_DIRECTORY_VIEW_POPUP_PATH_EXTENSION_ACTIONS, subdirectory, NULL); + gtk_ui_manager_add_ui (ui_manager, + view->details->extensions_menu_merge_id, + path, + gtk_action_get_name (action), + gtk_action_get_name (action), + (menu != NULL) ? GTK_UI_MANAGER_MENU : GTK_UI_MANAGER_MENUITEM, + FALSE); + g_free (path); + + path = g_build_path ("/", FM_DIRECTORY_VIEW_MENU_PATH_EXTENSION_ACTIONS_PLACEHOLDER, subdirectory, NULL); + gtk_ui_manager_add_ui (ui_manager, + view->details->extensions_menu_merge_id, + path, + gtk_action_get_name (action), + gtk_action_get_name (action), + (menu != NULL) ? GTK_UI_MANAGER_MENU : GTK_UI_MANAGER_MENUITEM, + FALSE); + g_free (path); + + /* recursively fill the menu */ + if (menu != NULL) { + char *subdir; + GList *children; + + children = caja_menu_get_items (menu); + + subdir = g_build_path ("/", subdirectory, gtk_action_get_name (action), NULL); + add_extension_menu_items (view, + files, + children, + subdir); + + caja_menu_item_list_free (children); + g_free (subdir); + } + } +} + +static void +reset_extension_actions_menu (FMDirectoryView *view, GList *selection) +{ + GList *items; + GtkUIManager *ui_manager; + + /* Clear any previous inserted items in the extension actions placeholder */ + ui_manager = caja_window_info_get_ui_manager (view->details->window); + + caja_ui_unmerge_ui (ui_manager, + &view->details->extensions_menu_merge_id, + &view->details->extensions_menu_action_group); + + caja_ui_prepare_merge_ui (ui_manager, + "DirExtensionsMenuGroup", + &view->details->extensions_menu_merge_id, + &view->details->extensions_menu_action_group); + + items = get_all_extension_menu_items (gtk_widget_get_toplevel (GTK_WIDGET (view)), + selection); + if (items != NULL) { + add_extension_menu_items (view, selection, items, ""); + + g_list_foreach (items, (GFunc) g_object_unref, NULL); + g_list_free (items); + } +} + +static char * +change_to_view_directory (FMDirectoryView *view) +{ + char *path; + char *old_path; + + old_path = g_get_current_dir (); + + path = get_view_directory (view); + + /* FIXME: What to do about non-local directories? */ + if (path != NULL) { + g_chdir (path); + } + + g_free (path); + + return old_path; +} + +static char ** +get_file_names_as_parameter_array (GList *selection, + CajaDirectory *model) +{ + CajaFile *file; + char **parameters; + GList *node; + GFile *file_location; + GFile *model_location; + int i; + + if (model == NULL) { + return NULL; + } + + parameters = g_new (char *, g_list_length (selection) + 1); + + model_location = caja_directory_get_location (model); + + for (node = selection, i = 0; node != NULL; node = node->next, i++) { + file = CAJA_FILE (node->data); + + if (!caja_file_is_local (file)) { + parameters[i] = NULL; + g_strfreev (parameters); + return NULL; + } + + file_location = caja_file_get_location (CAJA_FILE (node->data)); + parameters[i] = g_file_get_relative_path (model_location, file_location); + if (parameters[i] == NULL) { + parameters[i] = g_file_get_path (file_location); + } + g_object_unref (file_location); + } + + g_object_unref (model_location); + + parameters[i] = NULL; + return parameters; +} + +static char * +get_file_paths_or_uris_as_newline_delimited_string (GList *selection, gboolean get_paths) +{ + char *path; + char *uri; + char *result; + CajaDesktopLink *link; + GString *expanding_string; + GList *node; + GFile *location; + + expanding_string = g_string_new (""); + for (node = selection; node != NULL; node = node->next) { + uri = NULL; + if (CAJA_IS_DESKTOP_ICON_FILE (node->data)) { + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (node->data)); + if (link != NULL) { + location = caja_desktop_link_get_activation_location (link); + uri = g_file_get_uri (location); + g_object_unref (location); + g_object_unref (G_OBJECT (link)); + } + } else { + uri = caja_file_get_uri (CAJA_FILE (node->data)); + } + if (uri == NULL) { + continue; + } + + if (get_paths) { + path = g_filename_from_uri (uri, NULL, NULL); + if (path != NULL) { + g_string_append (expanding_string, path); + g_free (path); + g_string_append (expanding_string, "\n"); + } + } else { + g_string_append (expanding_string, uri); + g_string_append (expanding_string, "\n"); + } + g_free (uri); + } + + result = expanding_string->str; + g_string_free (expanding_string, FALSE); + + return result; +} + +static char * +get_file_paths_as_newline_delimited_string (GList *selection) +{ + return get_file_paths_or_uris_as_newline_delimited_string (selection, TRUE); +} + +static char * +get_file_uris_as_newline_delimited_string (GList *selection) +{ + return get_file_paths_or_uris_as_newline_delimited_string (selection, FALSE); +} + +/* returns newly allocated strings for setting the environment variables */ +static void +get_strings_for_environment_variables (FMDirectoryView *view, GList *selected_files, + char **file_paths, char **uris, char **uri) +{ + char *directory_uri; + + /* We need to check that the directory uri starts with "file:" since + * caja_directory_is_local returns FALSE for nfs. + */ + directory_uri = caja_directory_get_uri (view->details->model); + if (eel_str_has_prefix (directory_uri, "file:") || + eel_uri_is_desktop (directory_uri) || + eel_uri_is_trash (directory_uri)) { + *file_paths = get_file_paths_as_newline_delimited_string (selected_files); + } else { + *file_paths = g_strdup (""); + } + g_free (directory_uri); + + *uris = get_file_uris_as_newline_delimited_string (selected_files); + + *uri = caja_directory_get_uri (view->details->model); + if (eel_uri_is_desktop (*uri)) { + g_free (*uri); + *uri = caja_get_desktop_directory_uri (); + } +} + +static FMDirectoryView * +get_directory_view_of_extra_pane (FMDirectoryView *view) +{ + CajaWindowSlotInfo *slot; + CajaView *next_view; + + slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); + if (slot != NULL) { + next_view = caja_window_slot_info_get_current_view (slot); + + if (FM_IS_DIRECTORY_VIEW (next_view)) { + return FM_DIRECTORY_VIEW (next_view); + } + } + return NULL; +} + +/* + * Set up some environment variables that scripts can use + * to take advantage of the current Caja state. + */ +static void set_script_environment_variables(FMDirectoryView* view, GList* selected_files) +{ + char* file_paths; + char* uris; + char* uri; + char* geometry_string; + FMDirectoryView* next_view; + + get_strings_for_environment_variables(view, selected_files, &file_paths, &uris, &uri); + + g_setenv("CAJA_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); + g_setenv("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); // compatibilidad GNOME + + g_free(file_paths); + + g_setenv("CAJA_SCRIPT_SELECTED_URIS", uris, TRUE); + g_setenv("NAUTILUS_SCRIPT_SELECTED_URIS", uris, TRUE); // compatibilidad GNOME + + g_free(uris); + + g_setenv("CAJA_SCRIPT_CURRENT_URI", uri, TRUE); + g_setenv("NAUTILUS_SCRIPT_CURRENT_URI", uri, TRUE); // compatibilidad GNOME + + + g_free(uri); + + geometry_string = eel_gtk_window_get_geometry_string(GTK_WINDOW (fm_directory_view_get_containing_window (view))); + + g_setenv("CAJA_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE); + g_setenv("NAUTILUS_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE); // compatibilidad GNOME + + g_free(geometry_string); + + /* next pane */ + next_view = get_directory_view_of_extra_pane(view); + + if (next_view) + { + GList* next_pane_selected_files = fm_directory_view_get_selection (next_view); + + get_strings_for_environment_variables(next_view, next_pane_selected_files, &file_paths, &uris, &uri); + + caja_file_list_free(next_pane_selected_files); + } + else + { + file_paths = g_strdup(""); + uris = g_strdup(""); + uri = g_strdup(""); + } + + g_setenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS", file_paths, TRUE); + g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS", file_paths, TRUE); // compatibilidad GNOME + g_free(file_paths); + + g_setenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS", uris, TRUE); + g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_URIS", uris, TRUE); // compatibilidad GNOME + g_free(uris); + + g_setenv("CAJA_SCRIPT_NEXT_PANE_CURRENT_URI", uri, TRUE); + g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_CURRENT_URI", uri, TRUE); // compatibilidad GNOME + g_free(uri); +} + +/* Unset all the special script environment variables. */ +static void unset_script_environment_variables(void) +{ + g_unsetenv("CAJA_SCRIPT_SELECTED_FILE_PATHS"); + g_unsetenv("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"); + + g_unsetenv("CAJA_SCRIPT_SELECTED_URIS"); + g_unsetenv("NAUTILUS_SCRIPT_SELECTED_URIS"); + + g_unsetenv("CAJA_SCRIPT_CURRENT_URI"); + g_unsetenv("NAUTILUS_SCRIPT_CURRENT_URI"); + + g_unsetenv("CAJA_SCRIPT_WINDOW_GEOMETRY"); + g_unsetenv("NAUTILUS_SCRIPT_WINDOW_GEOMETRY"); + + g_unsetenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS"); + g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS"); + + g_unsetenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS"); + g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_URIS"); + + g_unsetenv("CAJA_SCRIPT_NEXT_PANE_CURRENT_URI"); + g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_CURRENT_URI"); +} + +static void +run_script_callback (GtkAction *action, gpointer callback_data) +{ + ScriptLaunchParameters *launch_parameters; + GdkScreen *screen; + GList *selected_files; + char *file_uri; + char *local_file_path; + char *quoted_path; + char *old_working_dir; + char **parameters, *name; + GtkWindow *window; + + launch_parameters = (ScriptLaunchParameters *) callback_data; + + file_uri = caja_file_get_uri (launch_parameters->file); + local_file_path = g_filename_from_uri (file_uri, NULL, NULL); + g_assert (local_file_path != NULL); + g_free (file_uri); + + quoted_path = g_shell_quote (local_file_path); + g_free (local_file_path); + + old_working_dir = change_to_view_directory (launch_parameters->directory_view); + + selected_files = fm_directory_view_get_selection (launch_parameters->directory_view); + set_script_environment_variables (launch_parameters->directory_view, selected_files); + + parameters = get_file_names_as_parameter_array (selected_files, + launch_parameters->directory_view->details->model); + + screen = gtk_widget_get_screen (GTK_WIDGET (launch_parameters->directory_view)); + + name = caja_file_get_name (launch_parameters->file); + /* FIXME: handle errors with dialog? Or leave up to each script? */ + window = fm_directory_view_get_containing_window (launch_parameters->directory_view); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "directory view run_script_callback, window=%p, name=\"%s\", script_path=\"%s\" (omitting script parameters)", + window, name, local_file_path); + caja_launch_application_from_command_array (screen, name, quoted_path, FALSE, + (const char * const *) parameters); + g_free (name); + g_strfreev (parameters); + + caja_file_list_free (selected_files); + unset_script_environment_variables (); + g_chdir (old_working_dir); + g_free (old_working_dir); + g_free (quoted_path); +} + +static void +add_script_to_scripts_menus (FMDirectoryView *directory_view, + CajaFile *file, + const char *menu_path, + const char *popup_path, + const char *popup_bg_path) +{ + ScriptLaunchParameters *launch_parameters; + char *tip; + char *name; + char *uri; + char *action_name; + char *escaped_label; + GdkPixbuf *pixbuf; + GtkUIManager *ui_manager; + GtkAction *action; + + name = caja_file_get_display_name (file); + uri = caja_file_get_uri (file); + tip = g_strdup_printf (_("Run \"%s\" on any selected items"), name); + + launch_parameters = script_launch_parameters_new (file, directory_view); + + action_name = escape_action_name (uri, "script_"); + escaped_label = eel_str_double_underscores (name); + + action = gtk_action_new (action_name, + escaped_label, + tip, + NULL); + + pixbuf = get_menu_icon_for_file (file); + if (pixbuf != NULL) { + g_object_set_data_full (G_OBJECT (action), "menu-icon", + pixbuf, + g_object_unref); + } + + g_signal_connect_data (action, "activate", + G_CALLBACK (run_script_callback), + launch_parameters, + (GClosureNotify)script_launch_parameters_free, 0); + + gtk_action_group_add_action_with_accel (directory_view->details->scripts_action_group, + action, NULL); + g_object_unref (action); + + ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); + + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->scripts_merge_id, + menu_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->scripts_merge_id, + popup_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->scripts_merge_id, + popup_bg_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + g_free (name); + g_free (uri); + g_free (tip); + g_free (action_name); + g_free (escaped_label); +} + +static void +add_submenu_to_directory_menus (FMDirectoryView *directory_view, + GtkActionGroup *action_group, + guint merge_id, + CajaFile *file, + const char *menu_path, + const char *popup_path, + const char *popup_bg_path) +{ + char *name; + GdkPixbuf *pixbuf; + char *uri; + GtkUIManager *ui_manager; + + ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); + uri = caja_file_get_uri (file); + name = caja_file_get_display_name (file); + pixbuf = get_menu_icon_for_file (file); + add_submenu (ui_manager, action_group, merge_id, menu_path, uri, name, pixbuf, TRUE); + add_submenu (ui_manager, action_group, merge_id, popup_path, uri, name, pixbuf, FALSE); + add_submenu (ui_manager, action_group, merge_id, popup_bg_path, uri, name, pixbuf, FALSE); + if (pixbuf) { + g_object_unref (pixbuf); + } + g_free (name); + g_free (uri); +} + +static gboolean +directory_belongs_in_scripts_menu (const char *uri) +{ + int num_levels; + int i; + + if (!eel_str_has_prefix (uri, scripts_directory_uri)) { + return FALSE; + } + + num_levels = 0; + for (i = scripts_directory_uri_length; uri[i] != '\0'; i++) { + if (uri[i] == '/') { + num_levels++; + } + } + + if (num_levels > MAX_MENU_LEVELS) { + return FALSE; + } + + return TRUE; +} + +static gboolean +update_directory_in_scripts_menu (FMDirectoryView *view, CajaDirectory *directory) +{ + char *menu_path, *popup_path, *popup_bg_path; + GList *file_list, *filtered, *node; + gboolean any_scripts; + CajaFile *file; + CajaDirectory *dir; + char *uri; + char *escaped_path; + + uri = caja_directory_get_uri (directory); + escaped_path = escape_action_path (uri + scripts_directory_uri_length); + g_free (uri); + menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER, + escaped_path, + NULL); + popup_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_SCRIPTS_PLACEHOLDER, + escaped_path, + NULL); + popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER, + escaped_path, + NULL); + g_free (escaped_path); + + file_list = caja_directory_get_file_list (directory); + filtered = caja_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE); + caja_file_list_free (file_list); + + file_list = caja_file_list_sort_by_display_name (filtered); + + any_scripts = FALSE; + for (node = file_list; node != NULL; node = node->next) { + file = node->data; + + if (caja_file_is_launchable (file)) { + add_script_to_scripts_menus (view, file, menu_path, popup_path, popup_bg_path); + any_scripts = TRUE; + } else if (caja_file_is_directory (file)) { + uri = caja_file_get_uri (file); + if (directory_belongs_in_scripts_menu (uri)) { + dir = caja_directory_get_by_uri (uri); + add_directory_to_scripts_directory_list (view, dir); + caja_directory_unref (dir); + + add_submenu_to_directory_menus (view, + view->details->scripts_action_group, + view->details->scripts_merge_id, + file, menu_path, popup_path, popup_bg_path); + + any_scripts = TRUE; + } + g_free (uri); + } + } + + caja_file_list_free (file_list); + + g_free (popup_path); + g_free (popup_bg_path); + g_free (menu_path); + + return any_scripts; +} + +static void +update_scripts_menu (FMDirectoryView *view) +{ + gboolean any_scripts; + GList *sorted_copy, *node; + CajaDirectory *directory; + char *uri; + GtkUIManager *ui_manager; + GtkAction *action; + + /* There is a race condition here. If we don't mark the scripts menu as + valid before we begin our task then we can lose script menu updates that + occur before we finish. */ + view->details->scripts_invalid = FALSE; + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + caja_ui_unmerge_ui (ui_manager, + &view->details->scripts_merge_id, + &view->details->scripts_action_group); + + caja_ui_prepare_merge_ui (ui_manager, + "ScriptsGroup", + &view->details->scripts_merge_id, + &view->details->scripts_action_group); + + /* As we walk through the directories, remove any that no longer belong. */ + any_scripts = FALSE; + sorted_copy = caja_directory_list_sort_by_uri + (caja_directory_list_copy (view->details->scripts_directory_list)); + for (node = sorted_copy; node != NULL; node = node->next) { + directory = node->data; + + uri = caja_directory_get_uri (directory); + if (!directory_belongs_in_scripts_menu (uri)) { + remove_directory_from_scripts_directory_list (view, directory); + } else if (update_directory_in_scripts_menu (view, directory)) { + any_scripts = TRUE; + } + g_free (uri); + } + caja_directory_list_free (sorted_copy); + + action = gtk_action_group_get_action (view->details->dir_action_group, FM_ACTION_SCRIPTS); + gtk_action_set_visible (action, any_scripts); +} + +static void +create_template_callback (GtkAction *action, gpointer callback_data) +{ + CreateTemplateParameters *parameters; + + parameters = callback_data; + + fm_directory_view_new_file (parameters->directory_view, NULL, parameters->file); +} + +static void +add_template_to_templates_menus (FMDirectoryView *directory_view, + CajaFile *file, + const char *menu_path, + const char *popup_bg_path) +{ + char *tmp, *tip, *uri, *name; + char *escaped_label; + GdkPixbuf *pixbuf; + char *action_name; + CreateTemplateParameters *parameters; + GtkUIManager *ui_manager; + GtkAction *action; + + tmp = caja_file_get_display_name (file); + name = eel_filename_strip_extension (tmp); + g_free (tmp); + + uri = caja_file_get_uri (file); + tip = g_strdup_printf (_("Create Document from template \"%s\""), name); + + action_name = escape_action_name (uri, "template_"); + escaped_label = eel_str_double_underscores (name); + + parameters = create_template_parameters_new (file, directory_view); + + action = gtk_action_new (action_name, + escaped_label, + tip, + NULL); + + pixbuf = get_menu_icon_for_file (file); + if (pixbuf != NULL) { + g_object_set_data_full (G_OBJECT (action), "menu-icon", + pixbuf, + g_object_unref); + } + + g_signal_connect_data (action, "activate", + G_CALLBACK (create_template_callback), + parameters, + (GClosureNotify)create_templates_parameters_free, 0); + + gtk_action_group_add_action (directory_view->details->templates_action_group, + action); + g_object_unref (action); + + ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); + + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->templates_merge_id, + menu_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->templates_merge_id, + popup_bg_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + g_free (escaped_label); + g_free (name); + g_free (tip); + g_free (uri); + g_free (action_name); +} + +static void +update_templates_directory (FMDirectoryView *view) +{ + CajaDirectory *templates_directory; + GList *node, *next; + char *templates_uri; + + for (node = view->details->templates_directory_list; node != NULL; node = next) { + next = node->next; + remove_directory_from_templates_directory_list (view, node->data); + } + + if (caja_should_use_templates_directory ()) { + templates_uri = caja_get_templates_directory_uri (); + templates_directory = caja_directory_get_by_uri (templates_uri); + g_free (templates_uri); + add_directory_to_templates_directory_list (view, templates_directory); + caja_directory_unref (templates_directory); + } +} + +static void +user_dirs_changed (FMDirectoryView *view) +{ + update_templates_directory (view); + view->details->templates_invalid = TRUE; + schedule_update_menus (view); +} + +static gboolean +directory_belongs_in_templates_menu (const char *templates_directory_uri, + const char *uri) +{ + int num_levels; + int i; + + if (templates_directory_uri == NULL) { + return FALSE; + } + + if (!g_str_has_prefix (uri, templates_directory_uri)) { + return FALSE; + } + + num_levels = 0; + for (i = strlen (templates_directory_uri); uri[i] != '\0'; i++) { + if (uri[i] == '/') { + num_levels++; + } + } + + if (num_levels > MAX_MENU_LEVELS) { + return FALSE; + } + + return TRUE; +} + +static gboolean +update_directory_in_templates_menu (FMDirectoryView *view, + const char *templates_directory_uri, + CajaDirectory *directory) +{ + char *menu_path, *popup_bg_path; + GList *file_list, *filtered, *node; + gboolean any_templates; + CajaFile *file; + CajaDirectory *dir; + char *escaped_path; + char *uri; + int num; + + /* We know this directory belongs to the template dir, so it must exist */ + g_assert (templates_directory_uri); + + uri = caja_directory_get_uri (directory); + escaped_path = escape_action_path (uri + strlen (templates_directory_uri)); + g_free (uri); + menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER, + escaped_path, + NULL); + popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER, + escaped_path, + NULL); + g_free (escaped_path); + + file_list = caja_directory_get_file_list (directory); + filtered = caja_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE); + caja_file_list_free (file_list); + + file_list = caja_file_list_sort_by_display_name (filtered); + + num = 0; + any_templates = FALSE; + for (node = file_list; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++) { + file = node->data; + + if (caja_file_is_directory (file)) { + uri = caja_file_get_uri (file); + if (directory_belongs_in_templates_menu (templates_directory_uri, uri)) { + dir = caja_directory_get_by_uri (uri); + add_directory_to_templates_directory_list (view, dir); + caja_directory_unref (dir); + + add_submenu_to_directory_menus (view, + view->details->templates_action_group, + view->details->templates_merge_id, + file, menu_path, NULL, popup_bg_path); + + any_templates = TRUE; + } + g_free (uri); + } else if (caja_file_can_read (file)) { + add_template_to_templates_menus (view, file, menu_path, popup_bg_path); + any_templates = TRUE; + } + } + + caja_file_list_free (file_list); + + g_free (popup_bg_path); + g_free (menu_path); + + return any_templates; +} + + + +static void +update_templates_menu (FMDirectoryView *view) +{ + gboolean any_templates; + GList *sorted_copy, *node; + CajaDirectory *directory; + GtkUIManager *ui_manager; + char *uri; + GtkAction *action; + char *templates_directory_uri; + + if (caja_should_use_templates_directory ()) { + templates_directory_uri = caja_get_templates_directory_uri (); + } else { + templates_directory_uri = NULL; + } + + /* There is a race condition here. If we don't mark the scripts menu as + valid before we begin our task then we can lose template menu updates that + occur before we finish. */ + view->details->templates_invalid = FALSE; + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + caja_ui_unmerge_ui (ui_manager, + &view->details->templates_merge_id, + &view->details->templates_action_group); + + caja_ui_prepare_merge_ui (ui_manager, + "TemplatesGroup", + &view->details->templates_merge_id, + &view->details->templates_action_group); + + /* As we walk through the directories, remove any that no longer belong. */ + any_templates = FALSE; + sorted_copy = caja_directory_list_sort_by_uri + (caja_directory_list_copy (view->details->templates_directory_list)); + for (node = sorted_copy; node != NULL; node = node->next) { + directory = node->data; + + uri = caja_directory_get_uri (directory); + if (!directory_belongs_in_templates_menu (templates_directory_uri, uri)) { + remove_directory_from_templates_directory_list (view, directory); + } else if (update_directory_in_templates_menu (view, + templates_directory_uri, + directory)) { + any_templates = TRUE; + } + g_free (uri); + } + caja_directory_list_free (sorted_copy); + + action = gtk_action_group_get_action (view->details->dir_action_group, FM_ACTION_NO_TEMPLATES); + gtk_action_set_visible (action, !any_templates); + + g_free (templates_directory_uri); +} + + +static void +action_open_scripts_folder_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + open_location (view, scripts_directory_uri, CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, 0); + + eel_show_info_dialog_with_details + (_("All executable files in this folder will appear in the " + "Scripts menu."), + _("Choosing a script from the menu will run " + "that script with any selected items as input."), + _("All executable files in this folder will appear in the " + "Scripts menu. Choosing a script from the menu will run " + "that script.\n\n" + "When executed from a local folder, scripts will be passed " + "the selected file names. When executed from a remote folder " + "(e.g. a folder showing web or ftp content), scripts will " + "be passed no parameters.\n\n" + "In all cases, the following environment variables will be " + "set by Caja, which the scripts may use:\n\n" + "CAJA_SCRIPT_SELECTED_FILE_PATHS: newline-delimited paths for selected files (only if local)\n\n" + "CAJA_SCRIPT_SELECTED_URIS: newline-delimited URIs for selected files\n\n" + "CAJA_SCRIPT_CURRENT_URI: URI for current location\n\n" + "CAJA_SCRIPT_WINDOW_GEOMETRY: position and size of current window\n\n" + "CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS: newline-delimited paths for selected files in the inactive pane of a split-view window (only if local)\n\n" + "CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS: newline-delimited URIs for selected files in the inactive pane of a split-view window\n\n" + "CAJA_SCRIPT_NEXT_PANE_CURRENT_URI: URI for current location in the inactive pane of a split-view window"), + fm_directory_view_get_containing_window (view)); +} + +static GtkMenu * +create_popup_menu (FMDirectoryView *view, const char *popup_path) +{ + GtkWidget *menu; + + menu = gtk_ui_manager_get_widget (caja_window_info_get_ui_manager (view->details->window), + popup_path); + gtk_menu_set_screen (GTK_MENU (menu), + gtk_widget_get_screen (GTK_WIDGET (view))); + gtk_widget_show (GTK_WIDGET (menu)); + + return GTK_MENU (menu); +} + +static void +copy_or_cut_files (FMDirectoryView *view, + GList *clipboard_contents, + gboolean cut) +{ + int count; + char *status_string, *name; + CajaClipboardInfo info; + GtkTargetList *target_list; + GtkTargetEntry *targets; + int n_targets; + + info.files = clipboard_contents; + info.cut = cut; + + target_list = gtk_target_list_new (NULL, 0); + gtk_target_list_add (target_list, copied_files_atom, 0, 0); + gtk_target_list_add_uri_targets (target_list, 0); + gtk_target_list_add_text_targets (target_list, 0); + + targets = gtk_target_table_new_from_list (target_list, &n_targets); + gtk_target_list_unref (target_list); + + gtk_clipboard_set_with_data (caja_clipboard_get (GTK_WIDGET (view)), + targets, n_targets, + caja_get_clipboard_callback, caja_clear_clipboard_callback, + NULL); + gtk_target_table_free (targets, n_targets); + + caja_clipboard_monitor_set_clipboard_info (caja_clipboard_monitor_get (), &info); + + count = g_list_length (clipboard_contents); + if (count == 1) { + name = caja_file_get_display_name (clipboard_contents->data); + if (cut) { + status_string = g_strdup_printf (_("\"%s\" will be moved " + "if you select the Paste command"), + name); + } else { + status_string = g_strdup_printf (_("\"%s\" will be copied " + "if you select the Paste command"), + name); + } + g_free (name); + } else { + if (cut) { + status_string = g_strdup_printf (ngettext("The %'d selected item will be moved " + "if you select the Paste command", + "The %'d selected items will be moved " + "if you select the Paste command", + count), + count); + } else { + status_string = g_strdup_printf (ngettext("The %'d selected item will be copied " + "if you select the Paste command", + "The %'d selected items will be copied " + "if you select the Paste command", + count), + count); + } + } + + caja_window_slot_info_set_status (view->details->slot, + status_string); + g_free (status_string); +} + +static void +action_copy_files_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + + view = FM_DIRECTORY_VIEW (callback_data); + + selection = fm_directory_view_get_selection_for_file_transfer (view); + copy_or_cut_files (view, selection, FALSE); + caja_file_list_free (selection); +} + +static void +move_copy_selection_to_location (FMDirectoryView *view, + int copy_action, + char *target_uri) +{ + GList *selection, *uris, *l; + + selection = fm_directory_view_get_selection_for_file_transfer (view); + if (selection == NULL) { + return; + } + + uris = NULL; + for (l = selection; l != NULL; l = l->next) { + uris = g_list_prepend (uris, + caja_file_get_uri ((CajaFile *) l->data)); + } + uris = g_list_reverse (uris); + + fm_directory_view_move_copy_items (uris, NULL, target_uri, + copy_action, + 0, 0, + view); + + eel_g_list_free_deep (uris); + caja_file_list_free (selection); +} + +static void +move_copy_selection_to_next_pane (FMDirectoryView *view, + int copy_action) +{ + CajaWindowSlotInfo *slot; + char *dest_location; + + slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); + g_return_if_fail (slot != NULL); + + dest_location = caja_window_slot_info_get_current_location (slot); + g_return_if_fail (dest_location != NULL); + + move_copy_selection_to_location (view, copy_action, dest_location); +} + +static void +action_copy_to_next_pane_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + move_copy_selection_to_next_pane (view, + GDK_ACTION_COPY); +} + +static void +action_move_to_next_pane_callback (GtkAction *action, gpointer callback_data) +{ + CajaWindowSlotInfo *slot; + char *dest_location; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); + g_return_if_fail (slot != NULL); + + dest_location = caja_window_slot_info_get_current_location (slot); + g_return_if_fail (dest_location != NULL); + + move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); +} + +static void +action_copy_to_home_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + char *dest_location; + + view = FM_DIRECTORY_VIEW (callback_data); + + dest_location = caja_get_home_directory_uri (); + move_copy_selection_to_location (view, GDK_ACTION_COPY, dest_location); + g_free (dest_location); +} + +static void +action_move_to_home_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + char *dest_location; + + view = FM_DIRECTORY_VIEW (callback_data); + + dest_location = caja_get_home_directory_uri (); + move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); + g_free (dest_location); +} + +static void +action_copy_to_desktop_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + char *dest_location; + + view = FM_DIRECTORY_VIEW (callback_data); + + dest_location = caja_get_desktop_directory_uri (); + move_copy_selection_to_location (view, GDK_ACTION_COPY, dest_location); + g_free (dest_location); +} + +static void +action_move_to_desktop_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + char *dest_location; + + view = FM_DIRECTORY_VIEW (callback_data); + + dest_location = caja_get_desktop_directory_uri (); + move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); + g_free (dest_location); +} + +static void +action_cut_files_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + + view = FM_DIRECTORY_VIEW (callback_data); + + selection = fm_directory_view_get_selection_for_file_transfer (view); + copy_or_cut_files (view, selection, TRUE); + caja_file_list_free (selection); +} + +static void +paste_clipboard_data (FMDirectoryView *view, + GtkSelectionData *selection_data, + char *destination_uri) +{ + gboolean cut; + GList *item_uris; + + cut = FALSE; + item_uris = caja_clipboard_get_uri_list_from_selection_data (selection_data, &cut, + copied_files_atom); + + if (item_uris == NULL|| destination_uri == NULL) { + caja_window_slot_info_set_status (view->details->slot, + _("There is nothing on the clipboard to paste.")); + } else { + fm_directory_view_move_copy_items (item_uris, NULL, destination_uri, + cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY, + 0, 0, + view); + + /* If items are cut then remove from clipboard */ + if (cut) { + gtk_clipboard_clear (caja_clipboard_get (GTK_WIDGET (view))); + } + + eel_g_list_free_deep (item_uris); + } +} + +static void +paste_clipboard_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + FMDirectoryView *view; + char *view_uri; + + view = FM_DIRECTORY_VIEW (data); + + view_uri = fm_directory_view_get_backing_uri (view); + + if (view->details->window != NULL) { + paste_clipboard_data (view, selection_data, view_uri); + } + + g_free (view_uri); + + g_object_unref (view); +} + +typedef struct { + FMDirectoryView *view; + CajaFile *target; +} PasteIntoData; + +static void +paste_into_clipboard_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer callback_data) +{ + PasteIntoData *data; + FMDirectoryView *view; + char *directory_uri; + + data = (PasteIntoData *) callback_data; + + view = FM_DIRECTORY_VIEW (data->view); + + if (view->details->window != NULL) { + directory_uri = caja_file_get_activation_uri (data->target); + + paste_clipboard_data (view, selection_data, directory_uri); + + g_free (directory_uri); + } + + g_object_unref (view); + caja_file_unref (data->target); + g_free (data); +} + +static void +action_paste_files_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + g_object_ref (view); + gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view)), + copied_files_atom, + paste_clipboard_received_callback, + view); +} + +static void +paste_into (FMDirectoryView *view, + CajaFile *target) +{ + PasteIntoData *data; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (CAJA_IS_FILE (target)); + + data = g_new (PasteIntoData, 1); + + data->view = g_object_ref (view); + data->target = caja_file_ref (target); + + gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view)), + copied_files_atom, + paste_into_clipboard_received_callback, + data); +} + +static void +action_paste_files_into_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + + view = FM_DIRECTORY_VIEW (callback_data); + selection = fm_directory_view_get_selection (view); + if (selection != NULL) { + paste_into (view, CAJA_FILE (selection->data)); + caja_file_list_free (selection); + } + +} + +static void +real_action_rename (FMDirectoryView *view, + gboolean select_all) +{ + CajaFile *file; + GList *selection; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + if (selection_not_empty_in_menu_callback (view, selection)) { + file = CAJA_FILE (selection->data); + if (!select_all) { + /* directories don't have a file extension, so + * they are always pre-selected as a whole */ + select_all = caja_file_is_directory (file); + } + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, file, select_all)); + } + + caja_file_list_free (selection); +} + +static void +action_rename_callback (GtkAction *action, + gpointer callback_data) +{ + real_action_rename (FM_DIRECTORY_VIEW (callback_data), FALSE); +} + +static void +action_rename_select_all_callback (GtkAction *action, + gpointer callback_data) +{ + real_action_rename (FM_DIRECTORY_VIEW (callback_data), TRUE); +} + +static void +file_mount_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED))) { + eel_show_error_dialog (_("Unable to mount location"), + error->message, NULL); + } +} + +static void +file_unmount_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + fm_directory_view_set_initiated_unmount (view, FALSE); + g_object_unref (view); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) { + eel_show_error_dialog (_("Unable to unmount location"), + error->message, NULL); + } +} + +static void +file_eject_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + fm_directory_view_set_initiated_unmount (view, FALSE); + g_object_unref (view); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) { + eel_show_error_dialog (_("Unable to eject location"), + error->message, NULL); + } +} + +static void +file_stop_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) { + eel_show_error_dialog (_("Unable to stop drive"), + error->message, NULL); + } +} + +static void +action_mount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_mount (file)) { + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + caja_file_mount (file, mount_op, NULL, + file_mount_callback, NULL); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +action_unmount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + if (caja_file_can_unmount (file)) { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_unmount (file, mount_op, NULL, + file_unmount_callback, g_object_ref (view)); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +action_format_volume_callback (GtkAction *action, + gpointer data) +{ +#ifdef TODO_GIO + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (something) { + g_spawn_command_line_async ("gfloppy", NULL); + } + } + caja_file_list_free (selection); +#endif +} + +static void +action_eject_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_eject (file)) { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_eject (file, mount_op, NULL, + file_eject_callback, g_object_ref (view)); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +file_start_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED))) { + eel_show_error_dialog (_("Unable to start location"), + error->message, NULL); + } +} + +static void +action_start_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_start (file, mount_op, NULL, + file_start_callback, NULL); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +action_stop_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_stop (file)) { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_stop (file, mount_op, NULL, + file_stop_callback, NULL); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +action_detect_media_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { + caja_file_poll_for_media (file); + } + } + caja_file_list_free (selection); +} + +static void +action_self_mount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + caja_file_mount (file, mount_op, NULL, file_mount_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_self_unmount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_unmount (file, mount_op, NULL, file_unmount_callback, g_object_ref (view)); + g_object_unref (mount_op); +} + +static void +action_self_eject_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_eject (file, mount_op, NULL, file_eject_callback, g_object_ref (view)); + g_object_unref (mount_op); +} + +static void +action_self_format_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + +#ifdef TODO_GIO + if (something) { + g_spawn_command_line_async ("gfloppy", NULL); + } +#endif +} + +static void +action_self_start_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_start (file, mount_op, NULL, file_start_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_self_stop_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_stop (file, mount_op, NULL, + file_stop_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_self_detect_media_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + caja_file_poll_for_media (file); +} + +static void +action_location_mount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + caja_file_mount (file, mount_op, NULL, file_mount_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_location_unmount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_unmount (file, mount_op, NULL, + file_unmount_callback, g_object_ref (view)); + g_object_unref (mount_op); +} + +static void +action_location_eject_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_eject (file, mount_op, NULL, + file_eject_callback, g_object_ref (view)); + g_object_unref (mount_op); +} + +static void +action_location_format_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + +#ifdef TODO_GIO + if (something) { + g_spawn_command_line_async ("gfloppy", NULL); + } +#endif +} + +static void +action_location_start_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_start (file, mount_op, NULL, file_start_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_location_stop_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_stop (file, mount_op, NULL, + file_stop_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_location_detect_media_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + caja_file_poll_for_media (file); +} + +static void +connect_to_server_response_callback (GtkDialog *dialog, + int response_id, + gpointer data) +{ + GtkEntry *entry; + char *uri; + const char *name; + char *icon; + + entry = GTK_ENTRY (data); + + switch (response_id) { + case GTK_RESPONSE_OK: + uri = g_object_get_data (G_OBJECT (dialog), "link-uri"); + icon = g_object_get_data (G_OBJECT (dialog), "link-icon"); + name = gtk_entry_get_text (entry); +#ifdef GIO_CONVERSION_DONE + mate_vfs_connect_to_server (uri, (char *)name, icon); +#endif + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + case GTK_RESPONSE_NONE: + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_CANCEL: + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + default : + g_assert_not_reached (); + } +} + +static void +entry_activate_callback (GtkEntry *entry, + gpointer user_data) +{ + GtkDialog *dialog; + + dialog = GTK_DIALOG (user_data); + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); +} + +static void +action_connect_to_server_link_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection; + FMDirectoryView *view; + char *uri; + CajaIconInfo *icon; + const char *icon_name; + char *name; + GtkWidget *dialog; + GtkWidget *label; + GtkWidget *entry; + GtkWidget *box; + char *title; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + + if (!eel_g_list_exactly_one_item (selection)) { + caja_file_list_free (selection); + return; + } + + file = CAJA_FILE (selection->data); + + uri = caja_file_get_activation_uri (file); + icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, 0); + icon_name = caja_icon_info_get_used_name (icon); + name = caja_file_get_display_name (file); + + if (uri != NULL) { + title = g_strdup_printf (_("Connect to Server %s"), name); + dialog = gtk_dialog_new_with_buttons (title, + fm_directory_view_get_containing_window (view), + GTK_DIALOG_NO_SEPARATOR, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + _("_Connect"), GTK_RESPONSE_OK, + NULL); + + g_object_set_data_full (G_OBJECT (dialog), "link-uri", g_strdup (uri), g_free); + g_object_set_data_full (G_OBJECT (dialog), "link-icon", g_strdup (icon_name), g_free); + + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); + + box = gtk_hbox_new (FALSE, 12); + gtk_widget_show (box); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + box, TRUE, TRUE, 0); + + label = gtk_label_new_with_mnemonic (_("Link _name:")); + gtk_widget_show (label); + + gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 12); + + entry = gtk_entry_new (); + if (name) { + gtk_entry_set_text (GTK_ENTRY (entry), name); + } + g_signal_connect (entry, + "activate", + G_CALLBACK (entry_activate_callback), + dialog); + + gtk_widget_show (entry); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + + gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 12); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_OK); + g_signal_connect (dialog, "response", + G_CALLBACK (connect_to_server_response_callback), + entry); + gtk_widget_show (dialog); + } + + g_free (uri); + g_object_unref (icon); + g_free (name); +} + +static void +action_location_open_alternate_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + fm_directory_view_activate_file (view, + file, + CAJA_WINDOW_OPEN_IN_NAVIGATION, + 0); +} + +static void +action_location_open_in_new_tab_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + fm_directory_view_activate_file (view, + file, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + CAJA_WINDOW_OPEN_FLAG_NEW_TAB); +} + +static void +action_location_open_folder_window_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + fm_directory_view_activate_file (view, + file, + CAJA_WINDOW_OPEN_IN_SPATIAL, + 0); +} + +static void +action_location_cut_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GList *files; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + files = g_list_append (NULL, file); + copy_or_cut_files (view, files, TRUE); + g_list_free (files); +} + +static void +action_location_copy_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GList *files; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + files = g_list_append (NULL, file); + copy_or_cut_files (view, files, FALSE); + g_list_free (files); +} + +static void +action_location_paste_files_into_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + paste_into (view, file); +} + +static void +action_location_trash_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GList *files; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + files = g_list_append (NULL, file); + trash_or_delete_files (fm_directory_view_get_containing_window (view), + files, TRUE, + view); + g_list_free (files); +} + +static void +action_location_delete_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GFile *location; + GList *files; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + location = caja_file_get_location (file); + + files = g_list_append (NULL, location); + caja_file_operations_delete (files, fm_directory_view_get_containing_window (view), + NULL, NULL); + + eel_g_object_list_free (files); +} + +static void +action_location_restore_from_trash_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GList l; + + view = FM_DIRECTORY_VIEW (callback_data); + file = view->details->location_popup_directory_as_file; + + l.prev = NULL; + l.next = NULL; + l.data = file; + caja_restore_files_from_trash (&l, + fm_directory_view_get_containing_window (view)); +} + +static void +fm_directory_view_init_show_hidden_files (FMDirectoryView *view) +{ + CajaWindowShowHiddenFilesMode mode; + gboolean show_hidden_changed; + gboolean show_hidden_default_setting; + + if (view->details->ignore_hidden_file_preferences) { + return; + } + + show_hidden_changed = FALSE; + mode = caja_window_info_get_hidden_files_mode (view->details->window); + + if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) { + show_hidden_default_setting = eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES); + if (show_hidden_default_setting != view->details->show_hidden_files) { + view->details->show_hidden_files = show_hidden_default_setting; + view->details->show_backup_files = show_hidden_default_setting; + show_hidden_changed = TRUE; + } + } else { + if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE) { + show_hidden_changed = !view->details->show_hidden_files; + view->details->show_hidden_files = TRUE; + view->details->show_backup_files = TRUE; + } else { + show_hidden_changed = view->details->show_hidden_files; + view->details->show_hidden_files = FALSE; + view->details->show_backup_files = FALSE; + } + } + + if (show_hidden_changed && (view->details->model != NULL)) { + load_directory (view, view->details->model); + } + +} + +static const GtkActionEntry directory_view_entries[] = { + /* name, stock id, label */ { "New Documents", "document-new", N_("Create _Document") }, + /* name, stock id, label */ { "Open With", NULL, N_("Open Wit_h"), + NULL, N_("Choose a program with which to open the selected item") }, + /* name, stock id */ { "Properties", GTK_STOCK_PROPERTIES, + /* label, accelerator */ N_("_Properties"), "<alt>Return", + /* tooltip */ N_("View or modify the properties of each selected item"), + G_CALLBACK (action_properties_callback) }, + /* name, stock id */ { "PropertiesAccel", NULL, + /* label, accelerator */ "PropertiesAccel", "<control>I", + /* tooltip */ NULL, + G_CALLBACK (action_properties_callback) }, + /* name, stock id */ { "New Folder", "folder-new", + /* label, accelerator */ N_("Create _Folder"), "<control><shift>N", + /* tooltip */ N_("Create a new empty folder inside this folder"), + G_CALLBACK (action_new_folder_callback) }, + /* name, stock id, label */ { "No Templates", NULL, N_("No templates installed") }, + /* name, stock id */ { "New Empty File", NULL, + /* translators: this is used to indicate that a file doesn't contain anything */ + /* label, accelerator */ N_("_Empty File"), NULL, + /* tooltip */ N_("Create a new empty file inside this folder"), + G_CALLBACK (action_new_empty_file_callback) }, + /* name, stock id */ { "New Launcher", NULL, + /* label, accelerator */ N_("Create L_auncher..."), NULL, + /* tooltip */ N_("Create a new launcher"), + G_CALLBACK (action_new_launcher_callback) }, + /* name, stock id */ { "Open", NULL, + /* label, accelerator */ N_("_Open"), "<control>o", + /* tooltip */ N_("Open the selected item in this window"), + G_CALLBACK (action_open_callback) }, + /* name, stock id */ { "OpenAccel", NULL, + /* label, accelerator */ "OpenAccel", "<alt>Down", + /* tooltip */ NULL, + G_CALLBACK (action_open_callback) }, + /* name, stock id */ { "OpenAlternate", NULL, + /* label, accelerator */ N_("Open in Navigation Window"), "<control><shift>o", + /* tooltip */ N_("Open each selected item in a navigation window"), + G_CALLBACK (action_open_alternate_callback) }, + /* name, stock id */ { "OpenInNewTab", NULL, + /* label, accelerator */ N_("Open in New _Tab"), "<control><shift>o", + /* tooltip */ N_("Open each selected item in a new tab"), + G_CALLBACK (action_open_new_tab_callback) }, + /* name, stock id */ { "OpenFolderWindow", NULL, + /* label, accelerator */ N_("Open in _Folder Window"), NULL, + /* tooltip */ N_("Open each selected item in a folder window"), + G_CALLBACK (action_open_folder_window_callback) }, + /* name, stock id */ { "OtherApplication1", NULL, + /* label, accelerator */ N_("Other _Application..."), NULL, + /* tooltip */ N_("Choose another application with which to open the selected item"), + G_CALLBACK (action_other_application_callback) }, + /* name, stock id */ { "OtherApplication2", NULL, + /* label, accelerator */ N_("Open With Other _Application..."), NULL, + /* tooltip */ N_("Choose another application with which to open the selected item"), + G_CALLBACK (action_other_application_callback) }, + /* name, stock id */ { "Open Scripts Folder", NULL, + /* label, accelerator */ N_("_Open Scripts Folder"), NULL, + /* tooltip */ N_("Show the folder containing the scripts that appear in this menu"), + G_CALLBACK (action_open_scripts_folder_callback) }, + /* name, stock id */ { "Empty Trash", NULL, + /* label, accelerator */ N_("E_mpty Trash"), NULL, + /* tooltip */ N_("Delete all items in the Trash"), + G_CALLBACK (action_empty_trash_callback) }, + /* name, stock id */ { "Cut", GTK_STOCK_CUT, + /* label, accelerator */ NULL, NULL, + /* tooltip */ N_("Prepare the selected files to be moved with a Paste command"), + G_CALLBACK (action_cut_files_callback) }, + /* name, stock id */ { "Copy", GTK_STOCK_COPY, + /* label, accelerator */ NULL, NULL, + /* tooltip */ N_("Prepare the selected files to be copied with a Paste command"), + G_CALLBACK (action_copy_files_callback) }, + /* name, stock id */ { "Paste", GTK_STOCK_PASTE, + /* label, accelerator */ NULL, NULL, + /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command"), + G_CALLBACK (action_paste_files_callback) }, + /* We make accelerator "" instead of null here to not inherit the stock + accelerator for paste */ + /* name, stock id */ { "Paste Files Into", GTK_STOCK_PASTE, + /* label, accelerator */ N_("_Paste Into Folder"), "", + /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command into the selected folder"), + G_CALLBACK (action_paste_files_into_callback) }, + /* name, stock id, label */ { "CopyToMenu", NULL, N_("Cop_y to") }, + /* name, stock id, label */ { "MoveToMenu", NULL, N_("M_ove to") }, + /* name, stock id */ { "Select All", NULL, + /* label, accelerator */ N_("Select _All"), "<control>A", + /* tooltip */ N_("Select all items in this window"), + G_CALLBACK (action_select_all_callback) }, + /* name, stock id */ { "Select Pattern", NULL, + /* label, accelerator */ N_("Select I_tems Matching..."), "<control>S", + /* tooltip */ N_("Select items in this window matching a given pattern"), + G_CALLBACK (action_select_pattern_callback) }, + /* name, stock id */ { "Invert Selection", NULL, + /* label, accelerator */ N_("_Invert Selection"), "<control><shift>I", + /* tooltip */ N_("Select all and only the items that are not currently selected"), + G_CALLBACK (action_invert_selection_callback) }, + /* name, stock id */ { "Duplicate", NULL, + /* label, accelerator */ N_("D_uplicate"), NULL, + /* tooltip */ N_("Duplicate each selected item"), + G_CALLBACK (action_duplicate_callback) }, + /* name, stock id */ { "Create Link", NULL, + /* label, accelerator */ N_("Ma_ke Link"), "<control>M", + /* tooltip */ N_("Create a symbolic link for each selected item"), + G_CALLBACK (action_create_link_callback) }, + /* name, stock id */ { "Rename", NULL, + /* label, accelerator */ N_("_Rename..."), "F2", + /* tooltip */ N_("Rename selected item"), + G_CALLBACK (action_rename_callback) }, + /* name, stock id */ { "RenameSelectAll", NULL, + /* label, accelerator */ "RenameSelectAll", "<shift>F2", + /* tooltip */ NULL, + G_CALLBACK (action_rename_select_all_callback) }, + /* name, stock id */ { "Trash", NULL, + /* label, accelerator */ N_("Mo_ve to Trash"), NULL, + /* tooltip */ N_("Move each selected item to the Trash"), + G_CALLBACK (action_trash_callback) }, + /* name, stock id */ { "Delete", NULL, + /* label, accelerator */ N_("_Delete"), "<shift>Delete", + /* tooltip */ N_("Delete each selected item, without moving to the Trash"), + G_CALLBACK (action_delete_callback) }, + /* name, stock id */ { "Restore From Trash", NULL, + /* label, accelerator */ N_("_Restore"), NULL, + NULL, + G_CALLBACK (action_restore_from_trash_callback) }, + /* + * multiview-TODO: decide whether "Reset to Defaults" should + * be window-wide, and not just view-wide. + * Since this also resets the "Show hidden files" mode, + * it is a mixture of both ATM. + */ + /* name, stock id */ { "Reset to Defaults", NULL, + /* label, accelerator */ N_("Reset View to _Defaults"), NULL, + /* tooltip */ N_("Reset sorting order and zoom level to match preferences for this view"), + G_CALLBACK (action_reset_to_defaults_callback) }, + /* name, stock id */ { "Connect To Server Link", NULL, + /* label, accelerator */ N_("Connect To This Server"), NULL, + /* tooltip */ N_("Make a permanent connection to this server"), + G_CALLBACK (action_connect_to_server_link_callback) }, + /* name, stock id */ { "Mount Volume", NULL, + /* label, accelerator */ N_("_Mount"), NULL, + /* tooltip */ N_("Mount the selected volume"), + G_CALLBACK (action_mount_volume_callback) }, + /* name, stock id */ { "Unmount Volume", NULL, + /* label, accelerator */ N_("_Unmount"), NULL, + /* tooltip */ N_("Unmount the selected volume"), + G_CALLBACK (action_unmount_volume_callback) }, + /* name, stock id */ { "Eject Volume", NULL, + /* label, accelerator */ N_("_Eject"), NULL, + /* tooltip */ N_("Eject the selected volume"), + G_CALLBACK (action_eject_volume_callback) }, + /* name, stock id */ { "Format Volume", NULL, + /* label, accelerator */ N_("_Format"), NULL, + /* tooltip */ N_("Format the selected volume"), + G_CALLBACK (action_format_volume_callback) }, + /* name, stock id */ { "Start Volume", NULL, + /* label, accelerator */ N_("_Start"), NULL, + /* tooltip */ N_("Start the selected volume"), + G_CALLBACK (action_start_volume_callback) }, + /* name, stock id */ { "Stop Volume", NULL, + /* label, accelerator */ N_("_Stop"), NULL, + /* tooltip */ N_("Stop the selected volume"), + G_CALLBACK (action_stop_volume_callback) }, + /* name, stock id */ { "Poll", NULL, + /* label, accelerator */ N_("_Detect Media"), NULL, + /* tooltip */ N_("Detect media in the selected drive"), + G_CALLBACK (action_detect_media_callback) }, + /* name, stock id */ { "Self Mount Volume", NULL, + /* label, accelerator */ N_("_Mount"), NULL, + /* tooltip */ N_("Mount the volume associated with the open folder"), + G_CALLBACK (action_self_mount_volume_callback) }, + /* name, stock id */ { "Self Unmount Volume", NULL, + /* label, accelerator */ N_("_Unmount"), NULL, + /* tooltip */ N_("Unmount the volume associated with the open folder"), + G_CALLBACK (action_self_unmount_volume_callback) }, + /* name, stock id */ { "Self Eject Volume", NULL, + /* label, accelerator */ N_("_Eject"), NULL, + /* tooltip */ N_("Eject the volume associated with the open folder"), + G_CALLBACK (action_self_eject_volume_callback) }, + /* name, stock id */ { "Self Format Volume", NULL, + /* label, accelerator */ N_("_Format"), NULL, + /* tooltip */ N_("Format the volume associated with the open folder"), + G_CALLBACK (action_self_format_volume_callback) }, + /* name, stock id */ { "Self Start Volume", NULL, + /* label, accelerator */ N_("_Start"), NULL, + /* tooltip */ N_("Start the volume associated with the open folder"), + G_CALLBACK (action_self_start_volume_callback) }, + /* name, stock id */ { "Self Stop Volume", NULL, + /* label, accelerator */ N_("_Stop"), NULL, + /* tooltip */ N_("Stop the volume associated with the open folder"), + G_CALLBACK (action_self_stop_volume_callback) }, + /* name, stock id */ { "Self Poll", NULL, + /* label, accelerator */ N_("_Detect Media"), NULL, + /* tooltip */ N_("Detect media in the selected drive"), + G_CALLBACK (action_self_detect_media_callback) }, + /* name, stock id */ { "OpenCloseParent", NULL, + /* label, accelerator */ N_("Open File and Close window"), "<alt><shift>Down", + /* tooltip */ NULL, + G_CALLBACK (action_open_close_parent_callback) }, + /* name, stock id */ { "Save Search", NULL, + /* label, accelerator */ N_("Sa_ve Search"), NULL, + /* tooltip */ N_("Save the edited search"), + G_CALLBACK (action_save_search_callback) }, + /* name, stock id */ { "Save Search As", NULL, + /* label, accelerator */ N_("Sa_ve Search As..."), NULL, + /* tooltip */ N_("Save the current search as a file"), + G_CALLBACK (action_save_search_as_callback) }, + + /* Location-specific actions */ + /* name, stock id */ { FM_ACTION_LOCATION_OPEN_ALTERNATE, NULL, + /* label, accelerator */ N_("Open in Navigation Window"), "", + /* tooltip */ N_("Open this folder in a navigation window"), + G_CALLBACK (action_location_open_alternate_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_OPEN_IN_NEW_TAB, NULL, + /* label, accelerator */ N_("Open in New _Tab"), "", + /* tooltip */ N_("Open this folder in a new tab"), + G_CALLBACK (action_location_open_in_new_tab_callback) }, + + /* name, stock id */ { FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW, NULL, + /* label, accelerator */ N_("Open in _Folder Window"), "", + /* tooltip */ N_("Open this folder in a folder window"), + G_CALLBACK (action_location_open_folder_window_callback) }, + + /* name, stock id */ { FM_ACTION_LOCATION_CUT, GTK_STOCK_CUT, + /* label, accelerator */ NULL, "", + /* tooltip */ N_("Prepare this folder to be moved with a Paste command"), + G_CALLBACK (action_location_cut_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_COPY, GTK_STOCK_COPY, + /* label, accelerator */ NULL, "", + /* tooltip */ N_("Prepare this folder to be copied with a Paste command"), + G_CALLBACK (action_location_copy_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_PASTE_FILES_INTO, GTK_STOCK_PASTE, + /* label, accelerator */ N_("_Paste Into Folder"), "", + /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command into this folder"), + G_CALLBACK (action_location_paste_files_into_callback) }, + + /* name, stock id */ { FM_ACTION_LOCATION_TRASH, NULL, + /* label, accelerator */ N_("Mo_ve to Trash"), "", + /* tooltip */ N_("Move this folder to the Trash"), + G_CALLBACK (action_location_trash_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_DELETE, CAJA_ICON_DELETE, + /* label, accelerator */ N_("_Delete"), "", + /* tooltip */ N_("Delete this folder, without moving to the Trash"), + G_CALLBACK (action_location_delete_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_RESTORE_FROM_TRASH, NULL, + /* label, accelerator */ N_("_Restore"), NULL, NULL, + G_CALLBACK (action_location_restore_from_trash_callback) }, + + /* name, stock id */ { "Location Mount Volume", NULL, + /* label, accelerator */ N_("_Mount"), NULL, + /* tooltip */ N_("Mount the volume associated with this folder"), + G_CALLBACK (action_location_mount_volume_callback) }, + /* name, stock id */ { "Location Unmount Volume", NULL, + /* label, accelerator */ N_("_Unmount"), NULL, + /* tooltip */ N_("Unmount the volume associated with this folder"), + G_CALLBACK (action_location_unmount_volume_callback) }, + /* name, stock id */ { "Location Eject Volume", NULL, + /* label, accelerator */ N_("_Eject"), NULL, + /* tooltip */ N_("Eject the volume associated with this folder"), + G_CALLBACK (action_location_eject_volume_callback) }, + /* name, stock id */ { "Location Format Volume", NULL, + /* label, accelerator */ N_("_Format"), NULL, + /* tooltip */ N_("Format the volume associated with this folder"), + G_CALLBACK (action_location_format_volume_callback) }, + /* name, stock id */ { "Location Start Volume", NULL, + /* label, accelerator */ N_("_Start"), NULL, + /* tooltip */ N_("Start the volume associated with this folder"), + G_CALLBACK (action_location_start_volume_callback) }, + /* name, stock id */ { "Location Stop Volume", NULL, + /* label, accelerator */ N_("_Stop"), NULL, + /* tooltip */ N_("Stop the volume associated with this folder"), + G_CALLBACK (action_location_stop_volume_callback) }, + /* name, stock id */ { "Location Poll", NULL, + /* label, accelerator */ N_("_Detect Media"), NULL, + /* tooltip */ N_("Detect media in the selected drive"), + G_CALLBACK (action_location_detect_media_callback) }, + + /* name, stock id */ { "LocationProperties", GTK_STOCK_PROPERTIES, + /* label, accelerator */ N_("_Properties"), NULL, + /* tooltip */ N_("View or modify the properties of this folder"), + G_CALLBACK (action_location_properties_callback) }, + + /* name, stock id, label */ {FM_ACTION_COPY_TO_NEXT_PANE, NULL, N_("_Other pane"), + NULL, N_("Copy the current selection to the other pane in the window"), + G_CALLBACK (action_copy_to_next_pane_callback) }, + /* name, stock id, label */ {FM_ACTION_MOVE_TO_NEXT_PANE, NULL, N_("_Other pane"), + NULL, N_("Move the current selection to the other pane in the window"), + G_CALLBACK (action_move_to_next_pane_callback) }, + /* name, stock id, label */ {FM_ACTION_COPY_TO_HOME, CAJA_ICON_HOME, + N_("_Home Folder"), NULL, + N_("Copy the current selection to the home folder"), + G_CALLBACK (action_copy_to_home_callback) }, + /* name, stock id, label */ {FM_ACTION_MOVE_TO_HOME, CAJA_ICON_HOME, + N_("_Home Folder"), NULL, + N_("Move the current selection to the home folder"), + G_CALLBACK (action_move_to_home_callback) }, + /* name, stock id, label */ {FM_ACTION_COPY_TO_DESKTOP, CAJA_ICON_DESKTOP, + N_("_Desktop"), NULL, + N_("Copy the current selection to the desktop"), + G_CALLBACK (action_copy_to_desktop_callback) }, + /* name, stock id, label */ {FM_ACTION_MOVE_TO_DESKTOP, CAJA_ICON_DESKTOP, + N_("_Desktop"), NULL, + N_("Move the current selection to the desktop"), + G_CALLBACK (action_move_to_desktop_callback) }, +}; + +static void +connect_proxy (FMDirectoryView *view, + GtkAction *action, + GtkWidget *proxy, + GtkActionGroup *action_group) +{ + GdkPixbuf *pixbuf; + GtkWidget *image; + + if (strcmp (gtk_action_get_name (action), FM_ACTION_NEW_EMPTY_FILE) == 0 && + GTK_IS_IMAGE_MENU_ITEM (proxy)) { + pixbuf = get_menu_icon ("text-x-generic"); + if (pixbuf != NULL) { + image = gtk_image_new_from_pixbuf (pixbuf); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), image); + + g_object_unref (pixbuf); + } + } +} + +static void +pre_activate (FMDirectoryView *view, + GtkAction *action, + GtkActionGroup *action_group) +{ + GdkEvent *event; + GtkWidget *proxy; + gboolean activated_from_popup; + + /* check whether action was activated through a popup menu. + * If not, unset the last stored context menu popup position */ + activated_from_popup = FALSE; + + event = gtk_get_current_event (); + proxy = gtk_get_event_widget (event); + + if (proxy != NULL) { + GtkWidget *toplevel; + GdkWindowTypeHint hint; + + toplevel = gtk_widget_get_toplevel (proxy); + + if (GTK_IS_WINDOW (toplevel)) { + hint = gtk_window_get_type_hint (GTK_WINDOW (toplevel)); + + if (hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU) { + activated_from_popup = TRUE; + } + } + } + + if (!activated_from_popup) { + update_context_menu_position_from_event (view, NULL); + } +} + +static void +real_merge_menus (FMDirectoryView *view) +{ + GtkActionGroup *action_group; + GtkUIManager *ui_manager; + GtkAction *action; + const char *ui; + char *tooltip; + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + + action_group = gtk_action_group_new ("DirViewActions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + view->details->dir_action_group = action_group; + gtk_action_group_add_actions (action_group, + directory_view_entries, G_N_ELEMENTS (directory_view_entries), + view); + + /* Translators: %s is a directory */ + tooltip = g_strdup_printf(_("Run or manage scripts from %s"), "~/.config/caja/scripts"); + /* Create a script action here specially because its tooltip is dynamic */ + action = gtk_action_new ("Scripts", _("_Scripts"), tooltip, NULL); + gtk_action_group_add_action (action_group, action); + g_object_unref (action); + g_free (tooltip); + + action = gtk_action_group_get_action (action_group, FM_ACTION_NO_TEMPLATES); + gtk_action_set_sensitive (action, FALSE); + + g_signal_connect_object (action_group, "connect-proxy", + G_CALLBACK (connect_proxy), G_OBJECT (view), + G_CONNECT_SWAPPED); + g_signal_connect_object (action_group, "pre-activate", + G_CALLBACK (pre_activate), G_OBJECT (view), + G_CONNECT_SWAPPED); + + /* Insert action group at end so clipboard action group ends up before it */ + gtk_ui_manager_insert_action_group (ui_manager, action_group, -1); + g_object_unref (action_group); /* owned by ui manager */ + + ui = caja_ui_string_get ("caja-directory-view-ui.xml"); + view->details->dir_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + g_signal_connect_object (fm_directory_view_get_background (view), "settings_changed", + G_CALLBACK (schedule_update_menus), G_OBJECT (view), + G_CONNECT_SWAPPED); + + view->details->scripts_invalid = TRUE; + view->details->templates_invalid = TRUE; +} + + +static gboolean +can_paste_into_file (CajaFile *file) +{ + if (caja_file_is_directory (file) && + caja_file_can_write (file)) { + return TRUE; + } + if (caja_file_has_activation_uri (file)) { + GFile *location; + CajaFile *activation_file; + gboolean res; + + location = caja_file_get_activation_location (file); + activation_file = caja_file_get (location); + g_object_unref (location); + + /* The target location might not have data for it read yet, + and we can't want to do sync I/O, so treat the unknown + case as can-write */ + res = (caja_file_get_file_type (activation_file) == G_FILE_TYPE_UNKNOWN) || + (caja_file_get_file_type (activation_file) == G_FILE_TYPE_DIRECTORY && + caja_file_can_write (activation_file)); + + caja_file_unref (activation_file); + + return res; + } + + return FALSE; +} + +static void +clipboard_targets_received (GtkClipboard *clipboard, + GdkAtom *targets, + int n_targets, + gpointer user_data) +{ + FMDirectoryView *view; + gboolean can_paste; + int i; + GList *selection; + int count; + GtkAction *action; + + view = FM_DIRECTORY_VIEW (user_data); + can_paste = FALSE; + + if (view->details->window == NULL || + !view->details->active) { + /* We've been destroyed or became inactive since call */ + g_object_unref (view); + return; + } + + if (targets) { + for (i=0; i < n_targets; i++) { + if (targets[i] == copied_files_atom) { + can_paste = TRUE; + } + } + } + + + selection = fm_directory_view_get_selection (view); + count = g_list_length (selection); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PASTE); + gtk_action_set_sensitive (action, + can_paste && !fm_directory_view_is_read_only (view)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PASTE_FILES_INTO); + gtk_action_set_sensitive (action, + can_paste && count == 1 && + can_paste_into_file (CAJA_FILE (selection->data))); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_PASTE_FILES_INTO); + g_object_set_data (G_OBJECT (action), + "can-paste-according-to-clipboard", + GINT_TO_POINTER (can_paste)); + gtk_action_set_sensitive (action, + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "can-paste-according-to-clipboard")) && + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "can-paste-according-to-destination"))); + + caja_file_list_free (selection); + + g_object_unref (view); +} + +static gboolean +showing_trash_directory (FMDirectoryView *view) +{ + CajaFile *file; + + file = fm_directory_view_get_directory_as_file (view); + if (file != NULL) { + return caja_file_is_in_trash (file); + } + return FALSE; +} + +static gboolean +should_show_empty_trash (FMDirectoryView *view) +{ + return (showing_trash_directory (view) || caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION); +} + +static gboolean +file_list_all_are_folders (GList *file_list) +{ + GList *l; + CajaFile *file, *linked_file; + char *activation_uri; + gboolean is_dir; + + for (l = file_list; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + if (caja_file_is_caja_link (file) && + !CAJA_IS_DESKTOP_ICON_FILE (file)) { + if (caja_file_is_launcher (file)) { + return FALSE; + } + + activation_uri = caja_file_get_activation_uri (file); + + if (activation_uri == NULL) { + g_free (activation_uri); + return FALSE; + } + + linked_file = caja_file_get_existing_by_uri (activation_uri); + + /* We might not actually know the type of the linked file yet, + * however we don't want to schedule a read, since that might do things + * like ask for password etc. This is a bit unfortunate, but I don't + * know any way around it, so we do various heuristics here + * to get things mostly right + */ + is_dir = + (linked_file != NULL && + caja_file_is_directory (linked_file)) || + (activation_uri != NULL && + activation_uri[strlen (activation_uri) - 1] == '/'); + + caja_file_unref (linked_file); + g_free (activation_uri); + + if (!is_dir) { + return FALSE; + } + } else if (!(caja_file_is_directory (file) || + CAJA_IS_DESKTOP_ICON_FILE (file))) { + return FALSE; + } + } + return TRUE; +} + +static void +file_should_show_foreach (CajaFile *file, + gboolean *show_mount, + gboolean *show_unmount, + gboolean *show_eject, + gboolean *show_connect, + gboolean *show_format, + gboolean *show_start, + gboolean *show_stop, + gboolean *show_poll, + GDriveStartStopType *start_stop_type) +{ + char *uri; + + *show_mount = FALSE; + *show_unmount = FALSE; + *show_eject = FALSE; + *show_connect = FALSE; + *show_format = FALSE; + *show_start = FALSE; + *show_stop = FALSE; + *show_poll = FALSE; + + if (caja_file_can_eject (file)) { + *show_eject = TRUE; + } + + if (caja_file_can_mount (file)) { + *show_mount = TRUE; + +#ifdef TODO_GIO + if (something && + g_find_program_in_path ("gfloppy")) { + *show_format = TRUE; + } +#endif + } + + if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { + *show_start = TRUE; + } + + if (caja_file_can_stop (file)) { + *show_stop = TRUE; + } + + /* Dot not show both Unmount and Eject/Safe Removal; too confusing to + * have too many menu entries */ + if (caja_file_can_unmount (file) && !*show_eject && !*show_stop) { + *show_unmount = TRUE; + } + + if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { + *show_poll = TRUE; + } + + *start_stop_type = caja_file_get_start_stop_type (file); + + if (caja_file_is_caja_link (file)) { + uri = caja_file_get_activation_uri (file); + if (uri != NULL && + (eel_istr_has_prefix (uri, "ftp:") || + eel_istr_has_prefix (uri, "ssh:") || + eel_istr_has_prefix (uri, "sftp:") || + eel_istr_has_prefix (uri, "dav:") || + eel_istr_has_prefix (uri, "davs:"))) { + *show_connect = TRUE; + } + g_free (uri); + } +} + +static void +file_should_show_self (CajaFile *file, + gboolean *show_mount, + gboolean *show_unmount, + gboolean *show_eject, + gboolean *show_format, + gboolean *show_start, + gboolean *show_stop, + gboolean *show_poll, + GDriveStartStopType *start_stop_type) +{ + *show_mount = FALSE; + *show_unmount = FALSE; + *show_eject = FALSE; + *show_format = FALSE; + *show_start = FALSE; + *show_stop = FALSE; + *show_poll = FALSE; + + if (file == NULL) { + return; + } + + if (caja_file_can_eject (file)) { + *show_eject = TRUE; + } + + if (caja_file_can_mount (file)) { + *show_mount = TRUE; + } + +#ifdef TODO_GIO + if (something && g_find_program_in_path ("gfloppy")) { + *show_format = TRUE; + } +#endif + + if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { + *show_start = TRUE; + } + + if (caja_file_can_stop (file)) { + *show_stop = TRUE; + } + + /* Dot not show both Unmount and Eject/Safe Removal; too confusing to + * have too many menu entries */ + if (caja_file_can_unmount (file) && !*show_eject && !*show_stop) { + *show_unmount = TRUE; + } + + if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { + *show_poll = TRUE; + } + + *start_stop_type = caja_file_get_start_stop_type (file); + +} + +static gboolean +files_are_all_directories (GList *files) +{ + CajaFile *file; + GList *l; + gboolean all_directories; + + all_directories = TRUE; + + for (l = files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + all_directories &= caja_file_is_directory (file); + } + + return all_directories; +} + +static gboolean +files_is_none_directory (GList *files) +{ + CajaFile *file; + GList *l; + gboolean no_directory; + + no_directory = TRUE; + + for (l = files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + no_directory &= !caja_file_is_directory (file); + } + + return no_directory; +} + +static void +update_restore_from_trash_action (GtkAction *action, + GList *files, + gboolean is_self) +{ + CajaFile *original_file; + CajaFile *original_dir; + GHashTable *original_dirs_hash; + GList *original_dirs; + GFile *original_location; + char *tooltip, *original_name; + + original_file = NULL; + original_dir = NULL; + original_dirs = NULL; + original_dirs_hash = NULL; + original_location = NULL; + original_name = NULL; + + if (files != NULL) { + if (g_list_length (files) == 1) { + original_file = caja_file_get_trash_original_file (files->data); + } else { + original_dirs_hash = caja_trashed_files_get_original_directories (files, NULL); + if (original_dirs_hash != NULL) { + original_dirs = g_hash_table_get_keys (original_dirs_hash); + if (g_list_length (original_dirs) == 1) { + original_dir = caja_file_ref (CAJA_FILE (original_dirs->data)); + } + } + } + } + + if (original_file != NULL || original_dirs != NULL) { + gtk_action_set_visible (action, TRUE); + + if (original_file != NULL) { + original_location = caja_file_get_location (original_file); + } else if (original_dir != NULL) { + original_location = caja_file_get_location (original_dir); + } + + if (original_location != NULL) { + original_name = g_file_get_parse_name (original_location); + } + + if (is_self) { + g_assert (g_list_length (files) == 1); + g_assert (original_location != NULL); + tooltip = g_strdup_printf (_("Move the open folder out of the trash to \"%s\""), original_name); + } else if (files_are_all_directories (files)) { + if (original_name != NULL) { + tooltip = g_strdup_printf (ngettext ("Move the selected folder out of the trash to \"%s\"", + "Move the selected folders out of the trash to \"%s\"", + g_list_length (files)), original_name); + } else { + tooltip = g_strdup_printf (ngettext ("Move the selected folder out of the trash", + "Move the selected folders out of the trash", + g_list_length (files))); + } + } else if (files_is_none_directory (files)) { + if (original_name != NULL) { + tooltip = g_strdup_printf (ngettext ("Move the selected file out of the trash to \"%s\"", + "Move the selected files out of the trash to \"%s\"", + g_list_length (files)), original_name); + } else { + tooltip = g_strdup_printf (ngettext ("Move the selected file out of the trash", + "Move the selected files out of the trash", + g_list_length (files))); + } + } else { + if (original_name != NULL) { + tooltip = g_strdup_printf (ngettext ("Move the selected item out of the trash to \"%s\"", + "Move the selected items out of the trash to \"%s\"", + g_list_length (files)), original_name); + } else { + tooltip = g_strdup_printf (ngettext ("Move the selected item out of the trash", + "Move the selected items out of the trash", + g_list_length (files))); + } + } + g_free (original_name); + + g_object_set (action, "tooltip", tooltip, NULL); + + if (original_location != NULL) { + g_object_unref (original_location); + } + } else { + gtk_action_set_visible (action, FALSE); + } + + caja_file_unref (original_file); + caja_file_unref (original_dir); + g_list_free (original_dirs); + + if (original_dirs_hash != NULL) { + g_hash_table_destroy (original_dirs_hash); + } +} + +static void +real_update_menus_volumes (FMDirectoryView *view, + GList *selection, + gint selection_count) +{ + GList *l; + CajaFile *file; + gboolean show_mount; + gboolean show_unmount; + gboolean show_eject; + gboolean show_connect; + gboolean show_format; + gboolean show_start; + gboolean show_stop; + gboolean show_poll; + GDriveStartStopType start_stop_type; + gboolean show_self_mount; + gboolean show_self_unmount; + gboolean show_self_eject; + gboolean show_self_format; + gboolean show_self_start; + gboolean show_self_stop; + gboolean show_self_poll; + GDriveStartStopType self_start_stop_type; + GtkAction *action; + + show_mount = (selection != NULL); + show_unmount = (selection != NULL); + show_eject = (selection != NULL); + show_connect = (selection != NULL && selection_count == 1); + show_format = (selection != NULL && selection_count == 1); + show_start = (selection != NULL && selection_count == 1); + show_stop = (selection != NULL && selection_count == 1); + show_poll = (selection != NULL && selection_count == 1); + start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; + self_start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; + + for (l = selection; l != NULL && (show_mount || show_unmount + || show_eject || show_connect + || show_format || show_start + || show_stop || show_poll); + l = l->next) { + gboolean show_mount_one; + gboolean show_unmount_one; + gboolean show_eject_one; + gboolean show_connect_one; + gboolean show_format_one; + gboolean show_start_one; + gboolean show_stop_one; + gboolean show_poll_one; + + file = CAJA_FILE (l->data); + file_should_show_foreach (file, + &show_mount_one, + &show_unmount_one, + &show_eject_one, + &show_connect_one, + &show_format_one, + &show_start_one, + &show_stop_one, + &show_poll_one, + &start_stop_type); + + show_mount &= show_mount_one; + show_unmount &= show_unmount_one; + show_eject &= show_eject_one; + show_connect &= show_connect_one; + show_format &= show_format_one; + show_start &= show_start_one; + show_stop &= show_stop_one; + show_poll &= show_poll_one; + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_CONNECT_TO_SERVER_LINK); + gtk_action_set_visible (action, show_connect); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOUNT_VOLUME); + gtk_action_set_visible (action, show_mount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_UNMOUNT_VOLUME); + gtk_action_set_visible (action, show_unmount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_EJECT_VOLUME); + gtk_action_set_visible (action, show_eject); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_FORMAT_VOLUME); + gtk_action_set_visible (action, show_format); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_START_VOLUME); + gtk_action_set_visible (action, show_start); + if (show_start) { + switch (start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Connect")); + gtk_action_set_tooltip (action, _("Connect to the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Start Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Start the selected multi-disk drive")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("U_nlock Drive")); + gtk_action_set_tooltip (action, _("Unlock the selected drive")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_STOP_VOLUME); + gtk_action_set_visible (action, show_stop); + if (show_stop) { + switch (start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Stop")); + gtk_action_set_tooltip (action, _("Stop the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Safely Remove Drive")); + gtk_action_set_tooltip (action, _("Safely remove the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Disconnect")); + gtk_action_set_tooltip (action, _("Disconnect the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Stop Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Stop the selected multi-disk drive")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Lock Drive")); + gtk_action_set_tooltip (action, _("Lock the selected drive")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_POLL); + gtk_action_set_visible (action, show_poll); + + show_self_mount = show_self_unmount = show_self_eject = + show_self_format = show_self_start = show_self_stop = show_self_poll = FALSE; + + file = fm_directory_view_get_directory_as_file (view); + file_should_show_self (file, + &show_self_mount, + &show_self_unmount, + &show_self_eject, + &show_self_format, + &show_self_start, + &show_self_stop, + &show_self_poll, + &self_start_stop_type); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_MOUNT_VOLUME); + gtk_action_set_visible (action, show_self_mount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_UNMOUNT_VOLUME); + gtk_action_set_visible (action, show_self_unmount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_EJECT_VOLUME); + gtk_action_set_visible (action, show_self_eject); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_FORMAT_VOLUME); + gtk_action_set_visible (action, show_self_format); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_START_VOLUME); + gtk_action_set_visible (action, show_self_start); + if (show_self_start) { + switch (self_start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Connect")); + gtk_action_set_tooltip (action, _("Connect to the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Start Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Start the multi-disk drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Unlock Drive")); + gtk_action_set_tooltip (action, _("Unlock the drive associated with the open folder")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_STOP_VOLUME); + gtk_action_set_visible (action, show_self_stop); + if (show_self_stop) { + switch (self_start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Stop")); + gtk_action_set_tooltip (action, _("_Stop the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Safely Remove Drive")); + gtk_action_set_tooltip (action, _("Safely remove the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Disconnect")); + gtk_action_set_tooltip (action, _("Disconnect the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Stop Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Stop the multi-disk drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Lock Drive")); + gtk_action_set_tooltip (action, _("Lock the drive associated with the open folder")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_POLL); + gtk_action_set_visible (action, show_self_poll); + +} + +static void +real_update_location_menu_volumes (FMDirectoryView *view) +{ + GtkAction *action; + CajaFile *file; + gboolean show_mount; + gboolean show_unmount; + gboolean show_eject; + gboolean show_connect; + gboolean show_format; + gboolean show_start; + gboolean show_stop; + gboolean show_poll; + GDriveStartStopType start_stop_type; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file)); + + file = CAJA_FILE (view->details->location_popup_directory_as_file); + file_should_show_foreach (file, + &show_mount, + &show_unmount, + &show_eject, + &show_connect, + &show_format, + &show_start, + &show_stop, + &show_poll, + &start_stop_type); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_MOUNT_VOLUME); + gtk_action_set_visible (action, show_mount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_UNMOUNT_VOLUME); + gtk_action_set_visible (action, show_unmount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_EJECT_VOLUME); + gtk_action_set_visible (action, show_eject); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_FORMAT_VOLUME); + gtk_action_set_visible (action, show_format); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_START_VOLUME); + gtk_action_set_visible (action, show_start); + if (show_start) { + switch (start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Connect")); + gtk_action_set_tooltip (action, _("Connect to the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Start Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Start the selected multi-disk drive")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Unlock Drive")); + gtk_action_set_tooltip (action, _("Unlock the selected drive")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_STOP_VOLUME); + gtk_action_set_visible (action, show_stop); + if (show_stop) { + switch (start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Stop")); + gtk_action_set_tooltip (action, _("Stop the selected volume")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Safely Remove Drive")); + gtk_action_set_tooltip (action, _("Safely remove the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Disconnect")); + gtk_action_set_tooltip (action, _("Disconnect the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Stop Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Stop the selected multi-disk drive")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Lock Drive")); + gtk_action_set_tooltip (action, _("Lock the selected drive")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_POLL); + gtk_action_set_visible (action, show_poll); +} + +/* TODO: we should split out this routine into two functions: + * Update on clipboard changes + * Update on selection changes + */ +static void +real_update_paste_menu (FMDirectoryView *view, + GList *selection, + gint selection_count) +{ + gboolean can_paste_files_into; + gboolean selection_is_read_only; + gboolean is_read_only; + GtkAction *action; + + selection_is_read_only = selection_count == 1 && + (!caja_file_can_write (CAJA_FILE (selection->data)) && + !caja_file_has_activation_uri (CAJA_FILE (selection->data))); + + is_read_only = fm_directory_view_is_read_only (view); + + can_paste_files_into = (selection_count == 1 && + can_paste_into_file (CAJA_FILE (selection->data))); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PASTE); + gtk_action_set_sensitive (action, !is_read_only); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PASTE_FILES_INTO); + gtk_action_set_visible (action, can_paste_files_into); + gtk_action_set_sensitive (action, !selection_is_read_only); + + /* Ask the clipboard */ + g_object_ref (view); /* Need to keep the object alive until we get the reply */ + gtk_clipboard_request_targets (caja_clipboard_get (GTK_WIDGET (view)), + clipboard_targets_received, + view); +} + +static void +real_update_location_menu (FMDirectoryView *view) +{ + GtkAction *action; + CajaFile *file; + gboolean is_special_link; + gboolean is_desktop_or_home_dir; + gboolean can_delete_file, show_delete; + gboolean show_separate_delete_command; + gboolean show_open_folder_window; + gboolean show_open_in_new_tab; + GList l; + char *label; + char *tip; + + show_open_folder_window = FALSE; + show_open_in_new_tab = FALSE; + + if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { + label = _("Open in New _Window"); + } else { + label = _("Browse in New _Window"); + show_open_folder_window = TRUE; + } + + show_open_in_new_tab = TRUE; + } else { + label = g_strdup (ngettext ("_Browse Folder", + "_Browse Folders", 1)); + } + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_OPEN_ALTERNATE); + g_object_set (action, + "label", label, + NULL); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_OPEN_IN_NEW_TAB); + gtk_action_set_visible (action, show_open_in_new_tab); + + if (show_open_in_new_tab) { + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { + label = _("Open in New _Tab"); + } else { + label = _("Browse in New _Tab"); + } + g_object_set (action, + "label", label, + NULL); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW); + gtk_action_set_visible (action, show_open_folder_window); + + file = view->details->location_popup_directory_as_file; + g_assert (CAJA_IS_FILE (file)); + g_assert (caja_file_check_if_ready (file, CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)); + + is_special_link = CAJA_IS_DESKTOP_ICON_FILE (file); + is_desktop_or_home_dir = caja_file_is_home (file) + || caja_file_is_desktop_directory (file); + + can_delete_file = + caja_file_can_delete (file) && + !is_special_link && + !is_desktop_or_home_dir; + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_CUT); + gtk_action_set_sensitive (action, can_delete_file); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_PASTE_FILES_INTO); + g_object_set_data (G_OBJECT (action), + "can-paste-according-to-destination", + GINT_TO_POINTER (can_paste_into_file (file))); + gtk_action_set_sensitive (action, + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "can-paste-according-to-clipboard")) && + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "can-paste-according-to-destination"))); + + show_delete = TRUE; + + if (file != NULL && + caja_file_is_in_trash (file)) { + if (caja_file_is_self_owned (file)) { + show_delete = FALSE; + } + + label = _("_Delete Permanently"); + tip = _("Delete the open folder permanently"); + show_separate_delete_command = FALSE; + } else { + label = _("Mo_ve to Trash"); + tip = _("Move the open folder to the Trash"); + show_separate_delete_command = show_delete_command_auto_value; + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_TRASH); + g_object_set (action, + "label", label, + "tooltip", tip, + "icon-name", (file != NULL && + caja_file_is_in_trash (file)) ? + CAJA_ICON_DELETE : CAJA_ICON_TRASH_FULL, + NULL); + gtk_action_set_sensitive (action, can_delete_file); + gtk_action_set_visible (action, show_delete); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_DELETE); + gtk_action_set_visible (action, show_separate_delete_command); + if (show_separate_delete_command) { + gtk_action_set_sensitive (action, can_delete_file); + g_object_set (action, + "icon-name", CAJA_ICON_DELETE, + "sensitive", can_delete_file, + NULL); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_RESTORE_FROM_TRASH); + l.prev = NULL; + l.next = NULL; + l.data = file; + update_restore_from_trash_action (action, &l, TRUE); + + real_update_location_menu_volumes (view); + + /* we silently assume that fm_directory_view_supports_properties always returns the same value. + * Therefore, we don't update the sensitivity of FM_ACTION_LOCATION_PROPERTIES */ +} + +static void +clipboard_changed_callback (CajaClipboardMonitor *monitor, FMDirectoryView *view) +{ + GList *selection; + gint selection_count; + + if (!view->details->active) { + return; + } + + selection = fm_directory_view_get_selection (view); + selection_count = g_list_length (selection); + + real_update_paste_menu (view, selection, selection_count); + + caja_file_list_free (selection); + +} + +static gboolean +can_delete_all (GList *files) +{ + CajaFile *file; + GList *l; + + for (l = files; l != NULL; l = l->next) { + file = l->data; + if (!caja_file_can_delete (file)) { + return FALSE; + } + } + return TRUE; +} + +static gboolean +has_writable_extra_pane (FMDirectoryView *view) +{ + FMDirectoryView *other_view; + + other_view = get_directory_view_of_extra_pane (view); + if (other_view != NULL) { + return !fm_directory_view_is_read_only (other_view); + } + return FALSE; +} + +static void +real_update_menus (FMDirectoryView *view) +{ + GList *selection, *l; + gint selection_count; + const char *tip, *label; + char *label_with_underscore; + gboolean selection_contains_special_link; + gboolean selection_contains_desktop_or_home_dir; + gboolean can_create_files; + gboolean can_delete_files; + gboolean can_copy_files; + gboolean can_link_files; + gboolean can_duplicate_files; + gboolean show_separate_delete_command; + gboolean vfolder_directory; + gboolean disable_command_line; + gboolean show_open_alternate; + gboolean can_open; + gboolean show_app; + gboolean show_save_search; + gboolean save_search_sensitive; + gboolean show_save_search_as; + gboolean show_open_folder_window; + GtkAction *action; + GAppInfo *app; + GIcon *app_icon; + GtkWidget *menuitem; + gboolean next_pane_is_writable; + gboolean show_properties; + + selection = fm_directory_view_get_selection (view); + selection_count = g_list_length (selection); + + selection_contains_special_link = special_link_in_selection (view); + selection_contains_desktop_or_home_dir = desktop_or_home_dir_in_selection (view); + + can_create_files = fm_directory_view_supports_creating_files (view); + can_delete_files = + can_delete_all (selection) && + selection_count != 0 && + !selection_contains_special_link && + !selection_contains_desktop_or_home_dir; + can_copy_files = selection_count != 0 + && !selection_contains_special_link; + + can_duplicate_files = can_create_files && can_copy_files; + can_link_files = can_create_files && can_copy_files; + + vfolder_directory = we_are_in_vfolder_desktop_dir (view); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_RENAME); + gtk_action_set_sensitive (action, + selection_count == 1 && + fm_directory_view_can_rename_file (view, selection->data)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_NEW_FOLDER); + gtk_action_set_sensitive (action, can_create_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN); + gtk_action_set_sensitive (action, selection_count != 0); + + can_open = show_app = selection_count != 0; + + for (l = selection; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (selection->data); + + if (!caja_mime_file_opens_in_external_app (file)) { + show_app = FALSE; + } + + if (!show_app) { + break; + } + } + + label_with_underscore = NULL; + + app = NULL; + app_icon = NULL; + + if (can_open && show_app) { + app = caja_mime_get_default_application_for_files (selection); + } + + if (app != NULL) { + char *escaped_app; + + escaped_app = eel_str_double_underscores (g_app_info_get_display_name (app)); + label_with_underscore = g_strdup_printf (_("_Open With %s"), + escaped_app); + + app_icon = g_app_info_get_icon (app); + if (app_icon != NULL) { + g_object_ref (app_icon); + } + + g_free (escaped_app); + g_object_unref (app); + } + + g_object_set (action, "label", + label_with_underscore ? label_with_underscore : _("_Open"), + NULL); + + menuitem = gtk_ui_manager_get_widget ( + caja_window_info_get_ui_manager (view->details->window), + FM_DIRECTORY_VIEW_MENU_PATH_OPEN); + + /* Only force displaying the icon if it is an application icon */ + gtk_image_menu_item_set_always_show_image ( + GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + + menuitem = gtk_ui_manager_get_widget ( + caja_window_info_get_ui_manager (view->details->window), + FM_DIRECTORY_VIEW_POPUP_PATH_OPEN); + + /* Only force displaying the icon if it is an application icon */ + gtk_image_menu_item_set_always_show_image ( + GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + + if (app_icon == NULL) { + app_icon = g_themed_icon_new (GTK_STOCK_OPEN); + } + + gtk_action_set_gicon (action, app_icon); + g_object_unref (app_icon); + + gtk_action_set_visible (action, can_open); + + g_free (label_with_underscore); + + show_open_alternate = file_list_all_are_folders (selection) && + selection_count > 0 && + !(caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_DESKTOP && + eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)); + show_open_folder_window = FALSE; + if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { + if (selection_count == 0 || selection_count == 1) { + label_with_underscore = g_strdup (_("Open in New _Window")); + } else { + label_with_underscore = g_strdup_printf (ngettext("Open in %'d New _Window", + "Open in %'d New _Windows", + selection_count), + selection_count); + } + } else { + if (selection_count == 0 || selection_count == 1) { + label_with_underscore = g_strdup (_("Browse in New _Window")); + } else { + label_with_underscore = g_strdup_printf (ngettext("Browse in %'d New _Window", + "Browse in %'d New _Windows", + selection_count), + selection_count); + } + show_open_folder_window = show_open_alternate; + } + } else { + label_with_underscore = g_strdup (ngettext ("_Browse Folder", + "_Browse Folders", + selection_count)); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN_ALTERNATE); + g_object_set (action, "label", + label_with_underscore, + NULL); + g_free (label_with_underscore); + + gtk_action_set_sensitive (action, selection_count != 0); + gtk_action_set_visible (action, show_open_alternate); + + /* Open in New Tab action */ + if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { + + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { + if (selection_count == 0 || selection_count == 1) { + label_with_underscore = g_strdup (_("Open in New _Tab")); + } else { + label_with_underscore = g_strdup_printf (ngettext("Open in %'d New _Tab", + "Open in %'d New _Tabs", + selection_count), + selection_count); + } + } else { + if (selection_count == 0 || selection_count == 1) { + label_with_underscore = g_strdup (_("Browse in New _Tab")); + } else { + label_with_underscore = g_strdup_printf (ngettext("Browse in %'d New _Tab", + "Browse in %'d New _Tabs", + selection_count), + selection_count); + } + } + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN_IN_NEW_TAB); + gtk_action_set_sensitive (action, selection_count != 0); + gtk_action_set_visible (action, show_open_alternate); + g_object_set (action, "label", + label_with_underscore, + NULL); + g_free (label_with_underscore); + } else { + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN_IN_NEW_TAB); + gtk_action_set_visible (action, FALSE); + } + + /* next pane actions, only in navigation mode */ + if (caja_window_info_get_window_type (view->details->window) != CAJA_WINDOW_NAVIGATION) { + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY_TO_NEXT_PANE); + gtk_action_set_visible (action, FALSE); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOVE_TO_NEXT_PANE); + gtk_action_set_visible (action, FALSE); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN_FOLDER_WINDOW); + gtk_action_set_visible (action, show_open_folder_window); + + /* Broken into its own function just for convenience */ + reset_open_with_menu (view, selection); + reset_extension_actions_menu (view, selection); + + if (all_selected_items_in_trash (view)) { + label = _("_Delete Permanently"); + tip = _("Delete all selected items permanently"); + show_separate_delete_command = FALSE; + } else { + label = _("Mo_ve to Trash"); + tip = _("Move each selected item to the Trash"); + show_separate_delete_command = show_delete_command_auto_value; + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_TRASH); + g_object_set (action, + "label", label, + "tooltip", tip, + "icon-name", all_selected_items_in_trash (view) ? + CAJA_ICON_DELETE : CAJA_ICON_TRASH_FULL, + NULL); + gtk_action_set_sensitive (action, can_delete_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_DELETE); + gtk_action_set_visible (action, show_separate_delete_command); + + if (show_separate_delete_command) { + g_object_set (action, + "label", _("_Delete"), + "icon-name", CAJA_ICON_DELETE, + NULL); + } + gtk_action_set_sensitive (action, can_delete_files); + + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_RESTORE_FROM_TRASH); + update_restore_from_trash_action (action, selection, FALSE); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_DUPLICATE); + gtk_action_set_sensitive (action, can_duplicate_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_CREATE_LINK); + gtk_action_set_sensitive (action, can_link_files); + g_object_set (action, "label", + ngettext ("Ma_ke Link", + "Ma_ke Links", + selection_count), + NULL); + + show_properties = (!FM_IS_DESKTOP_ICON_VIEW (view) || selection_count > 0) && + fm_directory_view_supports_properties (view); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PROPERTIES); + + gtk_action_set_sensitive (action, show_properties); + + if (selection_count == 0) { + gtk_action_set_tooltip (action, _("View or modify the properties of the open folder")); + } else { + gtk_action_set_tooltip (action, _("View or modify the properties of each selected item")); + } + + gtk_action_set_visible (action, show_properties); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PROPERTIES_ACCEL); + + gtk_action_set_sensitive (action, show_properties); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_EMPTY_TRASH); + g_object_set (action, + "label", _("E_mpty Trash"), + NULL); + gtk_action_set_sensitive (action, !caja_trash_monitor_is_empty ()); + gtk_action_set_visible (action, should_show_empty_trash (view)); + + show_save_search = FALSE; + save_search_sensitive = FALSE; + show_save_search_as = FALSE; + if (view->details->model && + CAJA_IS_SEARCH_DIRECTORY (view->details->model)) { + CajaSearchDirectory *search; + + search = CAJA_SEARCH_DIRECTORY (view->details->model); + if (caja_search_directory_is_saved_search (search)) { + show_save_search = TRUE; + save_search_sensitive = caja_search_directory_is_modified (search); + } else { + show_save_search_as = TRUE; + } + } + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SAVE_SEARCH); + gtk_action_set_visible (action, show_save_search); + gtk_action_set_sensitive (action, save_search_sensitive); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SAVE_SEARCH_AS); + gtk_action_set_visible (action, show_save_search_as); + + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELECT_ALL); + gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELECT_PATTERN); + gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_INVERT_SELECTION); + gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_CUT); + gtk_action_set_sensitive (action, can_delete_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY); + gtk_action_set_sensitive (action, can_copy_files); + + real_update_paste_menu (view, selection, selection_count); + + disable_command_line = eel_preferences_get_boolean (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_NEW_LAUNCHER); + gtk_action_set_visible (action, vfolder_directory && !disable_command_line); + gtk_action_set_sensitive (action, can_create_files); + + real_update_menus_volumes (view, selection, selection_count); + + caja_file_list_free (selection); + + if (view->details->scripts_invalid) { + update_scripts_menu (view); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_NEW_DOCUMENTS); + gtk_action_set_sensitive (action, can_create_files); + + if (can_create_files && view->details->templates_invalid) { + update_templates_menu (view); + } + + next_pane_is_writable = has_writable_extra_pane (view); + + /* next pane: works if file is copyable, and next pane is writable */ + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY_TO_NEXT_PANE); + gtk_action_set_sensitive (action, can_copy_files && next_pane_is_writable); + + /* move to next pane: works if file is cuttable, and next pane is writable */ + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOVE_TO_NEXT_PANE); + gtk_action_set_sensitive (action, can_delete_files && next_pane_is_writable); + + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY_TO_HOME); + gtk_action_set_sensitive (action, can_copy_files); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY_TO_DESKTOP); + gtk_action_set_sensitive (action, can_copy_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOVE_TO_HOME); + gtk_action_set_sensitive (action, can_delete_files); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOVE_TO_DESKTOP); + gtk_action_set_sensitive (action, can_delete_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + "CopyToMenu"); + gtk_action_set_sensitive (action, can_copy_files); + action = gtk_action_group_get_action (view->details->dir_action_group, + "MoveToMenu"); + gtk_action_set_sensitive (action, can_delete_files); +} + +/** + * fm_directory_view_pop_up_selection_context_menu + * + * Pop up a context menu appropriate to the selected items. + * @view: FMDirectoryView of interest. + * @event: The event that triggered this context menu. + * + * Return value: CajaDirectory for this view. + * + **/ +void +fm_directory_view_pop_up_selection_context_menu (FMDirectoryView *view, + GdkEventButton *event) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Make the context menu items not flash as they update to proper disabled, + * etc. states by forcing menus to update now. + */ + update_menus_if_pending (view); + + update_context_menu_position_from_event (view, event); + + eel_pop_up_context_menu (create_popup_menu + (view, FM_DIRECTORY_VIEW_POPUP_PATH_SELECTION), + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + event); +} + +/** + * fm_directory_view_pop_up_background_context_menu + * + * Pop up a context menu appropriate to the view globally at the last right click location. + * @view: FMDirectoryView of interest. + * + * Return value: CajaDirectory for this view. + * + **/ +void +fm_directory_view_pop_up_background_context_menu (FMDirectoryView *view, + GdkEventButton *event) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Make the context menu items not flash as they update to proper disabled, + * etc. states by forcing menus to update now. + */ + update_menus_if_pending (view); + + update_context_menu_position_from_event (view, event); + + + eel_pop_up_context_menu (create_popup_menu + (view, FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND), + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + event); +} + +static void +real_pop_up_location_context_menu (FMDirectoryView *view) +{ + /* always update the menu before showing it. Shouldn't be too expensive. */ + real_update_location_menu (view); + + update_context_menu_position_from_event (view, view->details->location_popup_event); + + eel_pop_up_context_menu (create_popup_menu + (view, FM_DIRECTORY_VIEW_POPUP_PATH_LOCATION), + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + view->details->location_popup_event); +} + +static void +location_popup_file_attributes_ready (CajaFile *file, + gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + g_assert (file == view->details->location_popup_directory_as_file); + + real_pop_up_location_context_menu (view); +} + +static void +unschedule_pop_up_location_context_menu (FMDirectoryView *view) +{ + if (view->details->location_popup_directory_as_file != NULL) { + g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file)); + caja_file_cancel_call_when_ready (view->details->location_popup_directory_as_file, + location_popup_file_attributes_ready, + view); + caja_file_unref (view->details->location_popup_directory_as_file); + view->details->location_popup_directory_as_file = NULL; + } +} + +static void +schedule_pop_up_location_context_menu (FMDirectoryView *view, + GdkEventButton *event, + CajaFile *file) +{ + g_assert (CAJA_IS_FILE (file)); + + if (view->details->location_popup_event != NULL) { + gdk_event_free ((GdkEvent *) view->details->location_popup_event); + } + view->details->location_popup_event = (GdkEventButton *) gdk_event_copy ((GdkEvent *)event); + + if (file == view->details->location_popup_directory_as_file) { + if (caja_file_check_if_ready (file, CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)) { + real_pop_up_location_context_menu (view); + } + } else { + unschedule_pop_up_location_context_menu (view); + + view->details->location_popup_directory_as_file = caja_file_ref (file); + caja_file_call_when_ready (view->details->location_popup_directory_as_file, + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO, + location_popup_file_attributes_ready, + view); + } +} + +/** + * fm_directory_view_pop_up_location_context_menu + * + * Pop up a context menu appropriate to the view globally. + * @view: FMDirectoryView of interest. + * @event: GdkEventButton triggering the popup. + * @location: The location the popup-menu should be created for, + * or NULL for the currently displayed location. + * + **/ +void +fm_directory_view_pop_up_location_context_menu (FMDirectoryView *view, + GdkEventButton *event, + const char *location) +{ + CajaFile *file; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + if (location != NULL) { + file = caja_file_get_by_uri (location); + } else { + file = caja_file_ref (view->details->directory_as_file); + } + + if (file != NULL) { + schedule_pop_up_location_context_menu (view, event, file); + caja_file_unref (file); + } +} + +static void +fm_directory_view_drop_proxy_received_uris (FMDirectoryView *view, + const GList *source_uri_list, + const char *target_uri, + GdkDragAction action) +{ + char *container_uri; + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + if (action == GDK_ACTION_ASK) { + action = caja_drag_drop_action_ask + (GTK_WIDGET (view), + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + if (action == 0) { + return; + } + } + + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + source_uri_list, + fm_directory_view_get_copied_files_atom (view)); + + fm_directory_view_move_copy_items (source_uri_list, NULL, + target_uri != NULL ? target_uri : container_uri, + action, 0, 0, view); + + g_free (container_uri); +} + +static void +fm_directory_view_drop_proxy_received_netscape_url (FMDirectoryView *view, + const char *netscape_url, + const char *target_uri, + GdkDragAction action) +{ + fm_directory_view_handle_netscape_url_drop (view, + netscape_url, + target_uri, + action, 0, 0); +} + +static void +schedule_update_menus (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Don't schedule updates after destroy (#349551), + * or if we are not active. + */ + if (view->details->window == NULL || + !view->details->active) { + return; + } + + view->details->menu_states_untrustworthy = TRUE; + + /* Schedule a menu update with the current update interval */ + if (view->details->update_menus_timeout_id == 0) { + view->details->update_menus_timeout_id + = g_timeout_add (view->details->update_interval, update_menus_timeout_callback, view); + } +} + +static void +remove_update_status_idle_callback (FMDirectoryView *view) +{ + if (view->details->update_status_idle_id != 0) { + g_source_remove (view->details->update_status_idle_id); + view->details->update_status_idle_id = 0; + } +} + +static gboolean +update_status_idle_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + fm_directory_view_display_selection_info (view); + view->details->update_status_idle_id = 0; + return FALSE; +} + +static void +schedule_update_status (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Make sure we haven't already destroyed it */ + if (view->details->window == NULL) { + return; + } + + if (view->details->loading) { + /* Don't update status bar while loading the dir */ + return; + } + + if (view->details->update_status_idle_id == 0) { + view->details->update_status_idle_id = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, + update_status_idle_callback, view, NULL); + } +} + +/** + * fm_directory_view_notify_selection_changed: + * + * Notify this view that the selection has changed. This is normally + * called only by subclasses. + * @view: FMDirectoryView whose selection has changed. + * + **/ +void +fm_directory_view_notify_selection_changed (FMDirectoryView *view) +{ + GList *selection; + GtkWindow *window; + + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (caja_debug_log_is_domain_enabled (CAJA_DEBUG_LOG_DOMAIN_USER)) { + selection = fm_directory_view_get_selection (view); + + window = fm_directory_view_get_containing_window (view); + caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, selection, + "selection changed in window %p", + window); + caja_file_list_free (selection); + } + + view->details->selection_was_removed = FALSE; + + if (!view->details->selection_change_is_due_to_shell) { + view->details->send_selection_change_to_shell = TRUE; + } + + /* Schedule a display of the new selection. */ + if (view->details->display_selection_idle_id == 0) { + view->details->display_selection_idle_id + = g_idle_add (display_selection_info_idle_callback, + view); + } + + if (view->details->batching_selection_level != 0) { + view->details->selection_changed_while_batched = TRUE; + } else { + /* Here is the work we do only when we're not + * batching selection changes. In other words, it's the slower + * stuff that we don't want to slow down selection techniques + * such as rubberband-selecting in icon view. + */ + + /* Schedule an update of menu item states to match selection */ + schedule_update_menus (view); + } +} + +static void +file_changed_callback (CajaFile *file, gpointer callback_data) +{ + FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data); + + schedule_changes (view); + + schedule_update_menus (view); + schedule_update_status (view); + + /* We might have different capabilities, so we need to update + * relative icon emblems . (Writeable etc). + * Don't do this for trash, as it never changes writability + * but does change a lot for the file count attribute. + */ + if (!caja_file_is_in_trash (file)) { + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, emblems_changed, (view)); + } +} + +/** + * load_directory: + * + * Switch the displayed location to a new uri. If the uri is not valid, + * the location will not be switched; user feedback will be provided instead. + * @view: FMDirectoryView whose location will be changed. + * @uri: A string representing the uri to switch to. + * + **/ +static void +load_directory (FMDirectoryView *view, + CajaDirectory *directory) +{ + CajaDirectory *old_directory; + CajaFile *old_file; + CajaFileAttributes attributes; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (CAJA_IS_DIRECTORY (directory)); + + fm_directory_view_stop (view); + fm_directory_view_clear (view); + + view->details->loading = TRUE; + + /* Update menus when directory is empty, before going to new + * location, so they won't have any false lingering knowledge + * of old selection. + */ + schedule_update_menus (view); + + while (view->details->subdirectory_list != NULL) { + fm_directory_view_remove_subdirectory (view, + view->details->subdirectory_list->data); + } + + disconnect_model_handlers (view); + + old_directory = view->details->model; + caja_directory_ref (directory); + view->details->model = directory; + caja_directory_unref (old_directory); + + old_file = view->details->directory_as_file; + view->details->directory_as_file = + caja_directory_get_corresponding_file (directory); + caja_file_unref (old_file); + + view->details->reported_load_error = FALSE; + + /* FIXME bugzilla.gnome.org 45062: In theory, we also need to monitor metadata here (as + * well as doing a call when ready), in case external forces + * change the directory's file metadata. + */ + attributes = + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO; + view->details->metadata_for_directory_as_file_pending = TRUE; + view->details->metadata_for_files_in_directory_pending = TRUE; + caja_file_call_when_ready + (view->details->directory_as_file, + attributes, + metadata_for_directory_as_file_ready_callback, view); + caja_directory_call_when_ready + (view->details->model, + attributes, + FALSE, + metadata_for_files_in_directory_ready_callback, view); + + /* If capabilities change, then we need to update the menus + * because of New Folder, and relative emblems. + */ + attributes = + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO; + caja_file_monitor_add (view->details->directory_as_file, + &view->details->directory_as_file, + attributes); + + view->details->file_changed_handler_id = g_signal_connect + (view->details->directory_as_file, "changed", + G_CALLBACK (file_changed_callback), view); +} + +static void +finish_loading (FMDirectoryView *view) +{ + CajaFileAttributes attributes; + + caja_window_info_report_load_underway (view->details->window, + CAJA_VIEW (view)); + + /* Tell interested parties that we've begun loading this directory now. + * Subclasses use this to know that the new metadata is now available. + */ + fm_directory_view_begin_loading (view); + + /* Assume we have now all information to show window */ + caja_window_info_view_visible (view->details->window, CAJA_VIEW (view)); + + if (caja_directory_are_all_files_seen (view->details->model)) { + /* Unschedule a pending update and schedule a new one with the minimal + * update interval. This gives the view a short chance at gathering the + * (cached) deep counts. + */ + unschedule_display_of_pending_files (view); + schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); + } + + /* Start loading. */ + + /* Connect handlers to learn about loading progress. */ + view->details->done_loading_handler_id = g_signal_connect + (view->details->model, "done_loading", + G_CALLBACK (done_loading_callback), view); + view->details->load_error_handler_id = g_signal_connect + (view->details->model, "load_error", + G_CALLBACK (load_error_callback), view); + + /* Monitor the things needed to get the right icon. Also + * monitor a directory's item count because the "size" + * attribute is based on that, and the file's metadata + * and possible custom name. + */ + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_EXTENSION_INFO; + + caja_directory_file_monitor_add (view->details->model, + &view->details->model, + view->details->show_hidden_files, + view->details->show_backup_files, + attributes, + files_added_callback, view); + + view->details->files_added_handler_id = g_signal_connect + (view->details->model, "files_added", + G_CALLBACK (files_added_callback), view); + view->details->files_changed_handler_id = g_signal_connect + (view->details->model, "files_changed", + G_CALLBACK (files_changed_callback), view); +} + +static void +finish_loading_if_all_metadata_loaded (FMDirectoryView *view) +{ + if (!view->details->metadata_for_directory_as_file_pending && + !view->details->metadata_for_files_in_directory_pending) { + finish_loading (view); + } +} + +static void +metadata_for_directory_as_file_ready_callback (CajaFile *file, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = callback_data; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (view->details->directory_as_file == file); + g_assert (view->details->metadata_for_directory_as_file_pending); + + view->details->metadata_for_directory_as_file_pending = FALSE; + + finish_loading_if_all_metadata_loaded (view); +} + +static void +metadata_for_files_in_directory_ready_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = callback_data; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (view->details->model == directory); + g_assert (view->details->metadata_for_files_in_directory_pending); + + view->details->metadata_for_files_in_directory_pending = FALSE; + + finish_loading_if_all_metadata_loaded (view); +} + +char ** +fm_directory_view_get_emblem_names_to_exclude (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_emblem_names_to_exclude, (view)); +} + +static char ** +real_get_emblem_names_to_exclude (FMDirectoryView *view) +{ + char **excludes; + int i; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + excludes = g_new (char *, 3); + + i = 0; + excludes[i++] = g_strdup (CAJA_FILE_EMBLEM_NAME_TRASH); + + if (!caja_file_can_write (view->details->directory_as_file)) { + excludes[i++] = g_strdup (CAJA_FILE_EMBLEM_NAME_CANT_WRITE); + } + + excludes[i++] = NULL; + + return excludes; +} + +/** + * fm_directory_view_merge_menus: + * + * Add this view's menus to the window's menu bar. + * @view: FMDirectoryView in question. + */ +static void +fm_directory_view_merge_menus (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + merge_menus, (view)); +} + +static void +fm_directory_view_unmerge_menus (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + unmerge_menus, (view)); +} + +static void +disconnect_handler (GObject *object, int *id) +{ + if (*id != 0) { + g_signal_handler_disconnect (object, *id); + *id = 0; + } +} + +static void +disconnect_directory_handler (FMDirectoryView *view, int *id) +{ + disconnect_handler (G_OBJECT (view->details->model), id); +} + +static void +disconnect_directory_as_file_handler (FMDirectoryView *view, int *id) +{ + disconnect_handler (G_OBJECT (view->details->directory_as_file), id); +} + +static void +disconnect_model_handlers (FMDirectoryView *view) +{ + if (view->details->model == NULL) { + return; + } + disconnect_directory_handler (view, &view->details->files_added_handler_id); + disconnect_directory_handler (view, &view->details->files_changed_handler_id); + disconnect_directory_handler (view, &view->details->done_loading_handler_id); + disconnect_directory_handler (view, &view->details->load_error_handler_id); + disconnect_directory_as_file_handler (view, &view->details->file_changed_handler_id); + caja_file_cancel_call_when_ready (view->details->directory_as_file, + metadata_for_directory_as_file_ready_callback, + view); + caja_directory_cancel_callback (view->details->model, + metadata_for_files_in_directory_ready_callback, + view); + caja_directory_file_monitor_remove (view->details->model, + &view->details->model); + caja_file_monitor_remove (view->details->directory_as_file, + &view->details->directory_as_file); +} + +/** + * fm_directory_view_reset_to_defaults: + * + * set sorting order, zoom level, etc. to match defaults + * + **/ +void +fm_directory_view_reset_to_defaults (FMDirectoryView *view) +{ + CajaWindowShowHiddenFilesMode mode; + + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + reset_to_defaults, (view)); + mode = caja_window_info_get_hidden_files_mode (view->details->window); + if (mode != CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) { + caja_window_info_set_hidden_files_mode (view->details->window, + CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT); + } +} + +/** + * fm_directory_view_select_all: + * + * select all the items in the view + * + **/ +void +fm_directory_view_select_all (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + select_all, (view)); +} + +/** + * fm_directory_view_set_selection: + * + * set the selection to the items identified in @selection. @selection + * should be a list of CajaFiles + * + **/ +void +fm_directory_view_set_selection (FMDirectoryView *view, GList *selection) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + set_selection, (view, selection)); +} + +static void +fm_directory_view_select_file (FMDirectoryView *view, CajaFile *file) +{ + GList file_list; + + file_list.data = file; + file_list.next = NULL; + file_list.prev = NULL; + fm_directory_view_set_selection (view, &file_list); +} + +/** + * fm_directory_view_get_selected_icon_locations: + * + * return an array of locations of selected icons if available + * Return value: GArray of GdkPoints + * + **/ +GArray * +fm_directory_view_get_selected_icon_locations (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_selected_icon_locations, (view)); +} + +/** + * fm_directory_view_reveal_selection: + * + * Scroll as necessary to reveal the selected items. + **/ +void +fm_directory_view_reveal_selection (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + reveal_selection, (view)); +} + +static gboolean +remove_all (gpointer key, gpointer value, gpointer callback_data) +{ + return TRUE; +} + +/** + * fm_directory_view_stop: + * + * Stop the current ongoing process, such as switching to a new uri. + * @view: FMDirectoryView in question. + * + **/ +void +fm_directory_view_stop (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + unschedule_display_of_pending_files (view); + reset_update_interval (view); + + /* Free extra undisplayed files */ + file_and_directory_list_free (view->details->new_added_files); + view->details->new_added_files = NULL; + file_and_directory_list_free (view->details->new_changed_files); + view->details->new_changed_files = NULL; + g_hash_table_foreach_remove (view->details->non_ready_files, remove_all, NULL); + file_and_directory_list_free (view->details->old_added_files); + view->details->old_added_files = NULL; + file_and_directory_list_free (view->details->old_changed_files); + view->details->old_changed_files = NULL; + eel_g_object_list_free (view->details->pending_locations_selected); + view->details->pending_locations_selected = NULL; + + if (view->details->model != NULL) { + caja_directory_file_monitor_remove (view->details->model, view); + } + done_loading (view, FALSE); +} + +gboolean +fm_directory_view_is_read_only (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + is_read_only, (view)); +} + +gboolean +fm_directory_view_is_empty (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + is_empty, (view)); +} + +gboolean +fm_directory_view_is_editable (FMDirectoryView *view) +{ + CajaDirectory *directory; + + directory = fm_directory_view_get_model (view); + + if (directory != NULL) { + return caja_directory_is_editable (directory); + } + + return TRUE; +} + +void +fm_directory_view_set_initiated_unmount (FMDirectoryView *view, + gboolean initiated_unmount) +{ + if (view->details->window != NULL) { + caja_window_info_set_initiated_unmount(view->details->window, + initiated_unmount); + } +} + +static gboolean +real_is_read_only (FMDirectoryView *view) +{ + CajaFile *file; + + if (!fm_directory_view_is_editable (view)) { + return TRUE; + } + + file = fm_directory_view_get_directory_as_file (view); + if (file != NULL) { + return !caja_file_can_write (file); + } + return FALSE; +} + +gboolean +fm_directory_view_supports_creating_files (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + supports_creating_files, (view)); +} + +gboolean +fm_directory_view_accepts_dragged_files (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + accepts_dragged_files, (view)); +} + +/** + * fm_directory_view_should_show_file + * + * Returns whether or not this file should be displayed based on + * current filtering options. + */ +gboolean +fm_directory_view_should_show_file (FMDirectoryView *view, CajaFile *file) +{ + return caja_file_should_show (file, + view->details->show_hidden_files, + view->details->show_backup_files, + view->details->show_foreign_files); +} + +static gboolean +real_supports_creating_files (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return !fm_directory_view_is_read_only (view) && !showing_trash_directory (view); +} + +static gboolean +real_accepts_dragged_files (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return !fm_directory_view_is_read_only (view); +} + +gboolean +fm_directory_view_supports_properties (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + supports_properties, (view)); +} + +static gboolean +real_supports_properties (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return TRUE; +} + +gboolean +fm_directory_view_supports_zooming (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + supports_zooming, (view)); +} + +static gboolean +real_supports_zooming (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return TRUE; +} + +gboolean +fm_directory_view_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + using_manual_layout, (view)); +} + +static gboolean +real_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return FALSE; +} + +/** + * fm_directory_view_update_menus: + * + * Update the sensitivity and wording of dynamic menu items. + * @view: FMDirectoryView in question. + */ +void +fm_directory_view_update_menus (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (!view->details->active) { + return; + } + + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + update_menus, (view)); + + view->details->menu_states_untrustworthy = FALSE; +} + +static void +schedule_update_menus_callback (gpointer callback_data) +{ + schedule_update_menus (FM_DIRECTORY_VIEW (callback_data)); +} + +void +fm_directory_view_ignore_hidden_file_preferences (FMDirectoryView *view) +{ + g_return_if_fail (view->details->model == NULL); + + if (view->details->ignore_hidden_file_preferences) { + return; + } + + view->details->show_hidden_files = FALSE; + view->details->show_backup_files = FALSE; + view->details->ignore_hidden_file_preferences = TRUE; +} + +void +fm_directory_view_set_show_foreign (FMDirectoryView *view, + gboolean show_foreign) +{ + view->details->show_foreign_files = show_foreign; +} + +char * +fm_directory_view_get_uri (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + if (view->details->model == NULL) { + return NULL; + } + return caja_directory_get_uri (view->details->model); +} + +/* Get the real directory where files will be stored and created */ +char * +fm_directory_view_get_backing_uri (FMDirectoryView *view) +{ + CajaDirectory *directory; + char *uri; + + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + if (view->details->model == NULL) { + return NULL; + } + + directory = view->details->model; + + if (CAJA_IS_DESKTOP_DIRECTORY (directory)) { + directory = caja_desktop_directory_get_real_directory (CAJA_DESKTOP_DIRECTORY (directory)); + } else { + caja_directory_ref (directory); + } + + uri = caja_directory_get_uri (directory); + + caja_directory_unref (directory); + + return uri; +} + +void +fm_directory_view_move_copy_items (const GList *item_uris, + GArray *relative_item_points, + const char *target_uri, + int copy_action, + int x, int y, + FMDirectoryView *view) +{ + CajaFile *target_file; + + g_assert (relative_item_points == NULL + || relative_item_points->len == 0 + || g_list_length ((GList *)item_uris) == relative_item_points->len); + + /* add the drop location to the icon offsets */ + offset_drop_points (relative_item_points, x, y); + + target_file = caja_file_get_existing_by_uri (target_uri); + /* special-case "command:" here instead of starting a move/copy */ + if (target_file != NULL && caja_file_is_launcher (target_file)) { + caja_file_unref (target_file); + caja_launch_desktop_file ( + gtk_widget_get_screen (GTK_WIDGET (view)), + target_uri, item_uris, + fm_directory_view_get_containing_window (view)); + return; + } else if (copy_action == GDK_ACTION_COPY && + caja_is_file_roller_installed () && + target_file != NULL && + caja_file_is_archive (target_file)) { + char *command, *quoted_uri, *tmp; + const GList *l; + GdkScreen *screen; + + /* Handle dropping onto a file-roller archiver file, instead of starting a move/copy */ + + caja_file_unref (target_file); + + quoted_uri = g_shell_quote (target_uri); + command = g_strconcat ("file-roller -a ", quoted_uri, NULL); + g_free (quoted_uri); + + for (l = item_uris; l != NULL; l = l->next) { + quoted_uri = g_shell_quote ((char *) l->data); + + tmp = g_strconcat (command, " ", quoted_uri, NULL); + g_free (command); + command = tmp; + + g_free (quoted_uri); + } + + screen = gtk_widget_get_screen (GTK_WIDGET (view)); + if (screen == NULL) { + screen = gdk_screen_get_default (); + } + gdk_spawn_command_line_on_screen (screen, command, NULL); + g_free (command); + + return; + } + caja_file_unref (target_file); + + caja_file_operations_copy_move + (item_uris, relative_item_points, + target_uri, copy_action, GTK_WIDGET (view), + copy_move_done_callback, pre_copy_move (view)); +} + +gboolean +fm_directory_view_can_accept_item (CajaFile *target_item, + const char *item_uri, + FMDirectoryView *view) +{ + g_return_val_if_fail (CAJA_IS_FILE (target_item), FALSE); + g_return_val_if_fail (item_uri != NULL, FALSE); + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return caja_drag_can_accept_item (target_item, item_uri); +} + +static void +fm_directory_view_trash_state_changed_callback (CajaTrashMonitor *trash_monitor, + gboolean state, gpointer callback_data) +{ + FMDirectoryView *view; + + view = (FMDirectoryView *) callback_data; + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + schedule_update_menus (view); +} + +void +fm_directory_view_start_batching_selection_changes (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + ++view->details->batching_selection_level; + view->details->selection_changed_while_batched = FALSE; +} + +void +fm_directory_view_stop_batching_selection_changes (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + g_return_if_fail (view->details->batching_selection_level > 0); + + if (--view->details->batching_selection_level == 0) { + if (view->details->selection_changed_while_batched) { + fm_directory_view_notify_selection_changed (view); + } + } +} + +static void +revert_slashes (char *string) +{ + while (*string != 0) { + if (*string == '/') { + *string = '\\'; + } + string++; + } +} + + +static GdkDragAction +ask_link_action (FMDirectoryView *view) +{ + int button_pressed; + GdkDragAction result; + GtkWindow *parent_window; + GtkWidget *dialog; + + parent_window = NULL; + + /* Don't use desktop window as parent, since that means + we show up an all desktops etc */ + if (! FM_IS_DESKTOP_ICON_VIEW (view)) { + parent_window = GTK_WINDOW (fm_directory_view_get_containing_window (view)); + } + + dialog = gtk_message_dialog_new (parent_window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("Download location?")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("You can download it or make a link to it.")); + + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("Make a _Link"), 0); + gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, 1); + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Download"), 2); + + gtk_window_set_title (GTK_WINDOW (dialog), ""); /* as per HIG */ + gtk_window_set_focus_on_map (GTK_WINDOW (dialog), TRUE); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), 2); + + gtk_window_present (GTK_WINDOW (dialog)); + + button_pressed = gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + + switch (button_pressed) { + case 0: + result = GDK_ACTION_LINK; + break; + case 1: + case GTK_RESPONSE_DELETE_EVENT: + result = 0; + break; + case 2: + result = GDK_ACTION_COPY; + break; + default: + g_assert_not_reached (); + result = 0; + } + + return result; +} + +typedef struct { + FMDirectoryView *view; + GCancellable *cancellable; + char *encoded_url; + char *target_uri; + int x; + int y; + guint timeout; +} NetscapeUrlDropAsk; + +static void +handle_netscape_url_drop_ask_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NetscapeUrlDropAsk *data; + GdkDragAction action; + GFileInfo *info; + GFile *f; + const char *mime_type; + + data = user_data; + f = G_FILE (source_object); + + info = g_file_query_info_finish (f, res, NULL); + mime_type = NULL; + + if (info) { + mime_type = g_file_info_get_content_type (info); + } + + if (mime_type != NULL && + (g_content_type_equals (mime_type, "text/html") || + g_content_type_equals (mime_type, "text/xml") || + g_content_type_equals (mime_type, "application/xhtml+xml"))) { + action = GDK_ACTION_LINK; + } else if (mime_type != NULL && + g_content_type_equals (mime_type, "text/plain")) { + action = ask_link_action (data->view); + } else { + action = GDK_ACTION_COPY; + } + if (info) { + g_object_unref (info); + } + + if (action != 0) { + fm_directory_view_handle_netscape_url_drop (data->view, + data->encoded_url, + data->target_uri, + action, + data->x, data->y); + } + + g_object_unref (data->view); + g_object_unref (data->cancellable); + if (data->timeout != 0) { + g_source_remove (data->timeout); + } + g_free (data->encoded_url); + g_free (data->target_uri); + g_free (data); +} + +static gboolean +handle_netscape_url_drop_timeout (gpointer user_data) +{ + NetscapeUrlDropAsk *data; + + data = user_data; + + g_cancellable_cancel (data->cancellable); + data->timeout = 0; + + return FALSE; +} + +static inline void +fm_directory_view_widget_to_file_operation_position (FMDirectoryView *view, + GdkPoint *position) +{ + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, + widget_to_file_operation_position, + (view, position)); +} + +static void +fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view, + int *x, int *y) +{ + GdkPoint position; + + position.x = *x; + position.y = *y; + fm_directory_view_widget_to_file_operation_position (view, &position); + *x = position.x; + *y = position.y; +} + +void +fm_directory_view_handle_netscape_url_drop (FMDirectoryView *view, + const char *encoded_url, + const char *target_uri, + GdkDragAction action, + int x, + int y) +{ + GdkPoint point; + GdkScreen *screen; + int screen_num; + char *url, *title; + char *link_name, *link_display_name; + char *container_uri; + GArray *points; + char **bits; + GList *uri_list = NULL; + GFile *f; + + if (encoded_url == NULL) { + return; + } + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + f = g_file_new_for_uri (target_uri != NULL ? target_uri : container_uri); + if (!g_file_is_native (f)) { + eel_show_warning_dialog (_("Drag and drop is not supported."), + _("Drag and drop is only supported on local file systems."), + fm_directory_view_get_containing_window (view)); + g_object_unref (f); + g_free (container_uri); + return; + } + g_object_unref (f); + + /* _NETSCAPE_URL_ works like this: $URL\n$TITLE */ + bits = g_strsplit (encoded_url, "\n", 0); + switch (g_strv_length (bits)) { + case 0: + g_strfreev (bits); + g_free (container_uri); + return; + case 1: + url = bits[0]; + title = NULL; + break; + default: + url = bits[0]; + title = bits[1]; + } + + if (action == GDK_ACTION_ASK) { + NetscapeUrlDropAsk *data; + + f = g_file_new_for_uri (url); + data = g_new0 (NetscapeUrlDropAsk, 1); + data->view = g_object_ref (view); + data->cancellable = g_cancellable_new (); + data->encoded_url = g_strdup (encoded_url); + data->target_uri = g_strdup (target_uri); + data->x = x; + data->y = y; + /* Ensure we wait at most 1 second for mimetype */ + data->timeout = g_timeout_add (1000, + handle_netscape_url_drop_timeout, + data); + g_file_query_info_async (f, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0, + 0, data->cancellable, + handle_netscape_url_drop_ask_cb, + data); + + g_free (container_uri); + return; + } + + fm_directory_view_widget_to_file_operation_position_xy (view, &x, &y); + + /* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE + * and we don't support combinations either. */ + if ((action != GDK_ACTION_DEFAULT) && + (action != GDK_ACTION_COPY) && + (action != GDK_ACTION_MOVE) && + (action != GDK_ACTION_LINK)) { + eel_show_warning_dialog (_("Drag and drop is not supported."), + _("An invalid drag type was used."), + fm_directory_view_get_containing_window (view)); + g_free (container_uri); + return; + } + + if (action == GDK_ACTION_LINK) { + if (eel_str_is_empty (title)) { + GFile *f; + + f = g_file_new_for_uri (url); + link_name = g_file_get_basename (f); + g_object_unref (f); + } else { + link_name = g_strdup (title); + } + + if (!eel_str_is_empty (link_name)) { + link_display_name = g_strdup_printf (_("Link to %s"), link_name); + + /* The filename can't contain slashes, strip em. + (the basename of http://foo/ is http://foo/) */ + revert_slashes (link_name); + + point.x = x; + point.y = y; + + screen = gtk_widget_get_screen (GTK_WIDGET (view)); + screen_num = gdk_screen_get_number (screen); + + caja_link_local_create (target_uri != NULL ? target_uri : container_uri, + link_name, + link_display_name, + "mate-fs-bookmark", + url, + &point, + screen_num, + TRUE); + + g_free (link_display_name); + } + g_free (link_name); + } else { + GdkPoint tmp_point = { 0, 0 }; + + /* pass in a 1-item array of icon positions, relative to x, y */ + points = g_array_new (FALSE, TRUE, sizeof (GdkPoint)); + g_array_append_val (points, tmp_point); + + uri_list = g_list_append (uri_list, url); + + fm_directory_view_move_copy_items (uri_list, points, + target_uri != NULL ? target_uri : container_uri, + action, x, y, view); + + g_list_free (uri_list); + g_array_free (points, TRUE); + } + + g_strfreev (bits); + + g_free (container_uri); +} + +void +fm_directory_view_handle_uri_list_drop (FMDirectoryView *view, + const char *item_uris, + const char *target_uri, + GdkDragAction action, + int x, + int y) +{ + gchar **uri_list; + GList *real_uri_list = NULL; + char *container_uri; + int n_uris, i; + GArray *points; + + if (item_uris == NULL) { + return; + } + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + if (action == GDK_ACTION_ASK) { + action = caja_drag_drop_action_ask + (GTK_WIDGET (view), + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + if (action == 0) { + g_free (container_uri); + return; + } + } + + /* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE + * and we don't support combinations either. */ + if ((action != GDK_ACTION_DEFAULT) && + (action != GDK_ACTION_COPY) && + (action != GDK_ACTION_MOVE) && + (action != GDK_ACTION_LINK)) { + eel_show_warning_dialog (_("Drag and drop is not supported."), + _("An invalid drag type was used."), + fm_directory_view_get_containing_window (view)); + g_free (container_uri); + return; + } + + n_uris = 0; + uri_list = g_uri_list_extract_uris (item_uris); + for (i = 0; uri_list[i] != NULL; i++) { + real_uri_list = g_list_append (real_uri_list, uri_list[i]); + n_uris++; + } + g_free (uri_list); + + /* do nothing if no real uris are left */ + if (n_uris == 0) { + g_free (container_uri); + return; + } + + if (n_uris == 1) { + GdkPoint tmp_point = { 0, 0 }; + + /* pass in a 1-item array of icon positions, relative to x, y */ + points = g_array_new (FALSE, TRUE, sizeof (GdkPoint)); + g_array_append_val (points, tmp_point); + } else { + points = NULL; + } + + fm_directory_view_widget_to_file_operation_position_xy (view, &x, &y); + + fm_directory_view_move_copy_items (real_uri_list, points, + target_uri != NULL ? target_uri : container_uri, + action, x, y, view); + + eel_g_list_free_deep (real_uri_list); + + if (points != NULL) + g_array_free (points, TRUE); + + g_free (container_uri); +} + +void +fm_directory_view_handle_text_drop (FMDirectoryView *view, + const char *text, + const char *target_uri, + GdkDragAction action, + int x, + int y) +{ + int length; + char *container_uri; + GdkPoint pos; + + if (text == NULL) { + return; + } + + g_return_if_fail (action == GDK_ACTION_COPY); + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + length = strlen (text); + + pos.x = x; + pos.y = y; + fm_directory_view_widget_to_file_operation_position (view, &pos); + + fm_directory_view_new_file_with_initial_contents ( + view, target_uri != NULL ? target_uri : container_uri, + /* Translator: This is the filename used for when you dnd text to a directory */ + _("dropped text.txt"), + text, length, &pos); + + g_free (container_uri); +} + +void +fm_directory_view_handle_raw_drop (FMDirectoryView *view, + const char *raw_data, + int length, + const char *target_uri, + const char *direct_save_uri, + GdkDragAction action, + int x, + int y) +{ + char *container_uri, *filename; + GFile *direct_save_full; + GdkPoint pos; + + if (raw_data == NULL) { + return; + } + + g_return_if_fail (action == GDK_ACTION_COPY); + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + pos.x = x; + pos.y = y; + fm_directory_view_widget_to_file_operation_position (view, &pos); + + filename = NULL; + if (direct_save_uri != NULL) { + direct_save_full = g_file_new_for_uri (direct_save_uri); + filename = g_file_get_basename (direct_save_full); + } + if (filename == NULL) { + /* Translator: This is the filename used for when you dnd raw + * data to a directory, if the source didn't supply a name. + */ + filename = _("dropped data"); + } + + fm_directory_view_new_file_with_initial_contents ( + view, target_uri != NULL ? target_uri : container_uri, + filename, raw_data, length, &pos); + + g_free (container_uri); +} + +gboolean +fm_directory_view_get_active (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + return view->details->active; +} + +static GArray * +real_get_selected_icon_locations (FMDirectoryView *view) +{ + /* By default, just return an empty list. */ + return g_array_new (FALSE, TRUE, sizeof (GdkPoint)); +} + +static void +fm_directory_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + FMDirectoryView *directory_view; + CajaWindowSlotInfo *slot; + CajaWindowInfo *window; + + directory_view = FM_DIRECTORY_VIEW (object); + + switch (prop_id) { + case PROP_WINDOW_SLOT: + g_assert (directory_view->details->slot == NULL); + + slot = CAJA_WINDOW_SLOT_INFO (g_value_get_object (value)); + window = caja_window_slot_info_get_window (slot); + + directory_view->details->slot = slot; + directory_view->details->window = window; + + g_signal_connect_object (directory_view->details->slot, + "active", G_CALLBACK (slot_active), + directory_view, 0); + g_signal_connect_object (directory_view->details->slot, + "inactive", G_CALLBACK (slot_inactive), + directory_view, 0); + + g_signal_connect_object (directory_view->details->window, + "hidden-files-mode-changed", G_CALLBACK (hidden_files_mode_changed), + directory_view, 0); + fm_directory_view_init_show_hidden_files (directory_view); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +gboolean +fm_directory_view_handle_scroll_event (FMDirectoryView *directory_view, + GdkEventScroll *event) +{ + if (event->state & GDK_CONTROL_MASK) { + switch (event->direction) { + case GDK_SCROLL_UP: + /* Zoom In */ + fm_directory_view_bump_zoom_level (directory_view, 1); + return TRUE; + + case GDK_SCROLL_DOWN: + /* Zoom Out */ + fm_directory_view_bump_zoom_level (directory_view, -1); + return TRUE; + + case GDK_SCROLL_LEFT: + case GDK_SCROLL_RIGHT: + break; + + default: + g_assert_not_reached (); + } + } + + return FALSE; +} + +/* handle Shift+Scroll, which will cause a zoom-in/out */ +static gboolean +fm_directory_view_scroll_event (GtkWidget *widget, + GdkEventScroll *event) +{ + FMDirectoryView *directory_view; + + directory_view = FM_DIRECTORY_VIEW (widget); + if (fm_directory_view_handle_scroll_event (directory_view, event)) { + return TRUE; + } + + return GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, event); +} + + +static void +fm_directory_view_parent_set (GtkWidget *widget, + GtkWidget *old_parent) +{ + FMDirectoryView *view; + GtkWidget *parent; + + view = FM_DIRECTORY_VIEW (widget); + + parent = gtk_widget_get_parent (widget); + g_assert (parent == NULL || old_parent == NULL); + + if (GTK_WIDGET_CLASS (parent_class)->parent_set != NULL) { + GTK_WIDGET_CLASS (parent_class)->parent_set (widget, old_parent); + } + + if (parent != NULL) { + g_assert (old_parent == NULL); + + if (view->details->slot == + caja_window_info_get_active_slot (view->details->window)) { + view->details->active = TRUE; + + fm_directory_view_merge_menus (view); + schedule_update_menus (view); + } + } else { + fm_directory_view_unmerge_menus (view); + remove_update_menus_timeout_callback (view); + } +} + +static void +fm_directory_view_class_init (FMDirectoryViewClass *klass) +{ + GtkWidgetClass *widget_class; + GtkScrolledWindowClass *scrolled_window_class; + GtkBindingSet *binding_set; + + widget_class = GTK_WIDGET_CLASS (klass); + scrolled_window_class = GTK_SCROLLED_WINDOW_CLASS (klass); + + G_OBJECT_CLASS (klass)->finalize = fm_directory_view_finalize; + G_OBJECT_CLASS (klass)->set_property = fm_directory_view_set_property; + + GTK_OBJECT_CLASS (klass)->destroy = fm_directory_view_destroy; + + widget_class->scroll_event = fm_directory_view_scroll_event; + widget_class->parent_set = fm_directory_view_parent_set; + + /* Get rid of the strange 3-pixel gap that GtkScrolledWindow + * uses by default. It does us no good. + */ + scrolled_window_class->scrollbar_spacing = 0; + + signals[ADD_FILE] = + g_signal_new ("add_file", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, add_file), + NULL, NULL, + caja_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); + signals[BEGIN_FILE_CHANGES] = + g_signal_new ("begin_file_changes", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, begin_file_changes), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[BEGIN_LOADING] = + g_signal_new ("begin_loading", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, begin_loading), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[CLEAR] = + g_signal_new ("clear", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, clear), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[END_FILE_CHANGES] = + g_signal_new ("end_file_changes", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, end_file_changes), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[FLUSH_ADDED_FILES] = + g_signal_new ("flush_added_files", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, flush_added_files), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[END_LOADING] = + g_signal_new ("end_loading", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, end_loading), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + signals[FILE_CHANGED] = + g_signal_new ("file_changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, file_changed), + NULL, NULL, + caja_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); + signals[LOAD_ERROR] = + g_signal_new ("load_error", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, load_error), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[REMOVE_FILE] = + g_signal_new ("remove_file", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, remove_file), + NULL, NULL, + caja_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); + + klass->accepts_dragged_files = real_accepts_dragged_files; + klass->file_limit_reached = real_file_limit_reached; + klass->file_still_belongs = real_file_still_belongs; + klass->get_emblem_names_to_exclude = real_get_emblem_names_to_exclude; + klass->get_selected_icon_locations = real_get_selected_icon_locations; + klass->is_read_only = real_is_read_only; + klass->load_error = real_load_error; + klass->can_rename_file = can_rename_file; + klass->start_renaming_file = start_renaming_file; + klass->supports_creating_files = real_supports_creating_files; + klass->supports_properties = real_supports_properties; + klass->supports_zooming = real_supports_zooming; + klass->using_manual_layout = real_using_manual_layout; + klass->merge_menus = real_merge_menus; + klass->unmerge_menus = real_unmerge_menus; + klass->update_menus = real_update_menus; + klass->set_is_active = real_set_is_active; + + /* Function pointers that subclasses must override */ + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, add_file); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, bump_zoom_level); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_in); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_out); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, clear); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, file_changed); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_background_widget); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection_for_file_transfer); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_item_count); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, is_empty); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, reset_to_defaults); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, restore_default_zoom_level); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, select_all); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, set_selection); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, invert_selection); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, zoom_to_level); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_zoom_level); + + copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE); + + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_WINDOW_SLOT, + g_param_spec_object ("window-slot", + "Window Slot", + "The parent window slot reference", + CAJA_TYPE_WINDOW_SLOT_INFO, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + signals[TRASH] = + g_signal_new ("trash", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (FMDirectoryViewClass, trash), + g_signal_accumulator_true_handled, NULL, + eel_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0); + signals[DELETE] = + g_signal_new ("delete", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (FMDirectoryViewClass, delete), + g_signal_accumulator_true_handled, NULL, + eel_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0); + + binding_set = gtk_binding_set_by_class (klass); + gtk_binding_entry_add_signal (binding_set, GDK_Delete, 0, + "trash", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, 0, + "trash", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, GDK_SHIFT_MASK, + "delete", 0); + + klass->trash = real_trash; + klass->delete = real_delete; +} diff --git a/src/file-manager/fm-directory-view.h b/src/file-manager/fm-directory-view.h new file mode 100644 index 00000000..f38cbad1 --- /dev/null +++ b/src/file-manager/fm-directory-view.h @@ -0,0 +1,495 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* fm-directory-view.h + * + * Copyright (C) 1999, 2000 Free Software Foundaton + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Ettore Perazzoli + * Darin Adler <[email protected]> + * John Sullivan <[email protected]> + * Pavel Cisler <[email protected]> + */ + +#ifndef FM_DIRECTORY_VIEW_H +#define FM_DIRECTORY_VIEW_H + +#include <gtk/gtk.h> +#include <eel/eel-background.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-icon-container.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-view.h> +#include <libcaja-private/caja-window-info.h> +#include <libcaja-private/caja-window-slot-info.h> +#include <gio/gio.h> + +typedef struct FMDirectoryView FMDirectoryView; +typedef struct FMDirectoryViewClass FMDirectoryViewClass; + +#define FM_TYPE_DIRECTORY_VIEW fm_directory_view_get_type() +#define FM_DIRECTORY_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_DIRECTORY_VIEW, FMDirectoryView)) +#define FM_DIRECTORY_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_DIRECTORY_VIEW, FMDirectoryViewClass)) +#define FM_IS_DIRECTORY_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_DIRECTORY_VIEW)) +#define FM_IS_DIRECTORY_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_DIRECTORY_VIEW)) +#define FM_DIRECTORY_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_DIRECTORY_VIEW, FMDirectoryViewClass)) + +typedef struct FMDirectoryViewDetails FMDirectoryViewDetails; + +struct FMDirectoryView +{ + GtkScrolledWindow parent; + FMDirectoryViewDetails *details; +}; + +struct FMDirectoryViewClass +{ + GtkScrolledWindowClass parent_class; + + /* The 'clear' signal is emitted to empty the view of its contents. + * It must be replaced by each subclass. + */ + void (* clear) (FMDirectoryView *view); + + /* The 'begin_file_changes' signal is emitted before a set of files + * are added to the view. It can be replaced by a subclass to do any + * necessary preparation for a set of new files. The default + * implementation does nothing. + */ + void (* begin_file_changes) (FMDirectoryView *view); + + /* The 'add_file' signal is emitted to add one file to the view. + * It must be replaced by each subclass. + */ + void (* add_file) (FMDirectoryView *view, + CajaFile *file, + CajaDirectory *directory); + void (* remove_file) (FMDirectoryView *view, + CajaFile *file, + CajaDirectory *directory); + + /* The 'file_changed' signal is emitted to signal a change in a file, + * including the file being removed. + * It must be replaced by each subclass. + */ + void (* file_changed) (FMDirectoryView *view, + CajaFile *file, + CajaDirectory *directory); + + /* The 'end_file_changes' signal is emitted after a set of files + * are added to the view. It can be replaced by a subclass to do any + * necessary cleanup (typically, cleanup for code in begin_file_changes). + * The default implementation does nothing. + */ + void (* end_file_changes) (FMDirectoryView *view); + + void (* flush_added_files) (FMDirectoryView *view); + + /* The 'begin_loading' signal is emitted before any of the contents + * of a directory are added to the view. It can be replaced by a + * subclass to do any necessary preparation to start dealing with a + * new directory. The default implementation does nothing. + */ + void (* begin_loading) (FMDirectoryView *view); + + /* The 'end_loading' signal is emitted after all of the contents + * of a directory are added to the view. It can be replaced by a + * subclass to do any necessary clean-up. The default implementation + * does nothing. + * + * If all_files_seen is true, the handler may assume that + * no load error ocurred, and all files of the underlying + * directory were loaded. + * + * Otherwise, end_loading was emitted due to cancellation, + * which usually means that not all files are available. + */ + void (* end_loading) (FMDirectoryView *view, + gboolean all_files_seen); + + /* The 'load_error' signal is emitted when the directory model + * reports an error in the process of monitoring the directory's + * contents. The load error indicates that the process of + * loading the contents has ended, but the directory is still + * being monitored. The default implementation handles common + * load failures like ACCESS_DENIED. + */ + void (* load_error) (FMDirectoryView *view, + GError *error); + + /* Function pointers that don't have corresponding signals */ + + /* reset_to_defaults is a function pointer that subclasses must + * override to set sort order, zoom level, etc to match default + * values. + */ + void (* reset_to_defaults) (FMDirectoryView *view); + + /* get_selection is not a signal; it is just a function pointer for + * subclasses to replace (override). Subclasses must replace it + * with a function that returns a newly-allocated GList of + * CajaFile pointers. + */ + GList * (* get_selection) (FMDirectoryView *view); + + /* get_selection_for_file_transfer is a function pointer for + * subclasses to replace (override). Subclasses must replace it + * with a function that returns a newly-allocated GList of + * CajaFile pointers. The difference from get_selection is + * that any files in the selection that also has a parent folder + * in the selection is not included. + */ + GList * (* get_selection_for_file_transfer)(FMDirectoryView *view); + + /* select_all is a function pointer that subclasses must override to + * select all of the items in the view */ + void (* select_all) (FMDirectoryView *view); + + /* set_selection is a function pointer that subclasses must + * override to select the specified items (and unselect all + * others). The argument is a list of CajaFiles. */ + + void (* set_selection) (FMDirectoryView *view, + GList *selection); + + /* invert_selection is a function pointer that subclasses must + * override to invert selection. */ + + void (* invert_selection) (FMDirectoryView *view); + + /* Return an array of locations of selected icons in their view. */ + GArray * (* get_selected_icon_locations) (FMDirectoryView *view); + + guint (* get_item_count) (FMDirectoryView *view); + + /* bump_zoom_level is a function pointer that subclasses must override + * to change the zoom level of an object. */ + void (* bump_zoom_level) (FMDirectoryView *view, + int zoom_increment); + + /* zoom_to_level is a function pointer that subclasses must override + * to set the zoom level of an object to the specified level. */ + void (* zoom_to_level) (FMDirectoryView *view, + CajaZoomLevel level); + + CajaZoomLevel (* get_zoom_level) (FMDirectoryView *view); + + /* restore_default_zoom_level is a function pointer that subclasses must override + * to restore the zoom level of an object to a default setting. */ + void (* restore_default_zoom_level) (FMDirectoryView *view); + + /* can_zoom_in is a function pointer that subclasses must override to + * return whether the view is at maximum size (furthest-in zoom level) */ + gboolean (* can_zoom_in) (FMDirectoryView *view); + + /* can_zoom_out is a function pointer that subclasses must override to + * return whether the view is at minimum size (furthest-out zoom level) */ + gboolean (* can_zoom_out) (FMDirectoryView *view); + + /* reveal_selection is a function pointer that subclasses may + * override to make sure the selected items are sufficiently + * apparent to the user (e.g., scrolled into view). By default, + * this does nothing. + */ + void (* reveal_selection) (FMDirectoryView *view); + + /* get_background is a function pointer that subclasses must + * override to return the EelBackground for this view. + */ + GtkWidget * (* get_background_widget) (FMDirectoryView *view); + + /* merge_menus is a function pointer that subclasses can override to + * add their own menu items to the window's menu bar. + * If overridden, subclasses must call parent class's function. + */ + void (* merge_menus) (FMDirectoryView *view); + void (* unmerge_menus) (FMDirectoryView *view); + + /* update_menus is a function pointer that subclasses can override to + * update the sensitivity or wording of menu items in the menu bar. + * It is called (at least) whenever the selection changes. If overridden, + * subclasses must call parent class's function. + */ + void (* update_menus) (FMDirectoryView *view); + + /* sort_files is a function pointer that subclasses can override + * to provide a sorting order to determine which files should be + * presented when only a partial list is provided. + */ + int (* compare_files) (FMDirectoryView *view, + CajaFile *a, + CajaFile *b); + + /* get_emblem_names_to_exclude is a function pointer that subclasses + * may override to specify a set of emblem names that should not + * be displayed with each file. By default, all emblems returned by + * CajaFile are displayed. + */ + char ** (* get_emblem_names_to_exclude) (FMDirectoryView *view); + + /* file_limit_reached is a function pointer that subclasses may + * override to control what happens when a directory is loaded + * that has more files than Caja can handle. The default + * implmentation puts up a dialog box that is appropriate to + * display when the user explicitly tried to visit a location that + * they would think of as a normal directory. + */ + void (* file_limit_reached) (FMDirectoryView *view); + + /* supports_properties is a function pointer that subclasses may + * override to control whether the "Show Properties" menu item + * should be enabled for selected items. The default implementation + * returns TRUE. + */ + gboolean (* supports_properties) (FMDirectoryView *view); + + /* supports_zooming is a function pointer that subclasses may + * override to control whether or not the zooming control and + * menu items should be enabled. The default implementation + * returns TRUE. + */ + gboolean (* supports_zooming) (FMDirectoryView *view); + + /* using_manual_layout is a function pointer that subclasses may + * override to control whether or not items can be freely positioned + * on the user-visible area. + * Note that this value is not guaranteed to be constant within the + * view's lifecycle. */ + gboolean (* using_manual_layout) (FMDirectoryView *view); + + /* is_read_only is a function pointer that subclasses may + * override to control whether or not the user is allowed to + * change the contents of the currently viewed directory. The + * default implementation checks the permissions of the + * directory. + */ + gboolean (* is_read_only) (FMDirectoryView *view); + + /* is_empty is a function pointer that subclasses must + * override to report whether the view contains any items. + */ + gboolean (* is_empty) (FMDirectoryView *view); + + /* supports_creating_files is a function pointer that subclasses may + * override to control whether or not new items can be created. + * be accepted. The default implementation checks whether the + * user has write permissions for the viewed directory, and whether + * the viewed directory is in the trash. + */ + gboolean (* supports_creating_files) (FMDirectoryView *view); + + /* accepts_dragged_files is a function pointer that subclasses may + * override to control whether or not files can be dropped in this + * location. The default implementation returns TRUE. + */ + gboolean (* accepts_dragged_files) (FMDirectoryView *view); + + gboolean (* can_rename_file) (FMDirectoryView *view, + CajaFile *file); + /* select_all specifies whether the whole filename should be selected + * or only its basename (i.e. everything except the extension) + * */ + void (* start_renaming_file) (FMDirectoryView *view, + CajaFile *file, + gboolean select_all); + + gboolean (* file_still_belongs) (FMDirectoryView *view, + CajaFile *file, + CajaDirectory *directory); + + /* convert *point from widget's coordinate system to a coordinate + * system used for specifying file operation positions, which is view-specific. + * + * This is used by the the icon view, which converts the screen position to a zoom + * level-independent coordinate system. + */ + void (* widget_to_file_operation_position) (FMDirectoryView *view, + GdkPoint *position); + + /* Preference change callbacks, overriden by icon and list views. + * Icon and list views respond by synchronizing to the new preference + * values and forcing an update if appropriate. + */ + void (* text_attribute_names_changed) (FMDirectoryView *view); + void (* embedded_text_policy_changed) (FMDirectoryView *view); + void (* image_display_policy_changed) (FMDirectoryView *view); + void (* click_policy_changed) (FMDirectoryView *view); + void (* sort_directories_first_changed) (FMDirectoryView *view); + + void (* emblems_changed) (FMDirectoryView *view); + + void (* set_is_active) (FMDirectoryView *view, + gboolean is_active); + + /* Signals used only for keybindings */ + gboolean (* trash) (FMDirectoryView *view); + gboolean (* delete) (FMDirectoryView *view); +}; + +/* GObject support */ +GType fm_directory_view_get_type (void); + +/* Functions callable from the user interface and elsewhere. */ +CajaWindowInfo *fm_directory_view_get_caja_window (FMDirectoryView *view); +CajaWindowSlotInfo *fm_directory_view_get_caja_window_slot (FMDirectoryView *view); +char * fm_directory_view_get_uri (FMDirectoryView *view); +char * fm_directory_view_get_backing_uri (FMDirectoryView *view); +gboolean fm_directory_view_can_accept_item (CajaFile *target_item, + const char *item_uri, + FMDirectoryView *view); +void fm_directory_view_display_selection_info (FMDirectoryView *view); +GList * fm_directory_view_get_selection (FMDirectoryView *view); +GList * fm_directory_view_get_selection_for_file_transfer (FMDirectoryView *view); +void fm_directory_view_invert_selection (FMDirectoryView *view); +void fm_directory_view_stop (FMDirectoryView *view); +guint fm_directory_view_get_item_count (FMDirectoryView *view); +gboolean fm_directory_view_can_zoom_in (FMDirectoryView *view); +gboolean fm_directory_view_can_zoom_out (FMDirectoryView *view); +GtkWidget * fm_directory_view_get_background_widget (FMDirectoryView *view); +void fm_directory_view_bump_zoom_level (FMDirectoryView *view, + int zoom_increment); +void fm_directory_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level); +CajaZoomLevel fm_directory_view_get_zoom_level (FMDirectoryView *view); +void fm_directory_view_restore_default_zoom_level (FMDirectoryView *view); +void fm_directory_view_reset_to_defaults (FMDirectoryView *view); +void fm_directory_view_select_all (FMDirectoryView *view); +void fm_directory_view_set_selection (FMDirectoryView *view, + GList *selection); +GArray * fm_directory_view_get_selected_icon_locations (FMDirectoryView *view); +void fm_directory_view_reveal_selection (FMDirectoryView *view); +gboolean fm_directory_view_is_empty (FMDirectoryView *view); +gboolean fm_directory_view_is_read_only (FMDirectoryView *view); +gboolean fm_directory_view_supports_creating_files (FMDirectoryView *view); +gboolean fm_directory_view_accepts_dragged_files (FMDirectoryView *view); +gboolean fm_directory_view_supports_properties (FMDirectoryView *view); +gboolean fm_directory_view_supports_zooming (FMDirectoryView *view); +gboolean fm_directory_view_using_manual_layout (FMDirectoryView *view); +void fm_directory_view_move_copy_items (const GList *item_uris, + GArray *relative_item_points, + const char *target_uri, + int copy_action, + int x, + int y, + FMDirectoryView *view); +GdkAtom fm_directory_view_get_copied_files_atom (FMDirectoryView *view); +gboolean fm_directory_view_get_active (FMDirectoryView *view); + +/* Wrappers for signal emitters. These are normally called + * only by FMDirectoryView itself. They have corresponding signals + * that observers might want to connect with. + */ +void fm_directory_view_clear (FMDirectoryView *view); +void fm_directory_view_begin_loading (FMDirectoryView *view); +void fm_directory_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen); + +gboolean fm_directory_view_get_loading (FMDirectoryView *view); + +/* Hooks for subclasses to call. These are normally called only by + * FMDirectoryView and its subclasses + */ +void fm_directory_view_activate_files (FMDirectoryView *view, + GList *files, + CajaWindowOpenMode mode, + CajaWindowOpenFlags flags, + gboolean confirm_multiple); +void fm_directory_view_activate_file (FMDirectoryView *view, + CajaFile *file, + CajaWindowOpenMode mode, + CajaWindowOpenFlags flags); +void fm_directory_view_start_batching_selection_changes (FMDirectoryView *view); +void fm_directory_view_stop_batching_selection_changes (FMDirectoryView *view); +void fm_directory_view_queue_file_change (FMDirectoryView *view, + CajaFile *file); +void fm_directory_view_notify_selection_changed (FMDirectoryView *view); +GtkUIManager * fm_directory_view_get_ui_manager (FMDirectoryView *view); +char ** fm_directory_view_get_emblem_names_to_exclude (FMDirectoryView *view); +CajaDirectory *fm_directory_view_get_model (FMDirectoryView *view); +GtkWindow *fm_directory_view_get_containing_window (FMDirectoryView *view); +CajaFile *fm_directory_view_get_directory_as_file (FMDirectoryView *view); +EelBackground * fm_directory_view_get_background (FMDirectoryView *view); +gboolean fm_directory_view_get_allow_moves (FMDirectoryView *view); +void fm_directory_view_pop_up_background_context_menu (FMDirectoryView *view, + GdkEventButton *event); +void fm_directory_view_pop_up_selection_context_menu (FMDirectoryView *view, + GdkEventButton *event); +void fm_directory_view_pop_up_location_context_menu (FMDirectoryView *view, + GdkEventButton *event, + const char *location); +void fm_directory_view_send_selection_change (FMDirectoryView *view); +gboolean fm_directory_view_should_show_file (FMDirectoryView *view, + CajaFile *file); +gboolean fm_directory_view_should_sort_directories_first (FMDirectoryView *view); +void fm_directory_view_update_menus (FMDirectoryView *view); +void fm_directory_view_new_folder (FMDirectoryView *view); +void fm_directory_view_new_file (FMDirectoryView *view, + const char *parent_uri, + CajaFile *source); +void fm_directory_view_ignore_hidden_file_preferences (FMDirectoryView *view); +void fm_directory_view_set_show_foreign (FMDirectoryView *view, + gboolean show_foreign); +void fm_directory_view_init_view_iface (CajaViewIface *iface); +gboolean fm_directory_view_handle_scroll_event (FMDirectoryView *view, + GdkEventScroll *event); +void fm_directory_view_handle_netscape_url_drop (FMDirectoryView *view, + const char *encoded_url, + const char *target_uri, + GdkDragAction action, + int x, + int y); +void fm_directory_view_handle_uri_list_drop (FMDirectoryView *view, + const char *item_uris, + const char *target_uri, + GdkDragAction action, + int x, + int y); +void fm_directory_view_handle_text_drop (FMDirectoryView *view, + const char *text, + const char *target_uri, + GdkDragAction action, + int x, + int y); +void fm_directory_view_handle_raw_drop (FMDirectoryView *view, + const char *raw_data, + int length, + const char *target_uri, + const char *direct_save_uri, + GdkDragAction action, + int x, + int y); +void fm_directory_view_freeze_updates (FMDirectoryView *view); +void fm_directory_view_unfreeze_updates (FMDirectoryView *view); +void fm_directory_view_add_subdirectory (FMDirectoryView *view, + CajaDirectory*directory); +void fm_directory_view_remove_subdirectory (FMDirectoryView *view, + CajaDirectory*directory); + +gboolean fm_directory_view_is_editable (FMDirectoryView *view); +void fm_directory_view_set_initiated_unmount (FMDirectoryView *view, + gboolean inititated_unmount); + +/* operations affecting two directory views */ +void fm_directory_view_move_copy_items_between_views (FMDirectoryView *source, FMDirectoryView *target, gboolean copy); + +#endif /* FM_DIRECTORY_VIEW_H */ diff --git a/src/file-manager/fm-ditem-page.c b/src/file-manager/fm-ditem-page.c new file mode 100644 index 00000000..af88b753 --- /dev/null +++ b/src/file-manager/fm-ditem-page.c @@ -0,0 +1,563 @@ +/* + * fm-ditem-page.c: Desktop item editing support + * + * Copyright (C) 2004 James Willcox + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Authors: James Willcox <[email protected]> + * + */ + +#include <config.h> +#include "fm-ditem-page.h" + +#include <string.h> + +#include <eel/eel-glib-extensions.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <libcaja-extension/caja-extension-types.h> +#include <libcaja-extension/caja-file-info.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-file-attributes.h> + +#define MAIN_GROUP "Desktop Entry" + +typedef struct ItemEntry +{ + const char *field; + const char *description; + char *current_value; + gboolean localized; + gboolean filename; +} ItemEntry; + +enum +{ + TARGET_URI_LIST +}; + +static const GtkTargetEntry target_table[] = +{ + { "text/uri-list", 0, TARGET_URI_LIST } +}; + +static gboolean +_g_key_file_load_from_gfile (GKeyFile *key_file, + GFile *file, + GKeyFileFlags flags, + GError **error) +{ + char *data; + gsize len; + gboolean res; + + if (!g_file_load_contents (file, NULL, &data, &len, NULL, error)) + { + return FALSE; + } + + res = g_key_file_load_from_data (key_file, data, len, flags, error); + + g_free (data); + + return res; +} + +static gboolean +_g_key_file_save_to_uri (GKeyFile *key_file, + const char *uri, + GError **error) +{ + GFile *file; + char *data; + gsize len; + + data = g_key_file_to_data (key_file, &len, error); + if (data == NULL) + { + return FALSE; + } + file = g_file_new_for_uri (uri); + if (!g_file_replace_contents (file, + data, len, + NULL, FALSE, + G_FILE_CREATE_NONE, + NULL, NULL, error)) + { + g_object_unref (file); + g_free (data); + return FALSE; + } + g_object_unref (file); + g_free (data); + return TRUE; +} + +static GKeyFile * +_g_key_file_new_from_file (GFile *file, + GKeyFileFlags flags, + GError **error) +{ + GKeyFile *key_file; + + key_file = g_key_file_new (); + if (!_g_key_file_load_from_gfile (key_file, file, flags, error)) + { + g_key_file_free (key_file); + key_file = NULL; + } + return key_file; +} + +static GKeyFile * +_g_key_file_new_from_uri (const char *uri, + GKeyFileFlags flags, + GError **error) +{ + GKeyFile *key_file; + GFile *file; + + file = g_file_new_for_uri (uri); + key_file = _g_key_file_new_from_file (file, flags, error); + g_object_unref (file); + return key_file; +} + +static ItemEntry * +item_entry_new (const char *field, + const char *description, + gboolean localized, + gboolean filename) +{ + ItemEntry *entry; + + entry = g_new0 (ItemEntry, 1); + entry->field = field; + entry->description = description; + entry->localized = localized; + entry->filename = filename; + + return entry; +} + +static void +item_entry_free (ItemEntry *entry) +{ + g_free (entry->current_value); + g_free (entry); +} + +static void +fm_ditem_page_url_drag_data_received (GtkWidget *widget, GdkDragContext *context, + int x, int y, + GtkSelectionData *selection_data, + guint info, guint time, + GtkEntry *entry) +{ + char **uris; + gboolean exactly_one; + char *path; + + uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); + exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + + if (!exactly_one) + { + g_strfreev (uris); + return; + } + + path = g_filename_from_uri (uris[0], NULL, NULL); + if (path != NULL) + { + gtk_entry_set_text (entry, path); + g_free (path); + } + else + { + gtk_entry_set_text (entry, uris[0]); + } + + g_strfreev (uris); +} + +static void +fm_ditem_page_exec_drag_data_received (GtkWidget *widget, GdkDragContext *context, + int x, int y, + GtkSelectionData *selection_data, + guint info, guint time, + GtkEntry *entry) +{ + char **uris; + gboolean exactly_one; + CajaFile *file; + GKeyFile *key_file; + char *uri, *type, *exec; + + uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); + exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + + if (!exactly_one) + { + g_strfreev (uris); + return; + } + + file = caja_file_get_by_uri (uris[0]); + + g_return_if_fail (file != NULL); + + uri = caja_file_get_uri (file); + if (caja_file_is_mime_type (file, "application/x-desktop")) + { + key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL); + if (key_file != NULL) + { + type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL); + if (type != NULL && strcmp (type, "Application") == 0) + { + exec = g_key_file_get_string (key_file, MAIN_GROUP, "Exec", NULL); + if (exec != NULL) + { + g_free (uri); + uri = exec; + } + } + g_free (type); + g_key_file_free (key_file); + } + } + gtk_entry_set_text (entry, + uri?uri:""); + gtk_widget_grab_focus (GTK_WIDGET (entry)); + + g_free (uri); + + caja_file_unref (file); + + g_strfreev (uris); +} + +static void +save_entry (GtkEntry *entry, GKeyFile *key_file, const char *uri) +{ + GError *error; + ItemEntry *item_entry; + const char *val; + gchar **languages; + + item_entry = g_object_get_data (G_OBJECT (entry), "item_entry"); + val = gtk_entry_get_text (entry); + + if (strcmp (val, item_entry->current_value) == 0) + { + return; /* No actual change, don't update file */ + } + + g_free (item_entry->current_value); + item_entry->current_value = g_strdup (val); + + if (item_entry->localized) + { + languages = (gchar **) g_get_language_names (); + g_key_file_set_locale_string (key_file, MAIN_GROUP, item_entry->field, languages[0], val); + } + else + { + g_key_file_set_string (key_file, MAIN_GROUP, item_entry->field, val); + } + + error = NULL; + + if (!_g_key_file_save_to_uri (key_file, uri, &error)) + { + g_warning ("%s", error->message); + g_error_free (error); + } +} + +static void +entry_activate_cb (GtkWidget *entry, + GtkWidget *container) +{ + const char *uri; + GKeyFile *key_file; + + uri = g_object_get_data (G_OBJECT (container), "uri"); + key_file = g_object_get_data (G_OBJECT (container), "keyfile"); + save_entry (GTK_ENTRY (entry), key_file, uri); +} + +static gboolean +entry_focus_out_cb (GtkWidget *entry, + GdkEventFocus *event, + GtkWidget *container) +{ + const char *uri; + GKeyFile *key_file; + + uri = g_object_get_data (G_OBJECT (container), "uri"); + key_file = g_object_get_data (G_OBJECT (container), "keyfile"); + save_entry (GTK_ENTRY (entry), key_file, uri); + return FALSE; +} + +static GtkWidget * +build_table (GtkWidget *container, + GKeyFile *key_file, + GtkSizeGroup *label_size_group, + GList *entries) +{ + GtkWidget *table; + GtkWidget *label; + GtkWidget *entry; + GList *l; + char *val; + int i; + + table = gtk_table_new (g_list_length (entries) + 1, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + i = 0; + + for (l = entries; l; l = l->next) + { + ItemEntry *item_entry = (ItemEntry *)l->data; + char *label_text; + + label_text = g_strdup_printf ("%s:", item_entry->description); + label = gtk_label_new (label_text); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + g_free (label_text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_size_group_add_widget (label_size_group, label); + + entry = gtk_entry_new (); + + if (item_entry->localized) + { + val = g_key_file_get_locale_string (key_file, + MAIN_GROUP, + item_entry->field, + NULL, NULL); + } + else + { + val = g_key_file_get_string (key_file, + MAIN_GROUP, + item_entry->field, + NULL); + } + + item_entry->current_value = g_strdup (val?val:""); + gtk_entry_set_text (GTK_ENTRY (entry), item_entry->current_value); + g_free (val); + + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, i, i+1, GTK_FILL, GTK_FILL, + 0, 0); + gtk_table_attach (GTK_TABLE (table), entry, + 1, 2, i, i+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, + 0, 0); + g_signal_connect (entry, "activate", + G_CALLBACK (entry_activate_cb), + container); + g_signal_connect (entry, "focus_out_event", + G_CALLBACK (entry_focus_out_cb), + container); + + g_object_set_data_full (G_OBJECT (entry), "item_entry", item_entry, + (GDestroyNotify)item_entry_free); + + if (item_entry->filename) + { + gtk_drag_dest_set (GTK_WIDGET (entry), + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (entry, "drag_data_received", + G_CALLBACK (fm_ditem_page_url_drag_data_received), + entry); + } + else if (strcmp (item_entry->field, "Exec") == 0) + { + gtk_drag_dest_set (GTK_WIDGET (entry), + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (entry, "drag_data_received", + G_CALLBACK (fm_ditem_page_exec_drag_data_received), + entry); + } + + i++; + } + + /* append dummy row */ + label = gtk_label_new (""); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, i, i+1, GTK_FILL, GTK_FILL, + 0, 0); + gtk_size_group_add_widget (label_size_group, label); + + + gtk_widget_show_all (table); + return table; +} + +static void +create_page (GKeyFile *key_file, GtkWidget *box) +{ + GtkWidget *table; + GList *entries; + GtkSizeGroup *label_size_group; + char *type; + + entries = NULL; + + type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL); + + if (g_strcmp0 (type, "Link") == 0) + { + entries = g_list_prepend (entries, + item_entry_new ("Comment", + _("Comment"), TRUE, FALSE)); + entries = g_list_prepend (entries, + item_entry_new ("URL", + _("URL"), FALSE, TRUE)); + entries = g_list_prepend (entries, + item_entry_new ("GenericName", + _("Description"), TRUE, FALSE)); + } + else if (g_strcmp0 (type, "Application") == 0) + { + entries = g_list_prepend (entries, + item_entry_new ("Comment", + _("Comment"), TRUE, FALSE)); + entries = g_list_prepend (entries, + item_entry_new ("Exec", + _("Command"), FALSE, FALSE)); + entries = g_list_prepend (entries, + item_entry_new ("GenericName", + _("Description"), TRUE, FALSE)); + } + else + { + /* we only handle launchers and links */ + + /* ensure that we build an empty table with a dummy row at the end */ + goto build_table; + } + g_free (type); + +build_table: + label_size_group = g_object_get_data (G_OBJECT (box), "label-size-group"); + + table = build_table (box, key_file, label_size_group, entries); + g_list_free (entries); + + gtk_box_pack_start (GTK_BOX (box), table, FALSE, TRUE, 0); + gtk_widget_show_all (GTK_WIDGET (box)); +} + + +static void +ditem_read_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GKeyFile *key_file; + GtkWidget *box; + gsize file_size; + char *file_contents; + + box = GTK_WIDGET (user_data); + + if (g_file_load_contents_finish (G_FILE (source_object), + res, + &file_contents, &file_size, + NULL, NULL)) + { + key_file = g_key_file_new (); + g_object_set_data_full (G_OBJECT (box), "keyfile", key_file, (GDestroyNotify)g_key_file_free); + if (g_key_file_load_from_data (key_file, file_contents, file_size, 0, NULL)) + { + create_page (key_file, box); + } + g_free (file_contents); + + } + g_object_unref (box); +} + +static void +fm_ditem_page_create_begin (const char *uri, + GtkWidget *box) +{ + GFile *location; + + location = g_file_new_for_uri (uri); + g_object_set_data_full (G_OBJECT (box), "uri", g_strdup (uri), g_free); + g_file_load_contents_async (location, NULL, ditem_read_cb, g_object_ref (box)); + g_object_unref (location); +} + +GtkWidget * +fm_ditem_page_make_box (GtkSizeGroup *label_size_group, + GList *files) +{ + CajaFileInfo *info; + char *uri; + GtkWidget *box; + + g_assert (fm_ditem_page_should_show (files)); + + box = gtk_vbox_new (FALSE, 6); + g_object_set_data_full (G_OBJECT (box), "label-size-group", + label_size_group, (GDestroyNotify) g_object_unref); + + info = CAJA_FILE_INFO (files->data); + + uri = caja_file_info_get_uri (info); + fm_ditem_page_create_begin (uri, box); + g_free (uri); + + return box; +} + +gboolean +fm_ditem_page_should_show (GList *files) +{ + CajaFileInfo *info; + + if (!files || files->next) + { + return FALSE; + } + + info = CAJA_FILE_INFO (files->data); + + if (!caja_file_info_is_mime_type (info, "application/x-desktop")) + { + return FALSE; + } + + return TRUE; +} + diff --git a/src/file-manager/fm-ditem-page.h b/src/file-manager/fm-ditem-page.h new file mode 100644 index 00000000..bf55d002 --- /dev/null +++ b/src/file-manager/fm-ditem-page.h @@ -0,0 +1,51 @@ +/* + * fm-ditem-page.h - A property page for desktop items + * + * Copyright (C) 2004 James Willcox + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Authors: James Willcox <[email protected]> + * + */ + +#ifndef FM_DITEM_PAGE_H +#define FM_DITEM_PAGE_H + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +#ifdef __cplusplus +extern "C" { +#endif + + /* This is a mis-nomer. Launcher editables initially were displayed on separate + * a property notebook page, which implemented the CajaPropertyPageProvider + * interface. + * + * Nowadays, they are displayed on the "Basic" page, so just the setup + * routines are left. + */ + + GtkWidget *fm_ditem_page_make_box (GtkSizeGroup *label_size_group, + GList *files); + gboolean fm_ditem_page_should_show (GList *files); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/file-manager/fm-empty-view.c b/src/file-manager/fm-empty-view.c new file mode 100644 index 00000000..d85c702c --- /dev/null +++ b/src/file-manager/fm-empty-view.c @@ -0,0 +1,412 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-empty-view.c - implementation of empty view of directory. + + Copyright (C) 2006 Free Software Foundation, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Christian Neumair <[email protected]> +*/ + +#include <config.h> +#include "fm-empty-view.h" + +#include <string.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-view.h> +#include <libcaja-private/caja-view-factory.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-vfs-extensions.h> + +struct FMEmptyViewDetails +{ + int number_of_files; +}; + +static GList *fm_empty_view_get_selection (FMDirectoryView *view); +static GList *fm_empty_view_get_selection_for_file_transfer (FMDirectoryView *view); +static void fm_empty_view_scroll_to_file (CajaView *view, + const char *uri); +static void fm_empty_view_iface_init (CajaViewIface *iface); + +G_DEFINE_TYPE_WITH_CODE (FMEmptyView, fm_empty_view, FM_TYPE_DIRECTORY_VIEW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, + fm_empty_view_iface_init)); + +/* for EEL_CALL_PARENT */ +#define parent_class fm_empty_view_parent_class + +static void +fm_empty_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + static GTimer *timer = NULL; + static gdouble cumu = 0, elaps; + FM_EMPTY_VIEW (view)->details->number_of_files++; + GdkPixbuf *icon; + + if (!timer) timer = g_timer_new (); + + g_timer_start (timer); + icon = caja_file_get_icon_pixbuf (file, caja_get_icon_size_for_zoom_level (CAJA_ZOOM_LEVEL_STANDARD), TRUE, 0); + + elaps = g_timer_elapsed (timer, NULL); + g_timer_stop (timer); + + g_object_unref (icon); + + cumu += elaps; + g_message ("entire loading: %.3f, cumulative %.3f", elaps, cumu); +} + + +static void +fm_empty_view_begin_loading (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_clear (FMDirectoryView *view) +{ +} + + +static void +fm_empty_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +} + +static GtkWidget * +fm_empty_view_get_background_widget (FMDirectoryView *view) +{ + return GTK_WIDGET (view); +} + +static GList * +fm_empty_view_get_selection (FMDirectoryView *view) +{ + return NULL; +} + + +static GList * +fm_empty_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ + return NULL; +} + +static guint +fm_empty_view_get_item_count (FMDirectoryView *view) +{ + return FM_EMPTY_VIEW (view)->details->number_of_files; +} + +static gboolean +fm_empty_view_is_empty (FMDirectoryView *view) +{ + return FM_EMPTY_VIEW (view)->details->number_of_files == 0; +} + +static void +fm_empty_view_end_file_changes (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FM_EMPTY_VIEW (view)->details->number_of_files--; + g_assert (FM_EMPTY_VIEW (view)->details->number_of_files >= 0); +} + +static void +fm_empty_view_set_selection (FMDirectoryView *view, GList *selection) +{ + fm_directory_view_notify_selection_changed (view); +} + +static void +fm_empty_view_select_all (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_reveal_selection (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_merge_menus (FMDirectoryView *view) +{ + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); +} + +static void +fm_empty_view_update_menus (FMDirectoryView *view) +{ + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); +} + +/* Reset sort criteria and zoom level to match defaults */ +static void +fm_empty_view_reset_to_defaults (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ +} + +static CajaZoomLevel +fm_empty_view_get_zoom_level (FMDirectoryView *view) +{ + return CAJA_ZOOM_LEVEL_STANDARD; +} + +static void +fm_empty_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ +} + +static void +fm_empty_view_restore_default_zoom_level (FMDirectoryView *view) +{ +} + +static gboolean +fm_empty_view_can_zoom_in (FMDirectoryView *view) +{ + return FALSE; +} + +static gboolean +fm_empty_view_can_zoom_out (FMDirectoryView *view) +{ + return FALSE; +} + +static void +fm_empty_view_start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ +} + +static void +fm_empty_view_click_policy_changed (FMDirectoryView *directory_view) +{ +} + + +static int +fm_empty_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2) +{ + if (file1 < file2) + { + return -1; + } + + if (file1 > file2) + { + return +1; + } + + return 0; +} + +static gboolean +fm_empty_view_using_manual_layout (FMDirectoryView *view) +{ + return FALSE; +} + +static void +fm_empty_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ +} + +static void +fm_empty_view_finalize (GObject *object) +{ + FMEmptyView *empty_view; + + empty_view = FM_EMPTY_VIEW (object); + g_free (empty_view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_empty_view_emblems_changed (FMDirectoryView *directory_view) +{ +} + +static char * +fm_empty_view_get_first_visible_file (CajaView *view) +{ + return NULL; +} + +static void +fm_empty_view_scroll_to_file (CajaView *view, + const char *uri) +{ +} + +static void +fm_empty_view_grab_focus (CajaView *view) +{ + gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static void +fm_empty_view_sort_directories_first_changed (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_class_init (FMEmptyViewClass *class) +{ + FMDirectoryViewClass *fm_directory_view_class; + + fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class); + + G_OBJECT_CLASS (class)->finalize = fm_empty_view_finalize; + + fm_directory_view_class->add_file = fm_empty_view_add_file; + fm_directory_view_class->begin_loading = fm_empty_view_begin_loading; + fm_directory_view_class->bump_zoom_level = fm_empty_view_bump_zoom_level; + fm_directory_view_class->can_zoom_in = fm_empty_view_can_zoom_in; + fm_directory_view_class->can_zoom_out = fm_empty_view_can_zoom_out; + fm_directory_view_class->click_policy_changed = fm_empty_view_click_policy_changed; + fm_directory_view_class->clear = fm_empty_view_clear; + fm_directory_view_class->file_changed = fm_empty_view_file_changed; + fm_directory_view_class->get_background_widget = fm_empty_view_get_background_widget; + fm_directory_view_class->get_selection = fm_empty_view_get_selection; + fm_directory_view_class->get_selection_for_file_transfer = fm_empty_view_get_selection_for_file_transfer; + fm_directory_view_class->get_item_count = fm_empty_view_get_item_count; + fm_directory_view_class->is_empty = fm_empty_view_is_empty; + fm_directory_view_class->remove_file = fm_empty_view_remove_file; + fm_directory_view_class->merge_menus = fm_empty_view_merge_menus; + fm_directory_view_class->update_menus = fm_empty_view_update_menus; + fm_directory_view_class->reset_to_defaults = fm_empty_view_reset_to_defaults; + fm_directory_view_class->restore_default_zoom_level = fm_empty_view_restore_default_zoom_level; + fm_directory_view_class->reveal_selection = fm_empty_view_reveal_selection; + fm_directory_view_class->select_all = fm_empty_view_select_all; + fm_directory_view_class->set_selection = fm_empty_view_set_selection; + fm_directory_view_class->compare_files = fm_empty_view_compare_files; + fm_directory_view_class->sort_directories_first_changed = fm_empty_view_sort_directories_first_changed; + fm_directory_view_class->start_renaming_file = fm_empty_view_start_renaming_file; + fm_directory_view_class->get_zoom_level = fm_empty_view_get_zoom_level; + fm_directory_view_class->zoom_to_level = fm_empty_view_zoom_to_level; + fm_directory_view_class->emblems_changed = fm_empty_view_emblems_changed; + fm_directory_view_class->end_file_changes = fm_empty_view_end_file_changes; + fm_directory_view_class->using_manual_layout = fm_empty_view_using_manual_layout; + fm_directory_view_class->end_loading = fm_empty_view_end_loading; +} + +static const char * +fm_empty_view_get_id (CajaView *view) +{ + return FM_EMPTY_VIEW_ID; +} + + +static void +fm_empty_view_iface_init (CajaViewIface *iface) +{ + fm_directory_view_init_view_iface (iface); + + iface->get_view_id = fm_empty_view_get_id; + iface->get_first_visible_file = fm_empty_view_get_first_visible_file; + iface->scroll_to_file = fm_empty_view_scroll_to_file; + iface->get_title = NULL; + iface->grab_focus = fm_empty_view_grab_focus; +} + + +static void +fm_empty_view_init (FMEmptyView *empty_view) +{ + empty_view->details = g_new0 (FMEmptyViewDetails, 1); +} + +static CajaView * +fm_empty_view_create (CajaWindowSlotInfo *slot) +{ + FMEmptyView *view; + + g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot)); + + view = g_object_new (FM_TYPE_EMPTY_VIEW, + "window-slot", slot, + NULL); + + return CAJA_VIEW (view); +} + +static gboolean +fm_empty_view_supports_uri (const char *uri, + GFileType file_type, + const char *mime_type) +{ + if (file_type == G_FILE_TYPE_DIRECTORY) + { + return TRUE; + } + if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) + { + return TRUE; + } + if (g_str_has_prefix (uri, "trash:")) + { + return TRUE; + } + if (g_str_has_prefix (uri, EEL_SEARCH_URI)) + { + return TRUE; + } + + return FALSE; +} + +static CajaViewInfo fm_empty_view = +{ + FM_EMPTY_VIEW_ID, + "Empty", + "Empty View", + "_Empty View", + "The empty view encountered an error.", + "Display this location with the empty view.", + fm_empty_view_create, + fm_empty_view_supports_uri +}; + +void +fm_empty_view_register (void) +{ + fm_empty_view.id = fm_empty_view.id; + fm_empty_view.view_combo_label = fm_empty_view.view_combo_label; + fm_empty_view.view_menu_label_with_mnemonic = fm_empty_view.view_menu_label_with_mnemonic; + fm_empty_view.error_label = fm_empty_view.error_label; + fm_empty_view.display_location_label = fm_empty_view.display_location_label; + + caja_view_factory_register (&fm_empty_view); +} diff --git a/src/file-manager/fm-empty-view.h b/src/file-manager/fm-empty-view.h new file mode 100644 index 00000000..795dd27a --- /dev/null +++ b/src/file-manager/fm-empty-view.h @@ -0,0 +1,60 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-empty-view.h - interface for empty view of directory. + + Copyright (C) 2006 Free Software Foundation, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Christian Neumair <[email protected]> +*/ + +#ifndef FM_EMPTY_VIEW_H +#define FM_EMPTY_VIEW_H + +#include "fm-directory-view.h" + +#define FM_TYPE_EMPTY_VIEW fm_empty_view_get_type() +#define FM_EMPTY_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_EMPTY_VIEW, FMEmptyView)) +#define FM_EMPTY_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_EMPTY_VIEW, FMEmptyViewClass)) +#define FM_IS_EMPTY_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_EMPTY_VIEW)) +#define FM_IS_EMPTY_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_EMPTY_VIEW)) +#define FM_EMPTY_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_EMPTY_VIEW, FMEmptyViewClass)) + +#define FM_EMPTY_VIEW_ID "OAFIID:Caja_File_Manager_Empty_View" + +typedef struct FMEmptyViewDetails FMEmptyViewDetails; + +typedef struct +{ + FMDirectoryView parent_instance; + FMEmptyViewDetails *details; +} FMEmptyView; + +typedef struct +{ + FMDirectoryViewClass parent_class; +} FMEmptyViewClass; + +GType fm_empty_view_get_type (void); +void fm_empty_view_register (void); + +#endif /* FM_EMPTY_VIEW_H */ diff --git a/src/file-manager/fm-error-reporting.c b/src/file-manager/fm-error-reporting.c new file mode 100644 index 00000000..f301e9a9 --- /dev/null +++ b/src/file-manager/fm-error-reporting.c @@ -0,0 +1,380 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-error-reporting.h - implementation of file manager functions that report + errors to the user. + + Copyright (C) 2000 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: John Sullivan <[email protected]> +*/ + +#include <config.h> +#include "fm-error-reporting.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-file.h> +#include <eel/eel-string.h> +#include <eel/eel-stock-dialogs.h> + +#define NEW_NAME_TAG "Caja: new name" +#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50 + +static void finish_rename (CajaFile *file, gboolean stop_timer, GError *error); + +void +fm_report_error_loading_directory (CajaFile *file, + GError *error, + GtkWindow *parent_window) +{ + char *file_name; + char *message; + + if (error == NULL || + error->message == NULL) + { + return; + } + + if (error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_NOT_MOUNTED) + { + /* This case is retried automatically */ + return; + } + + file_name = caja_file_get_display_name (file); + + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_PERMISSION_DENIED: + message = g_strdup_printf (_("You do not have the permissions necessary to view the contents of \"%s\"."), + file_name); + break; + case G_IO_ERROR_NOT_FOUND: + message = g_strdup_printf (_("\"%s\" could not be found. Perhaps it has recently been deleted."), + file_name); + break; + default: + message = g_strdup_printf (_("Sorry, could not display all the contents of \"%s\": %s"), file_name, + error->message); + } + } + else + { + message = g_strdup (error->message); + } + + eel_show_error_dialog (_("The folder contents could not be displayed."), message, parent_window); + + g_free (file_name); + g_free (message); +} + +void +fm_report_error_renaming_file (CajaFile *file, + const char *new_name, + GError *error, + GtkWindow *parent_window) +{ + char *original_name, *original_name_truncated; + char *new_name_truncated; + char *message; + + /* Truncate names for display since very long file names with no spaces + * in them won't get wrapped, and can create insanely wide dialog boxes. + */ + original_name = caja_file_get_display_name (file); + original_name_truncated = eel_str_middle_truncate (original_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); + g_free (original_name); + + new_name_truncated = eel_str_middle_truncate (new_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); + + message = NULL; + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_EXISTS: + message = g_strdup_printf (_("The name \"%s\" is already used in this folder. " + "Please use a different name."), + new_name_truncated); + break; + case G_IO_ERROR_NOT_FOUND: + message = g_strdup_printf (_("There is no \"%s\" in this folder. " + "Perhaps it was just moved or deleted?"), + original_name_truncated); + break; + case G_IO_ERROR_PERMISSION_DENIED: + message = g_strdup_printf (_("You do not have the permissions necessary to rename \"%s\"."), + original_name_truncated); + break; + case G_IO_ERROR_INVALID_FILENAME: + if (strchr (new_name, '/') != NULL) + { + message = g_strdup_printf (_("The name \"%s\" is not valid because it contains the character \"/\". " + "Please use a different name."), + new_name_truncated); + } + else + { + message = g_strdup_printf (_("The name \"%s\" is not valid. " + "Please use a different name."), + new_name_truncated); + } + break; + default: + break; + } + } + + if (message == NULL) + { + /* We should invent decent error messages for every case we actually experience. */ + g_warning ("Hit unhandled case %s:%d in fm_report_error_renaming_file", + g_quark_to_string (error->domain), error->code); + /* fall through */ + message = g_strdup_printf (_("Sorry, could not rename \"%s\" to \"%s\": %s"), + original_name_truncated, new_name_truncated, + error->message); + } + + g_free (original_name_truncated); + g_free (new_name_truncated); + + eel_show_error_dialog (_("The item could not be renamed."), message, parent_window); + g_free (message); +} + +void +fm_report_error_setting_group (CajaFile *file, + GError *error, + GtkWindow *parent_window) +{ + char *file_name; + char *message; + + if (error == NULL) + { + return; + } + + file_name = caja_file_get_display_name (file); + + message = NULL; + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_PERMISSION_DENIED: + message = g_strdup_printf (_("You do not have the permissions necessary to change the group of \"%s\"."), + file_name); + break; + default: + break; + } + } + + if (message == NULL) + { + /* We should invent decent error messages for every case we actually experience. */ + g_warning ("Hit unhandled case %s:%d in fm_report_error_setting_group", + g_quark_to_string (error->domain), error->code); + /* fall through */ + message = g_strdup_printf (_("Sorry, could not change the group of \"%s\": %s"), file_name, + error->message); + } + + + eel_show_error_dialog (_("The group could not be changed."), message, parent_window); + + g_free (file_name); + g_free (message); +} + +void +fm_report_error_setting_owner (CajaFile *file, + GError *error, + GtkWindow *parent_window) +{ + char *file_name; + char *message; + + if (error == NULL) + { + return; + } + + file_name = caja_file_get_display_name (file); + + message = g_strdup_printf (_("Sorry, could not change the owner of \"%s\": %s"), file_name, error->message); + + eel_show_error_dialog (_("The owner could not be changed."), message, parent_window); + + g_free (file_name); + g_free (message); +} + +void +fm_report_error_setting_permissions (CajaFile *file, + GError *error, + GtkWindow *parent_window) +{ + char *file_name; + char *message; + + if (error == NULL) + { + return; + } + + file_name = caja_file_get_display_name (file); + + message = g_strdup_printf (_("Sorry, could not change the permissions of \"%s\": %s"), file_name, error->message); + + eel_show_error_dialog (_("The permissions could not be changed."), message, parent_window); + + g_free (file_name); + g_free (message); +} + +typedef struct _FMRenameData +{ + char *name; + CajaFileOperationCallback callback; + gpointer callback_data; +} FMRenameData; + +static void +fm_rename_data_free (FMRenameData *data) +{ + g_free (data->name); + g_free (data); +} + +static void +rename_callback (CajaFile *file, GFile *result_location, + GError *error, gpointer callback_data) +{ + FMRenameData *data; + + g_assert (CAJA_IS_FILE (file)); + g_assert (callback_data == NULL); + + data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG); + g_assert (data != NULL); + + if (error && + !(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)) + { + /* If rename failed, notify the user. */ + fm_report_error_renaming_file (file, data->name, error, NULL); + } + + finish_rename (file, TRUE, error); +} + +static void +cancel_rename_callback (gpointer callback_data) +{ + GError *error; + + error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); + finish_rename (CAJA_FILE (callback_data), FALSE, error); + g_error_free (error); +} + +static void +finish_rename (CajaFile *file, gboolean stop_timer, GError *error) +{ + FMRenameData *data; + + data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG); + if (data == NULL) + { + return; + } + + /* Cancel both the rename and the timed wait. */ + caja_file_cancel (file, rename_callback, NULL); + if (stop_timer) + { + eel_timed_wait_stop (cancel_rename_callback, file); + } + + if (data->callback != NULL) + { + data->callback (file, NULL, error, data->callback_data); + } + + /* Let go of file name. */ + g_object_set_data (G_OBJECT (file), NEW_NAME_TAG, NULL); +} + +void +fm_rename_file (CajaFile *file, + const char *new_name, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + char *old_name, *wait_message; + FMRenameData *data; + char *uri; + GError *error; + + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (new_name != NULL); + + /* Stop any earlier rename that's already in progress. */ + error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); + finish_rename (file, TRUE, error); + g_error_free (error); + + data = g_new0 (FMRenameData, 1); + data->name = g_strdup (new_name); + data->callback = callback; + data->callback_data = callback_data; + + /* Attach the new name to the file. */ + g_object_set_data_full (G_OBJECT (file), + NEW_NAME_TAG, + data, (GDestroyNotify)fm_rename_data_free); + + /* Start the timed wait to cancel the rename. */ + old_name = caja_file_get_display_name (file); + wait_message = g_strdup_printf (_("Renaming \"%s\" to \"%s\"."), + old_name, + new_name); + g_free (old_name); + eel_timed_wait_start (cancel_rename_callback, file, wait_message, + NULL); /* FIXME bugzilla.gnome.org 42395: Parent this? */ + g_free (wait_message); + + uri = caja_file_get_uri (file); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "rename file old=\"%s\", new=\"%s\"", + uri, new_name); + g_free (uri); + + /* Start the rename. */ + caja_file_rename (file, new_name, + rename_callback, NULL); +} diff --git a/src/file-manager/fm-error-reporting.h b/src/file-manager/fm-error-reporting.h new file mode 100644 index 00000000..556e5ae2 --- /dev/null +++ b/src/file-manager/fm-error-reporting.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-error-reporting.h - interface for file manager functions that report + errors to the user. + + Copyright (C) 2000 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: John Sullivan <[email protected]> +*/ + +#ifndef FM_ERROR_REPORTING_H +#define FM_ERROR_REPORTING_H + +#include <gtk/gtk.h> +#include <libcaja-private/caja-file.h> + +void fm_report_error_loading_directory (CajaFile *file, + GError *error, + GtkWindow *parent_window); +void fm_report_error_renaming_file (CajaFile *file, + const char *new_name, + GError *error, + GtkWindow *parent_window); +void fm_report_error_setting_permissions (CajaFile *file, + GError *error, + GtkWindow *parent_window); +void fm_report_error_setting_owner (CajaFile *file, + GError *error, + GtkWindow *parent_window); +void fm_report_error_setting_group (CajaFile *file, + GError *error, + GtkWindow *parent_window); + +/* FIXME bugzilla.gnome.org 42394: Should this file be renamed or should this function be moved? */ +void fm_rename_file (CajaFile *file, + const char *new_name, + CajaFileOperationCallback callback, + gpointer callback_data); + +#endif /* FM_ERROR_REPORTING_H */ diff --git a/src/file-manager/fm-icon-container.c b/src/file-manager/fm-icon-container.c new file mode 100644 index 00000000..86e54669 --- /dev/null +++ b/src/file-manager/fm-icon-container.c @@ -0,0 +1,625 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-container.h - the container widget for file manager icons + + Copyright (C) 2002 Sun Microsystems, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Michael Meeks <[email protected]> +*/ +#include <config.h> + +#include <string.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-thumbnails.h> +#include <libcaja-private/caja-desktop-icon-file.h> + +#include "fm-icon-container.h" + +#define ICON_TEXT_ATTRIBUTES_NUM_ITEMS 3 +#define ICON_TEXT_ATTRIBUTES_DEFAULT_TOKENS "size,date_modified,type" + +G_DEFINE_TYPE (FMIconContainer, fm_icon_container, CAJA_TYPE_ICON_CONTAINER); + +static GQuark attribute_none_q; + +static FMIconView * +get_icon_view (CajaIconContainer *container) +{ + /* Type unsafe comparison for performance */ + return ((FMIconContainer *)container)->view; +} + +static CajaIconInfo * +fm_icon_container_get_icon_images (CajaIconContainer *container, + CajaIconData *data, + int size, + GList **emblem_pixbufs, + char **embedded_text, + gboolean for_drag_accept, + gboolean need_large_embeddded_text, + gboolean *embedded_text_needs_loading, + gboolean *has_window_open) +{ + FMIconView *icon_view; + char **emblems_to_ignore; + CajaFile *file; + gboolean use_embedding; + CajaFileIconFlags flags; + guint emblem_size; + + file = (CajaFile *) data; + + g_assert (CAJA_IS_FILE (file)); + icon_view = get_icon_view (container); + g_return_val_if_fail (icon_view != NULL, NULL); + + use_embedding = FALSE; + if (embedded_text) + { + *embedded_text = caja_file_peek_top_left_text (file, need_large_embeddded_text, embedded_text_needs_loading); + use_embedding = *embedded_text != NULL; + } + + if (emblem_pixbufs != NULL) + { + emblem_size = caja_icon_get_emblem_size_for_icon_size (size); + /* don't return images larger than the actual icon size */ + emblem_size = MIN (emblem_size, size); + + if (emblem_size > 0) + { + emblems_to_ignore = fm_directory_view_get_emblem_names_to_exclude + (FM_DIRECTORY_VIEW (icon_view)); + *emblem_pixbufs = caja_file_get_emblem_pixbufs (file, + emblem_size, + FALSE, + emblems_to_ignore); + g_strfreev (emblems_to_ignore); + } + } + + *has_window_open = caja_file_has_open_window (file); + + flags = CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM; + if (!fm_icon_view_is_compact (icon_view) || + caja_icon_container_get_zoom_level (container) > CAJA_ZOOM_LEVEL_STANDARD) + { + flags |= CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS; + if (fm_icon_view_is_compact (icon_view)) + { + flags |= CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE; + } + } + + if (use_embedding) + { + flags |= CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT; + } + if (for_drag_accept) + { + flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT; + } + + return caja_file_get_icon (file, size, flags); +} + +static char * +fm_icon_container_get_icon_description (CajaIconContainer *container, + CajaIconData *data) +{ + CajaFile *file; + char *mime_type; + const char *description; + + file = CAJA_FILE (data); + g_assert (CAJA_IS_FILE (file)); + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) + { + return NULL; + } + + mime_type = caja_file_get_mime_type (file); + description = g_content_type_get_description (mime_type); + g_free (mime_type); + return g_strdup (description); +} + +static void +fm_icon_container_start_monitor_top_left (CajaIconContainer *container, + CajaIconData *data, + gconstpointer client, + gboolean large_text) +{ + CajaFile *file; + CajaFileAttributes attributes; + + file = (CajaFile *) data; + + g_assert (CAJA_IS_FILE (file)); + + attributes = CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT; + if (large_text) + { + attributes |= CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT; + } + caja_file_monitor_add (file, client, attributes); +} + +static void +fm_icon_container_stop_monitor_top_left (CajaIconContainer *container, + CajaIconData *data, + gconstpointer client) +{ + CajaFile *file; + + file = (CajaFile *) data; + + g_assert (CAJA_IS_FILE (file)); + + caja_file_monitor_remove (file, client); +} + +static void +fm_icon_container_prioritize_thumbnailing (CajaIconContainer *container, + CajaIconData *data) +{ + CajaFile *file; + char *uri; + + file = (CajaFile *) data; + + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_thumbnailing (file)) + { + uri = caja_file_get_uri (file); + caja_thumbnail_prioritize (uri); + g_free (uri); + } +} + +/* + * Get the preference for which caption text should appear + * beneath icons. + */ +static GQuark * +fm_icon_container_get_icon_text_attributes_from_preferences (void) +{ + static GQuark *attributes = NULL; + + if (attributes == NULL) + { + eel_preferences_add_auto_string_array_as_quarks (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS, + &attributes); + } + + /* We don't need to sanity check the attributes list even though it came + * from preferences. + * + * There are 2 ways that the values in the list could be bad. + * + * 1) The user picks "bad" values. "bad" values are those that result in + * there being duplicate attributes in the list. + * + * 2) Value stored in MateConf are tampered with. Its possible physically do + * this by pulling the rug underneath MateConf and manually editing its + * config files. Its also possible to use a third party MateConf key + * editor and store garbage for the keys in question. + * + * Thankfully, the Caja preferences machinery deals with both of + * these cases. + * + * In the first case, the preferences dialog widgetry prevents + * duplicate attributes by making "bad" choices insensitive. + * + * In the second case, the preferences getter (and also the auto storage) for + * string_array values are always valid members of the enumeration associated + * with the preference. + * + * So, no more error checking on attributes is needed here and we can return + * a the auto stored value. + */ + return attributes; +} + +static int +quarkv_length (GQuark *attributes) +{ + int i; + i = 0; + while (attributes[i] != 0) + { + i++; + } + return i; +} + +/** + * fm_icon_view_get_icon_text_attribute_names: + * + * Get a list representing which text attributes should be displayed + * beneath an icon. The result is dependent on zoom level and possibly + * user configuration. Don't free the result. + * @view: FMIconView to query. + * + **/ +static GQuark * +fm_icon_container_get_icon_text_attribute_names (CajaIconContainer *container, + int *len) +{ + GQuark *attributes; + int piece_count; + + const int pieces_by_level[] = + { + 0, /* CAJA_ZOOM_LEVEL_SMALLEST */ + 0, /* CAJA_ZOOM_LEVEL_SMALLER */ + 0, /* CAJA_ZOOM_LEVEL_SMALL */ + 1, /* CAJA_ZOOM_LEVEL_STANDARD */ + 2, /* CAJA_ZOOM_LEVEL_LARGE */ + 2, /* CAJA_ZOOM_LEVEL_LARGER */ + 3 /* CAJA_ZOOM_LEVEL_LARGEST */ + }; + + piece_count = pieces_by_level[caja_icon_container_get_zoom_level (container)]; + + attributes = fm_icon_container_get_icon_text_attributes_from_preferences (); + + *len = MIN (piece_count, quarkv_length (attributes)); + + return attributes; +} + +/* This callback returns the text, both the editable part, and the + * part below that is not editable. + */ +static void +fm_icon_container_get_icon_text (CajaIconContainer *container, + CajaIconData *data, + char **editable_text, + char **additional_text, + gboolean include_invisible) +{ + char *actual_uri; + gchar *description; + GQuark *attributes; + char *text_array[4]; + int i, j, num_attributes; + FMIconView *icon_view; + CajaFile *file; + gboolean use_additional; + + file = CAJA_FILE (data); + + g_assert (CAJA_IS_FILE (file)); + g_assert (editable_text != NULL); + icon_view = get_icon_view (container); + g_return_if_fail (icon_view != NULL); + + use_additional = (additional_text != NULL); + + /* In the smallest zoom mode, no text is drawn. */ + if (caja_icon_container_get_zoom_level (container) == CAJA_ZOOM_LEVEL_SMALLEST && + !include_invisible) + { + *editable_text = NULL; + } + else + { + /* Strip the suffix for caja object xml files. */ + *editable_text = caja_file_get_display_name (file); + } + + if (!use_additional) + { + return; + } + + if (fm_icon_view_is_compact (icon_view)) + { + *additional_text = NULL; + return; + } + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) + { + /* Don't show the normal extra information for desktop icons, it doesn't + * make sense. */ + *additional_text = NULL; + return; + } + + /* Handle link files specially. */ + if (caja_file_is_caja_link (file)) + { + /* FIXME bugzilla.gnome.org 42531: Does sync. I/O and works only locally. */ + *additional_text = NULL; + if (caja_file_is_local (file)) + { + actual_uri = caja_file_get_uri (file); + description = caja_link_local_get_additional_text (actual_uri); + if (description) + *additional_text = g_strdup_printf (" \n%s\n ", description); + g_free (description); + g_free (actual_uri); + } + /* Don't show the normal extra information for desktop files, it doesn't + * make sense. */ + return; + } + + /* Find out what attributes go below each icon. */ + attributes = fm_icon_container_get_icon_text_attribute_names (container, + &num_attributes); + + /* Get the attributes. */ + j = 0; + for (i = 0; i < num_attributes; ++i) + { + if (attributes[i] == attribute_none_q) + { + continue; + } + + text_array[j++] = + caja_file_get_string_attribute_with_default_q (file, attributes[i]); + } + text_array[j] = NULL; + + /* Return them. */ + if (j == 0) + { + *additional_text = NULL; + } + else if (j == 1) + { + /* Only one item, avoid the strdup + free */ + *additional_text = text_array[0]; + } + else + { + *additional_text = g_strjoinv ("\n", text_array); + + for (i = 0; i < j; i++) + { + g_free (text_array[i]); + } + } +} + +/* Sort as follows: + * 0) computer link + * 1) home link + * 2) network link + * 3) mount links + * 4) other + * 5) trash link + */ +typedef enum +{ + SORT_COMPUTER_LINK, + SORT_HOME_LINK, + SORT_NETWORK_LINK, + SORT_MOUNT_LINK, + SORT_OTHER, + SORT_TRASH_LINK +} SortCategory; + +static SortCategory +get_sort_category (CajaFile *file) +{ + CajaDesktopLink *link; + SortCategory category; + + category = SORT_OTHER; + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) + { + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); + if (link != NULL) + { + switch (caja_desktop_link_get_link_type (link)) + { + case CAJA_DESKTOP_LINK_COMPUTER: + category = SORT_COMPUTER_LINK; + break; + case CAJA_DESKTOP_LINK_HOME: + category = SORT_HOME_LINK; + break; + case CAJA_DESKTOP_LINK_MOUNT: + category = SORT_MOUNT_LINK; + break; + case CAJA_DESKTOP_LINK_TRASH: + category = SORT_TRASH_LINK; + break; + case CAJA_DESKTOP_LINK_NETWORK: + category = SORT_NETWORK_LINK; + break; + default: + category = SORT_OTHER; + break; + } + g_object_unref (link); + } + } + + return category; +} + +static int +fm_desktop_icon_container_icons_compare (CajaIconContainer *container, + CajaIconData *data_a, + CajaIconData *data_b) +{ + CajaFile *file_a; + CajaFile *file_b; + FMDirectoryView *directory_view; + SortCategory category_a, category_b; + + file_a = (CajaFile *) data_a; + file_b = (CajaFile *) data_b; + + directory_view = FM_DIRECTORY_VIEW (FM_ICON_CONTAINER (container)->view); + g_return_val_if_fail (directory_view != NULL, 0); + + category_a = get_sort_category (file_a); + category_b = get_sort_category (file_b); + + if (category_a == category_b) + { + return caja_file_compare_for_sort + (file_a, file_b, CAJA_FILE_SORT_BY_DISPLAY_NAME, + fm_directory_view_should_sort_directories_first (directory_view), + FALSE); + } + + if (category_a < category_b) + { + return -1; + } + else + { + return +1; + } +} + +static int +fm_icon_container_compare_icons (CajaIconContainer *container, + CajaIconData *icon_a, + CajaIconData *icon_b) +{ + FMIconView *icon_view; + + icon_view = get_icon_view (container); + g_return_val_if_fail (icon_view != NULL, 0); + + if (FM_ICON_CONTAINER (container)->sort_for_desktop) + { + return fm_desktop_icon_container_icons_compare + (container, icon_a, icon_b); + } + + /* Type unsafe comparisons for performance */ + return fm_icon_view_compare_files (icon_view, + (CajaFile *)icon_a, + (CajaFile *)icon_b); +} + +static int +fm_icon_container_compare_icons_by_name (CajaIconContainer *container, + CajaIconData *icon_a, + CajaIconData *icon_b) +{ + return caja_file_compare_for_sort + (CAJA_FILE (icon_a), + CAJA_FILE (icon_b), + CAJA_FILE_SORT_BY_DISPLAY_NAME, + FALSE, FALSE); +} + +static void +fm_icon_container_freeze_updates (CajaIconContainer *container) +{ + FMIconView *icon_view; + icon_view = get_icon_view (container); + g_return_if_fail (icon_view != NULL); + fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +fm_icon_container_unfreeze_updates (CajaIconContainer *container) +{ + FMIconView *icon_view; + icon_view = get_icon_view (container); + g_return_if_fail (icon_view != NULL); + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +fm_icon_container_dispose (GObject *object) +{ + FMIconContainer *icon_container; + + icon_container = FM_ICON_CONTAINER (object); + + icon_container->view = NULL; + + G_OBJECT_CLASS (fm_icon_container_parent_class)->dispose (object); +} + +static void +fm_icon_container_class_init (FMIconContainerClass *klass) +{ + CajaIconContainerClass *ic_class; + + ic_class = &klass->parent_class; + + attribute_none_q = g_quark_from_static_string ("none"); + + ic_class->get_icon_text = fm_icon_container_get_icon_text; + ic_class->get_icon_images = fm_icon_container_get_icon_images; + ic_class->get_icon_description = fm_icon_container_get_icon_description; + ic_class->start_monitor_top_left = fm_icon_container_start_monitor_top_left; + ic_class->stop_monitor_top_left = fm_icon_container_stop_monitor_top_left; + ic_class->prioritize_thumbnailing = fm_icon_container_prioritize_thumbnailing; + + ic_class->compare_icons = fm_icon_container_compare_icons; + ic_class->compare_icons_by_name = fm_icon_container_compare_icons_by_name; + ic_class->freeze_updates = fm_icon_container_freeze_updates; + ic_class->unfreeze_updates = fm_icon_container_unfreeze_updates; + + G_OBJECT_CLASS (klass)->dispose = fm_icon_container_dispose; +} + +static void +fm_icon_container_init (FMIconContainer *icon_container) +{ +} + +CajaIconContainer * +fm_icon_container_construct (FMIconContainer *icon_container, FMIconView *view) +{ + AtkObject *atk_obj; + + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + + icon_container->view = view; + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_container)); + atk_object_set_name (atk_obj, _("Icon View")); + + return CAJA_ICON_CONTAINER (icon_container); +} + +CajaIconContainer * +fm_icon_container_new (FMIconView *view) +{ + return fm_icon_container_construct + (g_object_new (FM_TYPE_ICON_CONTAINER, NULL), + view); +} + +void +fm_icon_container_set_sort_desktop (FMIconContainer *container, + gboolean desktop) +{ + container->sort_for_desktop = desktop; +} diff --git a/src/file-manager/fm-icon-container.h b/src/file-manager/fm-icon-container.h new file mode 100644 index 00000000..a0527acf --- /dev/null +++ b/src/file-manager/fm-icon-container.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-container.h - the container widget for file manager icons + + Copyright (C) 2002 Sun Microsystems, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Michael Meeks <[email protected]> +*/ + +#ifndef FM_ICON_CONTAINER_H +#define FM_ICON_CONTAINER_H + +#include <libcaja-private/caja-icon-container.h> +#include "fm-icon-view.h" + +typedef struct FMIconContainer FMIconContainer; +typedef struct FMIconContainerClass FMIconContainerClass; + +#define FM_TYPE_ICON_CONTAINER fm_icon_container_get_type() +#define FM_ICON_CONTAINER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_ICON_CONTAINER, FMIconContainer)) +#define FM_ICON_CONTAINER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_ICON_CONTAINER, FMIconContainerClass)) +#define FM_IS_ICON_CONTAINER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_ICON_CONTAINER)) +#define FM_IS_ICON_CONTAINER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_ICON_CONTAINER)) +#define FM_ICON_CONTAINER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_ICON_CONTAINER, FMIconContainerClass)) + +typedef struct FMIconContainerDetails FMIconContainerDetails; + +struct FMIconContainer +{ + CajaIconContainer parent; + + FMIconView *view; + gboolean sort_for_desktop; +}; + +struct FMIconContainerClass +{ + CajaIconContainerClass parent_class; +}; + +GType fm_icon_container_get_type (void); +CajaIconContainer *fm_icon_container_construct (FMIconContainer *icon_container, + FMIconView *view); +CajaIconContainer *fm_icon_container_new (FMIconView *view); +void fm_icon_container_set_sort_desktop (FMIconContainer *container, + gboolean desktop); + +#endif /* FM_ICON_CONTAINER_H */ diff --git a/src/file-manager/fm-icon-view.c b/src/file-manager/fm-icon-view.c new file mode 100644 index 00000000..7d8687a2 --- /dev/null +++ b/src/file-manager/fm-icon-view.c @@ -0,0 +1,3439 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-view.c - implementation of icon view of directory. + + Copyright (C) 2000, 2001 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: John Sullivan <[email protected]> +*/ + +#include <config.h> +#include "fm-icon-view.h" + +#include "fm-actions.h" +#include "fm-icon-container.h" +#include "fm-desktop-icon-view.h" +#include "fm-error-reporting.h" +#include <stdlib.h> +#include <eel/eel-background.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <errno.h> +#include <fcntl.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-container.h> +#include <libcaja-private/caja-icon-dnd.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <locale.h> +#include <signal.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "caja-audio-mime-types.h" + +#define POPUP_PATH_ICON_APPEARANCE "/selection/Icon Appearance Items" + +enum +{ + PROP_0, + PROP_COMPACT +}; + +typedef struct +{ + const CajaFileSortType sort_type; + const char *metadata_text; + const char *action; + const char *menu_label; + const char *menu_hint; +} SortCriterion; + +typedef enum +{ + MENU_ITEM_TYPE_STANDARD, + MENU_ITEM_TYPE_CHECK, + MENU_ITEM_TYPE_RADIO, + MENU_ITEM_TYPE_TREE +} MenuItemType; + +struct FMIconViewDetails +{ + GList *icons_not_positioned; + + guint react_to_icon_change_idle_id; + + const SortCriterion *sort; + gboolean sort_reversed; + + GtkActionGroup *icon_action_group; + guint icon_merge_id; + + int audio_preview_timeout; + CajaFile *audio_preview_file; + int audio_preview_child_watch; + GPid audio_preview_child_pid; + + gboolean filter_by_screen; + int num_screens; + + gboolean compact; + + gulong clipboard_handler_id; +}; + + +/* Note that the first item in this list is the default sort, + * and that the items show up in the menu in the order they + * appear in this list. + */ +static const SortCriterion sort_criteria[] = +{ + { + CAJA_FILE_SORT_BY_DISPLAY_NAME, + "name", + "Sort by Name", + N_("by _Name"), + N_("Keep icons sorted by name in rows") + }, + { + CAJA_FILE_SORT_BY_SIZE, + "size", + "Sort by Size", + N_("by _Size"), + N_("Keep icons sorted by size in rows") + }, + { + CAJA_FILE_SORT_BY_TYPE, + "type", + "Sort by Type", + N_("by _Type"), + N_("Keep icons sorted by type in rows") + }, + { + CAJA_FILE_SORT_BY_MTIME, + "modification date", + "Sort by Modification Date", + N_("by Modification _Date"), + N_("Keep icons sorted by modification date in rows") + }, + { + CAJA_FILE_SORT_BY_EMBLEMS, + "emblems", + "Sort by Emblems", + N_("by _Emblems"), + N_("Keep icons sorted by emblems in rows") + }, + { + CAJA_FILE_SORT_BY_TRASHED_TIME, + "trashed", + "Sort by Trash Time", + N_("by T_rash Time"), + N_("Keep icons sorted by trash time in rows") + } +}; + +static gboolean default_sort_in_reverse_order = FALSE; +static int preview_sound_auto_value; + +static void fm_icon_view_set_directory_sort_by (FMIconView *icon_view, + CajaFile *file, + const char *sort_by); +static void fm_icon_view_set_zoom_level (FMIconView *view, + CajaZoomLevel new_level, + gboolean always_emit); +static void fm_icon_view_update_click_mode (FMIconView *icon_view); +static void fm_icon_view_set_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file, + gboolean tighter_layout); +static gboolean fm_icon_view_supports_manual_layout (FMIconView *icon_view); +static gboolean fm_icon_view_supports_scaling (FMIconView *icon_view); +static void fm_icon_view_reveal_selection (FMDirectoryView *view); +static const SortCriterion *get_sort_criterion_by_sort_type (CajaFileSortType sort_type); +static void set_sort_criterion_by_sort_type (FMIconView *icon_view, + CajaFileSortType sort_type); +static gboolean set_sort_reversed (FMIconView *icon_view, + gboolean new_value); +static void switch_to_manual_layout (FMIconView *view); +static void preview_audio (FMIconView *icon_view, + CajaFile *file, + gboolean start_flag); +static void update_layout_menus (FMIconView *view); +static CajaFileSortType get_default_sort_order (CajaFile *file, + gboolean *reversed); + + +static void fm_icon_view_iface_init (CajaViewIface *iface); + +G_DEFINE_TYPE_WITH_CODE (FMIconView, fm_icon_view, FM_TYPE_DIRECTORY_VIEW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, + fm_icon_view_iface_init)); + +static void +fm_icon_view_destroy (GtkObject *object) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (object); + + if (icon_view->details->react_to_icon_change_idle_id != 0) + { + g_source_remove (icon_view->details->react_to_icon_change_idle_id); + icon_view->details->react_to_icon_change_idle_id = 0; + } + + if (icon_view->details->clipboard_handler_id != 0) + { + g_signal_handler_disconnect (caja_clipboard_monitor_get (), + icon_view->details->clipboard_handler_id); + icon_view->details->clipboard_handler_id = 0; + } + + /* kill any sound preview process that is ongoing */ + preview_audio (icon_view, NULL, FALSE); + + if (icon_view->details->icons_not_positioned) + { + caja_file_list_free (icon_view->details->icons_not_positioned); + icon_view->details->icons_not_positioned = NULL; + } + + GTK_OBJECT_CLASS (fm_icon_view_parent_class)->destroy (object); +} + + +static void +fm_icon_view_finalize (GObject *object) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (object); + + g_free (icon_view->details); + + G_OBJECT_CLASS (fm_icon_view_parent_class)->finalize (object); +} + +static CajaIconContainer * +get_icon_container (FMIconView *icon_view) +{ + return CAJA_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))); +} + +static gboolean +get_stored_icon_position_callback (CajaIconContainer *container, + CajaFile *file, + CajaIconPosition *position, + FMIconView *icon_view) +{ + char *position_string, *scale_string; + gboolean position_good; + char c; + + g_assert (CAJA_IS_ICON_CONTAINER (container)); + g_assert (CAJA_IS_FILE (file)); + g_assert (position != NULL); + g_assert (FM_IS_ICON_VIEW (icon_view)); + + if (!fm_icon_view_supports_manual_layout (icon_view)) + { + return FALSE; + } + + /* Get the current position of this icon from the metadata. */ + position_string = caja_file_get_metadata + (file, CAJA_METADATA_KEY_ICON_POSITION, ""); + position_good = sscanf + (position_string, " %d , %d %c", + &position->x, &position->y, &c) == 2; + g_free (position_string); + + /* If it is the desktop directory, maybe the mate-libs metadata has information about it */ + + /* Disable scaling if not on the desktop */ + if (fm_icon_view_supports_scaling (icon_view)) + { + /* Get the scale of the icon from the metadata. */ + scale_string = caja_file_get_metadata + (file, CAJA_METADATA_KEY_ICON_SCALE, "1"); + position->scale = g_ascii_strtod (scale_string, NULL); + if (errno != 0) + { + position->scale = 1.0; + } + + g_free (scale_string); + } + else + { + position->scale = 1.0; + } + + return position_good; +} + +static void +real_set_sort_criterion (FMIconView *icon_view, + const SortCriterion *sort, + gboolean clear) +{ + CajaFile *file; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + + if (clear) + { + caja_file_set_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, NULL, NULL); + caja_file_set_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, NULL, NULL); + icon_view->details->sort = + get_sort_criterion_by_sort_type (get_default_sort_order + (file, &icon_view->details->sort_reversed)); + } + else + { + /* Store the new sort setting. */ + fm_icon_view_set_directory_sort_by (icon_view, + file, + sort->metadata_text); + } + + /* Update the layout menus to match the new sort setting. */ + update_layout_menus (icon_view); +} + +static void +set_sort_criterion (FMIconView *icon_view, const SortCriterion *sort) +{ + if (sort == NULL || + icon_view->details->sort == sort) + { + return; + } + + icon_view->details->sort = sort; + + real_set_sort_criterion (icon_view, sort, FALSE); +} + +static void +clear_sort_criterion (FMIconView *icon_view) +{ + real_set_sort_criterion (icon_view, NULL, TRUE); +} + +static void +action_stretch_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_ICON_VIEW (callback_data)); + + caja_icon_container_show_stretch_handles + (get_icon_container (FM_ICON_VIEW (callback_data))); +} + +static void +action_unstretch_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_ICON_VIEW (callback_data)); + + caja_icon_container_unstretch + (get_icon_container (FM_ICON_VIEW (callback_data))); +} + +static void +fm_icon_view_clean_up (FMIconView *icon_view) +{ + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, clean_up, (icon_view)); +} + +static void +fm_icon_view_real_clean_up (FMIconView *icon_view) +{ + CajaIconContainer *icon_container; + gboolean saved_sort_reversed; + + icon_container = get_icon_container (icon_view); + + /* Hardwire Clean Up to always be by name, in forward order */ + saved_sort_reversed = icon_view->details->sort_reversed; + + set_sort_reversed (icon_view, FALSE); + set_sort_criterion (icon_view, &sort_criteria[0]); + + caja_icon_container_sort (icon_container); + caja_icon_container_freeze_icon_positions (icon_container); + + set_sort_reversed (icon_view, saved_sort_reversed); +} + +static void +action_clean_up_callback (GtkAction *action, gpointer callback_data) +{ + fm_icon_view_clean_up (FM_ICON_VIEW (callback_data)); +} + +static void +set_tighter_layout (FMIconView *icon_view, gboolean new_value) +{ + fm_icon_view_set_directory_tighter_layout (icon_view, + fm_directory_view_get_directory_as_file + (FM_DIRECTORY_VIEW (icon_view)), + new_value); + caja_icon_container_set_tighter_layout (get_icon_container (icon_view), + new_value); +} + +static void +action_tighter_layout_callback (GtkAction *action, + gpointer user_data) +{ + g_assert (FM_IS_ICON_VIEW (user_data)); + + set_tighter_layout (FM_ICON_VIEW (user_data), + gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))); +} + + +static gboolean +fm_icon_view_using_auto_layout (FMIconView *icon_view) +{ + return caja_icon_container_is_auto_layout + (get_icon_container (icon_view)); +} + +static gboolean +fm_icon_view_using_tighter_layout (FMIconView *icon_view) +{ + return caja_icon_container_is_tighter_layout + (get_icon_container (icon_view)); +} + +static void +action_sort_radio_callback (GtkAction *action, + GtkRadioAction *current, + FMIconView *view) +{ + CajaFileSortType sort_type; + + sort_type = gtk_radio_action_get_current_value (current); + + /* Note that id might be a toggle item. + * Ignore non-sort ids so that they don't cause sorting. + */ + if (sort_type == CAJA_FILE_SORT_NONE) + { + switch_to_manual_layout (view); + } + else + { + set_sort_criterion_by_sort_type (view, sort_type); + } +} + +static void +list_covers (CajaIconData *data, gpointer callback_data) +{ + GSList **file_list; + + file_list = callback_data; + + *file_list = g_slist_prepend (*file_list, data); +} + +static void +unref_cover (CajaIconData *data, gpointer callback_data) +{ + caja_file_unref (CAJA_FILE (data)); +} + +static void +fm_icon_view_clear (FMDirectoryView *view) +{ + CajaIconContainer *icon_container; + GSList *file_list; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_container = get_icon_container (FM_ICON_VIEW (view)); + if (!icon_container) + return; + + /* Clear away the existing icons. */ + file_list = NULL; + caja_icon_container_for_each (icon_container, list_covers, &file_list); + caja_icon_container_clear (icon_container); + g_slist_foreach (file_list, (GFunc)unref_cover, NULL); + g_slist_free (file_list); +} + + +static gboolean +should_show_file_on_screen (FMDirectoryView *view, CajaFile *file) +{ + char *screen_string; + int screen_num; + FMIconView *icon_view; + GdkScreen *screen; + + icon_view = FM_ICON_VIEW (view); + + if (!fm_directory_view_should_show_file (view, file)) + { + return FALSE; + } + + /* Get the screen for this icon from the metadata. */ + screen_string = caja_file_get_metadata + (file, CAJA_METADATA_KEY_SCREEN, "0"); + screen_num = atoi (screen_string); + g_free (screen_string); + screen = gtk_widget_get_screen (GTK_WIDGET (view)); + + if (screen_num != gdk_screen_get_number (screen) && + (screen_num < icon_view->details->num_screens || + gdk_screen_get_number (screen) > 0)) + { + return FALSE; + } + + return TRUE; +} + +static void +fm_icon_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMIconView *icon_view; + + /* This used to assert that 'directory == fm_directory_view_get_model (view)', but that + * resulted in a lot of crash reports (bug #352592). I don't see how that trace happens. + * It seems that somehow we get a files_changed event sent to the view from a directory + * that isn't the model, but the code disables the monitor and signal callback handlers when + * changing directories. Maybe we can get some more information when this happens. + * Further discussion in bug #368178. + */ + if (directory != fm_directory_view_get_model (view)) + { + char *file_uri, *dir_uri, *model_uri; + file_uri = caja_file_get_uri (file); + dir_uri = caja_directory_get_uri (directory); + model_uri = caja_directory_get_uri (fm_directory_view_get_model (view)); + g_warning ("fm_icon_view_remove_file() - directory not icon view model, shouldn't happen.\n" + "file: %p:%s, dir: %p:%s, model: %p:%s, view loading: %d\n" + "If you see this, please add this info to http://bugzilla.gnome.org/show_bug.cgi?id=368178", + file, file_uri, directory, dir_uri, fm_directory_view_get_model (view), model_uri, fm_directory_view_get_loading (view)); + g_free (file_uri); + g_free (dir_uri); + g_free (model_uri); + } + + icon_view = FM_ICON_VIEW (view); + + if (caja_icon_container_remove (get_icon_container (icon_view), + CAJA_ICON_CONTAINER_ICON_DATA (file))) + { + if (file == icon_view->details->audio_preview_file) + { + preview_audio (icon_view, NULL, FALSE); + } + + caja_file_unref (file); + } +} + +static void +fm_icon_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMIconView *icon_view; + CajaIconContainer *icon_container; + + g_assert (directory == fm_directory_view_get_model (view)); + + icon_view = FM_ICON_VIEW (view); + icon_container = get_icon_container (icon_view); + + if (icon_view->details->filter_by_screen && + !should_show_file_on_screen (view, file)) + { + return; + } + + /* Reset scroll region for the first icon added when loading a directory. */ + if (fm_directory_view_get_loading (view) && caja_icon_container_is_empty (icon_container)) + { + caja_icon_container_reset_scroll_region (icon_container); + } + + if (caja_icon_container_add (icon_container, + CAJA_ICON_CONTAINER_ICON_DATA (file))) + { + caja_file_ref (file); + } +} + +static void +fm_icon_view_flush_added_files (FMDirectoryView *view) +{ + caja_icon_container_layout_now (get_icon_container (FM_ICON_VIEW (view))); +} + +static void +fm_icon_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMIconView *icon_view; + + g_assert (directory == fm_directory_view_get_model (view)); + + g_return_if_fail (view != NULL); + icon_view = FM_ICON_VIEW (view); + + if (!icon_view->details->filter_by_screen) + { + caja_icon_container_request_update + (get_icon_container (icon_view), + CAJA_ICON_CONTAINER_ICON_DATA (file)); + return; + } + + if (!should_show_file_on_screen (view, file)) + { + fm_icon_view_remove_file (view, file, directory); + } + else + { + + caja_icon_container_request_update + (get_icon_container (icon_view), + CAJA_ICON_CONTAINER_ICON_DATA (file)); + } +} + +static gboolean +fm_icon_view_supports_auto_layout (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_auto_layout, (view)); +} + +static gboolean +fm_icon_view_supports_scaling (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_scaling, (view)); +} + +static gboolean +fm_icon_view_supports_manual_layout (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_manual_layout, (view)); +} + +static gboolean +fm_icon_view_supports_keep_aligned (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_keep_aligned, (view)); +} + +static gboolean +fm_icon_view_supports_labels_beside_icons (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_labels_beside_icons, (view)); +} + +static gboolean +fm_icon_view_supports_tighter_layout (FMIconView *view) +{ + return !fm_icon_view_is_compact (view); +} + +static void +update_layout_menus (FMIconView *view) +{ + gboolean is_auto_layout; + GtkAction *action; + const char *action_name; + CajaFile *file; + + if (view->details->icon_action_group == NULL) + { + return; + } + + is_auto_layout = fm_icon_view_using_auto_layout (view); + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + + if (fm_icon_view_supports_auto_layout (view)) + { + /* Mark sort criterion. */ + action_name = is_auto_layout ? view->details->sort->action : FM_ACTION_MANUAL_LAYOUT; + action = gtk_action_group_get_action (view->details->icon_action_group, + action_name); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_TIGHTER_LAYOUT); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + fm_icon_view_using_tighter_layout (view)); + gtk_action_set_sensitive (action, fm_icon_view_supports_tighter_layout (view)); + gtk_action_set_visible (action, fm_icon_view_supports_tighter_layout (view)); + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_REVERSED_ORDER); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + view->details->sort_reversed); + gtk_action_set_sensitive (action, is_auto_layout); + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_SORT_TRASH_TIME); + + if (file != NULL && caja_file_is_in_trash (file)) + { + gtk_action_set_visible (action, TRUE); + } + else + { + gtk_action_set_visible (action, FALSE); + } + } + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_MANUAL_LAYOUT); + gtk_action_set_visible (action, + fm_icon_view_supports_manual_layout (view)); + + /* Clean Up is only relevant for manual layout */ + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_CLEAN_UP); + gtk_action_set_sensitive (action, !is_auto_layout); + + if (FM_IS_DESKTOP_ICON_VIEW (view)) + { + gtk_action_set_label (action, _("_Organize Desktop by Name")); + } + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_KEEP_ALIGNED); + gtk_action_set_visible (action, + fm_icon_view_supports_keep_aligned (view)); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + caja_icon_container_is_keep_aligned (get_icon_container (view))); + gtk_action_set_sensitive (action, !is_auto_layout); +} + + +static char * +fm_icon_view_get_directory_sort_by (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return g_strdup ("name"); + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, icon_view, + get_directory_sort_by, (icon_view, file)); +} + +static CajaFileSortType default_sort_order = CAJA_FILE_SORT_BY_DISPLAY_NAME; + +static CajaFileSortType +get_default_sort_order (CajaFile *file, gboolean *reversed) +{ + static gboolean auto_storaged_added = FALSE; + CajaFileSortType retval; + + if (auto_storaged_added == FALSE) + { + auto_storaged_added = TRUE; + eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER, + (int *) &default_sort_order); + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + &default_sort_in_reverse_order); + + } + + retval = caja_file_get_default_sort_type (file, reversed); + + if (retval == CAJA_FILE_SORT_NONE) + { + + if (reversed != NULL) + { + *reversed = default_sort_in_reverse_order; + } + + retval = CLAMP (default_sort_order, CAJA_FILE_SORT_BY_DISPLAY_NAME, + CAJA_FILE_SORT_BY_EMBLEMS); + } + + return retval; +} + +static char * +fm_icon_view_real_get_directory_sort_by (FMIconView *icon_view, + CajaFile *file) +{ + const SortCriterion *default_sort_criterion; + default_sort_criterion = get_sort_criterion_by_sort_type (get_default_sort_order (file, NULL)); + g_return_val_if_fail (default_sort_criterion != NULL, NULL); + + return caja_file_get_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, + default_sort_criterion->metadata_text); +} + +static void +fm_icon_view_set_directory_sort_by (FMIconView *icon_view, + CajaFile *file, + const char *sort_by) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return; + } + + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, + set_directory_sort_by, (icon_view, file, sort_by)); +} + +static void +fm_icon_view_real_set_directory_sort_by (FMIconView *icon_view, + CajaFile *file, + const char *sort_by) +{ + const SortCriterion *default_sort_criterion; + default_sort_criterion = get_sort_criterion_by_sort_type (get_default_sort_order (file, NULL)); + g_return_if_fail (default_sort_criterion != NULL); + + caja_file_set_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, + default_sort_criterion->metadata_text, + sort_by); +} + +static gboolean +fm_icon_view_get_directory_sort_reversed (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return FALSE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, icon_view, + get_directory_sort_reversed, (icon_view, file)); +} + +static gboolean +fm_icon_view_real_get_directory_sort_reversed (FMIconView *icon_view, + CajaFile *file) +{ + gboolean reversed; + + get_default_sort_order (file, &reversed); + return caja_file_get_boolean_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, + reversed); +} + +static void +fm_icon_view_set_directory_sort_reversed (FMIconView *icon_view, + CajaFile *file, + gboolean sort_reversed) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return; + } + + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, + set_directory_sort_reversed, + (icon_view, file, sort_reversed)); +} + +static void +fm_icon_view_real_set_directory_sort_reversed (FMIconView *icon_view, + CajaFile *file, + gboolean sort_reversed) +{ + gboolean reversed; + + get_default_sort_order (file, &reversed); + caja_file_set_boolean_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, + reversed, sort_reversed); +} + +static gboolean +get_default_directory_keep_aligned (void) +{ + return TRUE; +} + +static gboolean +fm_icon_view_get_directory_keep_aligned (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_keep_aligned (icon_view)) + { + return FALSE; + } + + return caja_file_get_boolean_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED, + get_default_directory_keep_aligned ()); +} + +static void +fm_icon_view_set_directory_keep_aligned (FMIconView *icon_view, + CajaFile *file, + gboolean keep_aligned) +{ + if (!fm_icon_view_supports_keep_aligned (icon_view)) + { + return; + } + + caja_file_set_boolean_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED, + get_default_directory_keep_aligned (), + keep_aligned); +} + +/* maintainence of auto layout boolean */ +static gboolean default_directory_manual_layout = FALSE; + +static gboolean +get_default_directory_manual_layout (void) +{ + static gboolean auto_storaged_added = FALSE; + + if (auto_storaged_added == FALSE) + { + auto_storaged_added = TRUE; + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT, + &default_directory_manual_layout); + } + + return default_directory_manual_layout; +} + +static gboolean +fm_icon_view_get_directory_auto_layout (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return FALSE; + } + + if (!fm_icon_view_supports_manual_layout (icon_view)) + { + return TRUE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, icon_view, + get_directory_auto_layout, (icon_view, file)); +} + +static gboolean +fm_icon_view_real_get_directory_auto_layout (FMIconView *icon_view, + CajaFile *file) +{ + + + return caja_file_get_boolean_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT, !get_default_directory_manual_layout ()); +} + +static void +fm_icon_view_set_directory_auto_layout (FMIconView *icon_view, + CajaFile *file, + gboolean auto_layout) +{ + if (!fm_icon_view_supports_auto_layout (icon_view) || + !fm_icon_view_supports_manual_layout (icon_view)) + { + return; + } + + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, + set_directory_auto_layout, (icon_view, file, auto_layout)); +} + +static void +fm_icon_view_real_set_directory_auto_layout (FMIconView *icon_view, + CajaFile *file, + gboolean auto_layout) +{ + if (!fm_icon_view_supports_manual_layout (icon_view)) + { + return; + } + + caja_file_set_boolean_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT, + !get_default_directory_manual_layout (), + auto_layout); +} +/* maintainence of tighter layout boolean */ + +static gboolean +fm_icon_view_get_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file) +{ + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, icon_view, + get_directory_tighter_layout, (icon_view, file)); +} + +static gboolean default_directory_tighter_layout = FALSE; + +static gboolean +get_default_directory_tighter_layout (void) +{ + static gboolean auto_storaged_added = FALSE; + + if (auto_storaged_added == FALSE) + { + auto_storaged_added = TRUE; + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT, + &default_directory_tighter_layout); + } + + return default_directory_tighter_layout; +} + +static gboolean +fm_icon_view_real_get_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_tighter_layout (icon_view)) + { + return FALSE; + } + + return caja_file_get_boolean_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT, + get_default_directory_tighter_layout ()); +} + +static void +fm_icon_view_set_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file, + gboolean tighter_layout) +{ + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, + set_directory_tighter_layout, (icon_view, file, tighter_layout)); +} + +static void +fm_icon_view_real_set_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file, + gboolean tighter_layout) +{ + if (!fm_icon_view_supports_tighter_layout (icon_view)) + { + return; + } + + caja_file_set_boolean_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT, + get_default_directory_tighter_layout (), + tighter_layout); +} + +static gboolean +real_supports_auto_layout (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return TRUE; +} + +static gboolean +real_supports_scaling (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return FALSE; +} + +static gboolean +real_supports_manual_layout (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return !fm_icon_view_is_compact (view); +} + +static gboolean +real_supports_keep_aligned (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return FALSE; +} + +static gboolean +real_supports_labels_beside_icons (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), TRUE); + + return TRUE; +} + +static gboolean +set_sort_reversed (FMIconView *icon_view, gboolean new_value) +{ + if (icon_view->details->sort_reversed == new_value) + { + return FALSE; + } + icon_view->details->sort_reversed = new_value; + + /* Store the new sort setting. */ + fm_icon_view_set_directory_sort_reversed (icon_view, fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)), new_value); + + /* Update the layout menus to match the new sort-order setting. */ + update_layout_menus (icon_view); + + return TRUE; +} + +static const SortCriterion * +get_sort_criterion_by_metadata_text (const char *metadata_text) +{ + guint i; + + /* Figure out what the new sort setting should be. */ + for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++) + { + if (strcmp (sort_criteria[i].metadata_text, metadata_text) == 0) + { + return &sort_criteria[i]; + } + } + return NULL; +} + +static const SortCriterion * +get_sort_criterion_by_sort_type (CajaFileSortType sort_type) +{ + guint i; + + /* Figure out what the new sort setting should be. */ + for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++) + { + if (sort_type == sort_criteria[i].sort_type) + { + return &sort_criteria[i]; + } + } + + return NULL; +} + +static CajaZoomLevel default_zoom_level = CAJA_ZOOM_LEVEL_STANDARD; +static CajaZoomLevel default_compact_zoom_level = CAJA_ZOOM_LEVEL_STANDARD; +#define DEFAULT_ZOOM_LEVEL(icon_view) icon_view->details->compact ? default_compact_zoom_level : default_zoom_level + +static CajaZoomLevel +get_default_zoom_level (FMIconView *icon_view) +{ + static gboolean auto_storage_added = FALSE; + + if (!auto_storage_added) + { + auto_storage_added = TRUE; + eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, + (int *) &default_zoom_level); + eel_preferences_add_auto_enum (CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL, + (int *) &default_compact_zoom_level); + } + + return CLAMP (DEFAULT_ZOOM_LEVEL(icon_view), CAJA_ZOOM_LEVEL_SMALLEST, CAJA_ZOOM_LEVEL_LARGEST); +} + +static void +set_labels_beside_icons (FMIconView *icon_view) +{ + gboolean labels_beside; + + if (fm_icon_view_supports_labels_beside_icons (icon_view)) + { + labels_beside = fm_icon_view_is_compact (icon_view) || + eel_preferences_get_boolean (CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS); + + if (labels_beside) + { + caja_icon_container_set_label_position + (get_icon_container (icon_view), + CAJA_ICON_LABEL_POSITION_BESIDE); + } + else + { + caja_icon_container_set_label_position + (get_icon_container (icon_view), + CAJA_ICON_LABEL_POSITION_UNDER); + } + } +} + +static void +set_columns_same_width (FMIconView *icon_view) +{ + gboolean all_columns_same_width; + + if (fm_icon_view_is_compact (icon_view)) + { + all_columns_same_width = eel_preferences_get_boolean (CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH); + caja_icon_container_set_all_columns_same_width (get_icon_container (icon_view), all_columns_same_width); + } +} + +static void +fm_icon_view_begin_loading (FMDirectoryView *view) +{ + FMIconView *icon_view; + GtkWidget *icon_container; + CajaFile *file; + int level; + char *sort_name; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_view = FM_ICON_VIEW (view); + file = fm_directory_view_get_directory_as_file (view); + icon_container = GTK_WIDGET (get_icon_container (icon_view)); + + caja_icon_container_begin_loading (CAJA_ICON_CONTAINER (icon_container)); + + caja_icon_container_set_allow_moves (CAJA_ICON_CONTAINER (icon_container), + fm_directory_view_get_allow_moves (view)); + + /* kill any sound preview process that is ongoing */ + preview_audio (icon_view, NULL, FALSE); + + /* FIXME bugzilla.gnome.org 45060: Should use methods instead + * of hardcoding desktop knowledge in here. + */ + if (FM_IS_DESKTOP_ICON_VIEW (view)) + { + caja_connect_desktop_background_to_file_metadata (CAJA_ICON_CONTAINER (icon_container), file); + } + else + { + GdkDragAction default_action; + + if (caja_window_info_get_window_type (fm_directory_view_get_caja_window (view)) == CAJA_WINDOW_NAVIGATION) + { + default_action = CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND; + } + else + { + default_action = CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND; + } + + caja_connect_background_to_file_metadata + (icon_container, + file, + default_action); + } + + + /* Set up the zoom level from the metadata. */ + if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (icon_view))) + { + if (icon_view->details->compact) + { + level = caja_file_get_integer_metadata + (file, + CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, + get_default_zoom_level (icon_view)); + } + else + { + level = caja_file_get_integer_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, + get_default_zoom_level (icon_view)); + } + + fm_icon_view_set_zoom_level (icon_view, level, TRUE); + } + + /* Set the sort mode. + * It's OK not to resort the icons because the + * container doesn't have any icons at this point. + */ + sort_name = fm_icon_view_get_directory_sort_by (icon_view, file); + set_sort_criterion (icon_view, get_sort_criterion_by_metadata_text (sort_name)); + g_free (sort_name); + + /* Set the sort direction from the metadata. */ + set_sort_reversed (icon_view, fm_icon_view_get_directory_sort_reversed (icon_view, file)); + + caja_icon_container_set_keep_aligned + (get_icon_container (icon_view), + fm_icon_view_get_directory_keep_aligned (icon_view, file)); + caja_icon_container_set_tighter_layout + (get_icon_container (icon_view), + fm_icon_view_get_directory_tighter_layout (icon_view, file)); + + set_labels_beside_icons (icon_view); + set_columns_same_width (icon_view); + + /* We must set auto-layout last, because it invokes the layout_changed + * callback, which works incorrectly if the other layout criteria are + * not already set up properly (see bug 6500, e.g.) + */ + caja_icon_container_set_auto_layout + (get_icon_container (icon_view), + fm_icon_view_get_directory_auto_layout (icon_view, file)); + + /* e.g. keep aligned may have changed */ + update_layout_menus (icon_view); +} + +static void +icon_view_notify_clipboard_info (CajaClipboardMonitor *monitor, + CajaClipboardInfo *info, + FMIconView *icon_view) +{ + GList *icon_data; + + icon_data = NULL; + if (info && info->cut) + { + icon_data = info->files; + } + + caja_icon_container_set_highlighted_for_clipboard ( + get_icon_container (icon_view), icon_data); +} + +static void +fm_icon_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ + FMIconView *icon_view; + GtkWidget *icon_container; + CajaClipboardMonitor *monitor; + CajaClipboardInfo *info; + + icon_view = FM_ICON_VIEW (view); + + icon_container = GTK_WIDGET (get_icon_container (icon_view)); + caja_icon_container_end_loading (CAJA_ICON_CONTAINER (icon_container), all_files_seen); + + monitor = caja_clipboard_monitor_get (); + info = caja_clipboard_monitor_get_clipboard_info (monitor); + + icon_view_notify_clipboard_info (monitor, info, icon_view); +} + +static CajaZoomLevel +fm_icon_view_get_zoom_level (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + + return caja_icon_container_get_zoom_level (get_icon_container (FM_ICON_VIEW (view))); +} + +static void +fm_icon_view_set_zoom_level (FMIconView *view, + CajaZoomLevel new_level, + gboolean always_emit) +{ + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST); + + icon_container = get_icon_container (view); + if (caja_icon_container_get_zoom_level (icon_container) == new_level) + { + if (always_emit) + { + g_signal_emit_by_name (view, "zoom_level_changed"); + } + return; + } + + if (view->details->compact) + { + caja_file_set_integer_metadata + (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), + CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, + get_default_zoom_level (view), + new_level); + } + else + { + caja_file_set_integer_metadata + (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), + CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, + get_default_zoom_level (view), + new_level); + } + + caja_icon_container_set_zoom_level (icon_container, new_level); + + g_signal_emit_by_name (view, "zoom_level_changed"); + + if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (view))) + { + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); + } +} + +static void +fm_icon_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ + FMIconView *icon_view; + CajaZoomLevel new_level; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_view = FM_ICON_VIEW (view); + new_level = fm_icon_view_get_zoom_level (view) + zoom_increment; + + if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST) + { + fm_directory_view_zoom_to_level (view, new_level); + } +} + +static void +fm_icon_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ + FMIconView *icon_view; + + g_assert (FM_IS_ICON_VIEW (view)); + + icon_view = FM_ICON_VIEW (view); + fm_icon_view_set_zoom_level (icon_view, zoom_level, FALSE); +} + +static void +fm_icon_view_restore_default_zoom_level (FMDirectoryView *view) +{ + FMIconView *icon_view; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_view = FM_ICON_VIEW (view); + fm_directory_view_zoom_to_level + (view, get_default_zoom_level (icon_view)); +} + +static gboolean +fm_icon_view_can_zoom_in (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return fm_icon_view_get_zoom_level (view) + < CAJA_ZOOM_LEVEL_LARGEST; +} + +static gboolean +fm_icon_view_can_zoom_out (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return fm_icon_view_get_zoom_level (view) + > CAJA_ZOOM_LEVEL_SMALLEST; +} + +static GtkWidget * +fm_icon_view_get_background_widget (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + + return GTK_WIDGET (get_icon_container (FM_ICON_VIEW (view))); +} + +static gboolean +fm_icon_view_is_empty (FMDirectoryView *view) +{ + g_assert (FM_IS_ICON_VIEW (view)); + + return caja_icon_container_is_empty + (get_icon_container (FM_ICON_VIEW (view))); +} + +static GList * +fm_icon_view_get_selection (FMDirectoryView *view) +{ + GList *list; + + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + + list = caja_icon_container_get_selection + (get_icon_container (FM_ICON_VIEW (view))); + caja_file_list_ref (list); + return list; +} + +static void +count_item (CajaIconData *icon_data, + gpointer callback_data) +{ + guint *count; + + count = callback_data; + (*count)++; +} + +static guint +fm_icon_view_get_item_count (FMDirectoryView *view) +{ + guint count; + + g_return_val_if_fail (FM_IS_ICON_VIEW (view), 0); + + count = 0; + + caja_icon_container_for_each + (get_icon_container (FM_ICON_VIEW (view)), + count_item, &count); + + return count; +} + +static void +set_sort_criterion_by_sort_type (FMIconView *icon_view, + CajaFileSortType sort_type) +{ + const SortCriterion *sort; + + g_assert (FM_IS_ICON_VIEW (icon_view)); + + sort = get_sort_criterion_by_sort_type (sort_type); + g_return_if_fail (sort != NULL); + + if (sort == icon_view->details->sort + && fm_icon_view_using_auto_layout (icon_view)) + { + return; + } + + set_sort_criterion (icon_view, sort); + caja_icon_container_sort (get_icon_container (icon_view)); + fm_icon_view_reveal_selection (FM_DIRECTORY_VIEW (icon_view)); +} + + +static void +action_reversed_order_callback (GtkAction *action, + gpointer user_data) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (user_data); + + if (set_sort_reversed (icon_view, + gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))) + { + caja_icon_container_sort (get_icon_container (icon_view)); + fm_icon_view_reveal_selection (FM_DIRECTORY_VIEW (icon_view)); + } +} + +static void +action_keep_aligned_callback (GtkAction *action, + gpointer user_data) +{ + FMIconView *icon_view; + CajaFile *file; + gboolean keep_aligned; + + icon_view = FM_ICON_VIEW (user_data); + + keep_aligned = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + fm_icon_view_set_directory_keep_aligned (icon_view, + file, + keep_aligned); + + caja_icon_container_set_keep_aligned (get_icon_container (icon_view), + keep_aligned); +} + +static void +switch_to_manual_layout (FMIconView *icon_view) +{ + if (!fm_icon_view_using_auto_layout (icon_view)) + { + return; + } + + icon_view->details->sort = &sort_criteria[0]; + + caja_icon_container_set_auto_layout + (get_icon_container (icon_view), FALSE); +} + +static void +layout_changed_callback (CajaIconContainer *container, + FMIconView *icon_view) +{ + CajaFile *file; + + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + + if (file != NULL) + { + fm_icon_view_set_directory_auto_layout + (icon_view, + file, + fm_icon_view_using_auto_layout (icon_view)); + fm_icon_view_set_directory_tighter_layout + (icon_view, + file, + fm_icon_view_using_tighter_layout (icon_view)); + } + + update_layout_menus (icon_view); +} + +static gboolean +fm_icon_view_can_rename_file (FMDirectoryView *view, CajaFile *file) +{ + if (!(fm_icon_view_get_zoom_level (view) > CAJA_ZOOM_LEVEL_SMALLEST)) + { + return FALSE; + } + + return FM_DIRECTORY_VIEW_CLASS(fm_icon_view_parent_class)->can_rename_file (view, file); +} + +static void +fm_icon_view_start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ + /* call parent class to make sure the right icon is selected */ + FM_DIRECTORY_VIEW_CLASS(fm_icon_view_parent_class)->start_renaming_file (view, file, select_all); + + /* start renaming */ + caja_icon_container_start_renaming_selected_item + (get_icon_container (FM_ICON_VIEW (view)), select_all); +} + +static const GtkActionEntry icon_view_entries[] = +{ + /* name, stock id, label */ { "Arrange Items", NULL, N_("Arran_ge Items") }, + /* name, stock id */ { "Stretch", NULL, + /* label, accelerator */ N_("Resize Icon..."), NULL, + /* tooltip */ N_("Make the selected icon resizable"), + G_CALLBACK (action_stretch_callback) + }, + /* name, stock id */ { "Unstretch", NULL, + /* label, accelerator */ N_("Restore Icons' Original Si_zes"), NULL, + /* tooltip */ N_("Restore each selected icon to its original size"), + G_CALLBACK (action_unstretch_callback) + }, + /* name, stock id */ { "Clean Up", NULL, + /* label, accelerator */ N_("_Organize by Name"), NULL, + /* tooltip */ N_("Reposition icons to better fit in the window and avoid overlapping"), + G_CALLBACK (action_clean_up_callback) + }, +}; + +static const GtkToggleActionEntry icon_view_toggle_entries[] = +{ + /* name, stock id */ { "Tighter Layout", NULL, + /* label, accelerator */ N_("Compact _Layout"), NULL, + /* tooltip */ N_("Toggle using a tighter layout scheme"), + G_CALLBACK (action_tighter_layout_callback), + 0 + }, + /* name, stock id */ { "Reversed Order", NULL, + /* label, accelerator */ N_("Re_versed Order"), NULL, + /* tooltip */ N_("Display icons in the opposite order"), + G_CALLBACK (action_reversed_order_callback), + 0 + }, + /* name, stock id */ { "Keep Aligned", NULL, + /* label, accelerator */ N_("_Keep Aligned"), NULL, + /* tooltip */ N_("Keep icons lined up on a grid"), + G_CALLBACK (action_keep_aligned_callback), + 0 + }, +}; + +static const GtkRadioActionEntry arrange_radio_entries[] = +{ + { + "Manual Layout", NULL, + N_("_Manually"), NULL, + N_("Leave icons wherever they are dropped"), + CAJA_FILE_SORT_NONE + }, + { + "Sort by Name", NULL, + N_("By _Name"), NULL, + N_("Keep icons sorted by name in rows"), + CAJA_FILE_SORT_BY_DISPLAY_NAME + }, + { + "Sort by Size", NULL, + N_("By _Size"), NULL, + N_("Keep icons sorted by size in rows"), + CAJA_FILE_SORT_BY_SIZE + }, + { + "Sort by Type", NULL, + N_("By _Type"), NULL, + N_("Keep icons sorted by type in rows"), + CAJA_FILE_SORT_BY_TYPE + }, + { + "Sort by Modification Date", NULL, + N_("By Modification _Date"), NULL, + N_("Keep icons sorted by modification date in rows"), + CAJA_FILE_SORT_BY_MTIME + }, + { + "Sort by Emblems", NULL, + N_("By _Emblems"), NULL, + N_("Keep icons sorted by emblems in rows"), + CAJA_FILE_SORT_BY_EMBLEMS + }, + { + "Sort by Trash Time", NULL, + N_("By T_rash Time"), NULL, + N_("Keep icons sorted by trash time in rows"), + CAJA_FILE_SORT_BY_TRASHED_TIME + }, +}; + +static void +fm_icon_view_merge_menus (FMDirectoryView *view) +{ + FMIconView *icon_view; + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + GtkAction *action; + const char *ui; + + g_assert (FM_IS_ICON_VIEW (view)); + + FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->merge_menus (view); + + icon_view = FM_ICON_VIEW (view); + + ui_manager = fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (icon_view)); + + action_group = gtk_action_group_new ("IconViewActions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + icon_view->details->icon_action_group = action_group; + gtk_action_group_add_actions (action_group, + icon_view_entries, G_N_ELEMENTS (icon_view_entries), + icon_view); + gtk_action_group_add_toggle_actions (action_group, + icon_view_toggle_entries, G_N_ELEMENTS (icon_view_toggle_entries), + icon_view); + gtk_action_group_add_radio_actions (action_group, + arrange_radio_entries, + G_N_ELEMENTS (arrange_radio_entries), + -1, + G_CALLBACK (action_sort_radio_callback), + icon_view); + + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); /* owned by ui manager */ + + ui = caja_ui_string_get ("caja-icon-view-ui.xml"); + icon_view->details->icon_merge_id = + gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + + /* Do one-time state-setting here; context-dependent state-setting + * is done in update_menus. + */ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + action = gtk_action_group_get_action (action_group, + FM_ACTION_ARRANGE_ITEMS); + gtk_action_set_visible (action, FALSE); + } + + if (fm_icon_view_supports_scaling (icon_view)) + { + gtk_ui_manager_add_ui (ui_manager, + icon_view->details->icon_merge_id, + POPUP_PATH_ICON_APPEARANCE, + FM_ACTION_STRETCH, + FM_ACTION_STRETCH, + GTK_UI_MANAGER_MENUITEM, + FALSE); + gtk_ui_manager_add_ui (ui_manager, + icon_view->details->icon_merge_id, + POPUP_PATH_ICON_APPEARANCE, + FM_ACTION_UNSTRETCH, + FM_ACTION_UNSTRETCH, + GTK_UI_MANAGER_MENUITEM, + FALSE); + } + + update_layout_menus (icon_view); +} + +static void +fm_icon_view_unmerge_menus (FMDirectoryView *view) +{ + FMIconView *icon_view; + GtkUIManager *ui_manager; + + icon_view = FM_ICON_VIEW (view); + + FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->unmerge_menus (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + if (ui_manager != NULL) + { + caja_ui_unmerge_ui (ui_manager, + &icon_view->details->icon_merge_id, + &icon_view->details->icon_action_group); + } +} + +static void +fm_icon_view_update_menus (FMDirectoryView *view) +{ + FMIconView *icon_view; + GList *selection; + int selection_count; + GtkAction *action; + CajaIconContainer *icon_container; + gboolean editable; + + icon_view = FM_ICON_VIEW (view); + + FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->update_menus(view); + + selection = fm_directory_view_get_selection (view); + selection_count = g_list_length (selection); + icon_container = get_icon_container (icon_view); + + action = gtk_action_group_get_action (icon_view->details->icon_action_group, + FM_ACTION_STRETCH); + gtk_action_set_sensitive (action, + selection_count == 1 + && icon_container != NULL + && !caja_icon_container_has_stretch_handles (icon_container)); + + gtk_action_set_visible (action, + fm_icon_view_supports_scaling (icon_view)); + + action = gtk_action_group_get_action (icon_view->details->icon_action_group, + FM_ACTION_UNSTRETCH); + g_object_set (action, "label", + eel_g_list_more_than_one_item (selection) + ? _("Restore Icons' Original Si_zes") + : _("Restore Icon's Original Si_ze"), + NULL); + gtk_action_set_sensitive (action, + icon_container != NULL + && caja_icon_container_is_stretched (icon_container)); + + gtk_action_set_visible (action, + fm_icon_view_supports_scaling (icon_view)); + + caja_file_list_free (selection); + + editable = fm_directory_view_is_editable (view); + action = gtk_action_group_get_action (icon_view->details->icon_action_group, + FM_ACTION_MANUAL_LAYOUT); + gtk_action_set_sensitive (action, editable); +} + +static void +fm_icon_view_reset_to_defaults (FMDirectoryView *view) +{ + CajaIconContainer *icon_container; + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (view); + icon_container = get_icon_container (icon_view); + + clear_sort_criterion (icon_view); + caja_icon_container_set_keep_aligned + (icon_container, get_default_directory_keep_aligned ()); + caja_icon_container_set_tighter_layout + (icon_container, get_default_directory_tighter_layout ()); + + caja_icon_container_sort (icon_container); + + /* Switch to manual layout of the default calls for it. + * This needs to happen last for the sort order menus + * to be in sync. + */ + if (get_default_directory_manual_layout ()) + { + switch_to_manual_layout (icon_view); + } + + update_layout_menus (icon_view); + + fm_icon_view_restore_default_zoom_level (view); +} + +static void +fm_icon_view_select_all (FMDirectoryView *view) +{ + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_container = get_icon_container (FM_ICON_VIEW (view)); + caja_icon_container_select_all (icon_container); +} + +static void +fm_icon_view_reveal_selection (FMDirectoryView *view) +{ + GList *selection; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + /* Make sure at least one of the selected items is scrolled into view */ + if (selection != NULL) + { + caja_icon_container_reveal + (get_icon_container (FM_ICON_VIEW (view)), + selection->data); + } + + caja_file_list_free (selection); +} + +static GArray * +fm_icon_view_get_selected_icon_locations (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + + return caja_icon_container_get_selected_icon_locations + (get_icon_container (FM_ICON_VIEW (view))); +} + + +static void +fm_icon_view_set_selection (FMDirectoryView *view, GList *selection) +{ + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + caja_icon_container_set_selection + (get_icon_container (FM_ICON_VIEW (view)), selection); +} + +static void +fm_icon_view_invert_selection (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + caja_icon_container_invert_selection + (get_icon_container (FM_ICON_VIEW (view))); +} + +static gboolean +fm_icon_view_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return !fm_icon_view_using_auto_layout (FM_ICON_VIEW (view)); +} + +static void +fm_icon_view_widget_to_file_operation_position (FMDirectoryView *view, + GdkPoint *position) +{ + g_assert (FM_IS_ICON_VIEW (view)); + + caja_icon_container_widget_to_file_operation_position + (get_icon_container (FM_ICON_VIEW (view)), position); +} + +static void +icon_container_activate_callback (CajaIconContainer *container, + GList *file_list, + FMIconView *icon_view) +{ + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, 0, + TRUE); +} + +static void +icon_container_activate_alternate_callback (CajaIconContainer *container, + GList *file_list, + FMIconView *icon_view) +{ + GdkEvent *event; + GdkEventButton *button_event; + GdkEventKey *key_event; + gboolean open_in_tab; + CajaWindowInfo *window_info; + CajaWindowOpenFlags flags; + + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + open_in_tab = FALSE; + + window_info = fm_directory_view_get_caja_window (FM_DIRECTORY_VIEW (icon_view)); + + if (caja_window_info_get_window_type (window_info) == CAJA_WINDOW_NAVIGATION) + { + event = gtk_get_current_event (); + if (event->type == GDK_BUTTON_PRESS || + event->type == GDK_BUTTON_RELEASE || + event->type == GDK_2BUTTON_PRESS || + event->type == GDK_3BUTTON_PRESS) + { + button_event = (GdkEventButton *) event; + open_in_tab = (button_event->state & GDK_SHIFT_MASK) == 0; + } + else if (event->type == GDK_KEY_PRESS || + event->type == GDK_KEY_RELEASE) + { + key_event = (GdkEventKey *) event; + open_in_tab = !((key_event->state & GDK_SHIFT_MASK) != 0 && + (key_event->state & GDK_CONTROL_MASK) != 0); + } + else + { + open_in_tab = TRUE; + } + } + + flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND; + if (open_in_tab) + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB; + } + else + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW; + } + + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + flags, + TRUE); +} + +static void +band_select_started_callback (CajaIconContainer *container, + FMIconView *icon_view) +{ + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + fm_directory_view_start_batching_selection_changes (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +band_select_ended_callback (CajaIconContainer *container, + FMIconView *icon_view) +{ + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + fm_directory_view_stop_batching_selection_changes (FM_DIRECTORY_VIEW (icon_view)); +} + +/* handle the preview signal by inspecting the mime type. For now, we only preview local sound files. */ + +static char ** +get_preview_argv (char *uri) +{ + char *command; + char **argv; + int i; + + command = g_find_program_in_path ("totem-audio-preview"); + if (command) + { + argv = g_new (char *, 3); + argv[0] = command; + argv[1] = g_strdup (uri); + argv[2] = NULL; + + return argv; + } + + command = g_find_program_in_path ("gst-launch-0.10"); + if (command) + { + argv = g_new (char *, 10); + i = 0; + argv[i++] = command; + argv[i++] = g_strdup ("playbin"); + argv[i++] = g_strconcat ("uri=", uri, NULL); + /* do not display videos */ + argv[i++] = g_strdup ("current-video=-1"); + argv[i++] = NULL; + return argv; + } + + return NULL; +} + +static void +audio_child_died (GPid pid, + gint status, + gpointer data) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (data); + + icon_view->details->audio_preview_child_watch = 0; + icon_view->details->audio_preview_child_pid = 0; +} + +/* here's the timer task that actually plays the file using mpg123, ogg123 or play. */ +/* FIXME bugzilla.gnome.org 41258: we should get the application from our mime-type stuff */ +static gboolean +play_file (gpointer callback_data) +{ + CajaFile *file; + FMIconView *icon_view; + GPid child_pid; + char **argv; + GError *error; + char *uri; + + icon_view = FM_ICON_VIEW (callback_data); + + /* Stop timeout */ + icon_view->details->audio_preview_timeout = 0; + + file = icon_view->details->audio_preview_file; + uri = caja_file_get_uri (file); + argv = get_preview_argv (uri); + g_free (uri); + if (argv == NULL) + { + return FALSE; + } + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, + argv, + NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + NULL, + NULL /* user_data */, + &child_pid, + NULL, NULL, NULL, + &error)) + { + g_strfreev (argv); + g_warning ("Error spawning sound preview: %s\n", error->message); + g_error_free (error); + return FALSE; + } + g_strfreev (argv); + + icon_view->details->audio_preview_child_watch = + g_child_watch_add (child_pid, + audio_child_died, NULL); + icon_view->details->audio_preview_child_pid = child_pid; + + return FALSE; +} + +/* FIXME bugzilla.gnome.org 42530: Hardcoding this here sucks. We should be using components + * for open ended things like this. + */ + +/* this routine is invoked from the preview signal handler to preview a sound file. We + want to wait a suitable delay until we actually do it, so set up a timer task to actually + start playing. If we move out before the task files, we remove it. */ + +static void +preview_audio (FMIconView *icon_view, CajaFile *file, gboolean start_flag) +{ + /* Stop current audio playback */ + if (icon_view->details->audio_preview_child_pid != 0) + { + kill (icon_view->details->audio_preview_child_pid, SIGTERM); + g_source_remove (icon_view->details->audio_preview_child_watch); + waitpid (icon_view->details->audio_preview_child_pid, NULL, 0); + icon_view->details->audio_preview_child_pid = 0; + } + + if (icon_view->details->audio_preview_timeout != 0) + { + g_source_remove (icon_view->details->audio_preview_timeout); + icon_view->details->audio_preview_timeout = 0; + } + + if (start_flag) + { + icon_view->details->audio_preview_file = file; + icon_view->details->audio_preview_timeout = g_timeout_add_seconds (1, play_file, icon_view); + } +} + +static gboolean +sound_preview_type_supported (CajaFile *file) +{ + char *mime_type; + guint i; + + mime_type = caja_file_get_mime_type (file); + if (mime_type == NULL) + { + return FALSE; + } + for (i = 0; i < G_N_ELEMENTS (audio_mime_types); i++) + { + if (g_content_type_is_a (mime_type, audio_mime_types[i])) + { + g_free (mime_type); + return TRUE; + } + } + + g_free (mime_type); + return FALSE; +} + + +static gboolean +should_preview_sound (CajaFile *file) +{ + GFile *location; + GFilesystemPreviewType use_preview; + + use_preview = caja_file_get_filesystem_use_preview (file); + + location = caja_file_get_location (file); + if (g_file_has_uri_scheme (location, "burn")) + { + g_object_unref (location); + return FALSE; + } + g_object_unref (location); + + /* Check user performance preference */ + if (preview_sound_auto_value == CAJA_SPEED_TRADEOFF_NEVER) + { + return FALSE; + } + + if (preview_sound_auto_value == CAJA_SPEED_TRADEOFF_ALWAYS) + { + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) + { + return FALSE; + } + else + { + return TRUE; + } + } + + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) + { + /* file system says to never preview anything */ + return FALSE; + } + else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL) + { + /* file system says we should treat file as if it's local */ + return TRUE; + } + else + { + /* only local files */ + return caja_file_is_local (file); + } +} + +static int +icon_container_preview_callback (CajaIconContainer *container, + CajaFile *file, + gboolean start_flag, + FMIconView *icon_view) +{ + int result; + char *file_name, *message; + + result = 0; + + /* preview files based on the mime_type. */ + /* at first, we just handle sounds */ + if (should_preview_sound (file)) + { + if (sound_preview_type_supported (file)) + { + result = 1; + preview_audio (icon_view, file, start_flag); + } + } + + /* Display file name in status area at low zoom levels, since + * the name is not displayed or hard to read in the icon view. + */ + if (fm_icon_view_get_zoom_level (FM_DIRECTORY_VIEW (icon_view)) <= CAJA_ZOOM_LEVEL_SMALLER) + { + if (start_flag) + { + file_name = caja_file_get_display_name (file); + message = g_strdup_printf (_("pointing at \"%s\""), file_name); + g_free (file_name); + caja_window_slot_info_set_status + (fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (icon_view)), + message); + g_free (message); + } + else + { + fm_directory_view_display_selection_info (FM_DIRECTORY_VIEW(icon_view)); + } + } + + return result; +} + +static void +renaming_icon_callback (CajaIconContainer *container, + GtkWidget *widget, + gpointer callback_data) +{ + FMDirectoryView *directory_view; + + directory_view = FM_DIRECTORY_VIEW (callback_data); + caja_clipboard_set_up_editable + (GTK_EDITABLE (widget), + fm_directory_view_get_ui_manager (directory_view), + FALSE); +} + +int +fm_icon_view_compare_files (FMIconView *icon_view, + CajaFile *a, + CajaFile *b) +{ + return caja_file_compare_for_sort + (a, b, icon_view->details->sort->sort_type, + /* Use type-unsafe cast for performance */ + fm_directory_view_should_sort_directories_first ((FMDirectoryView *)icon_view), + icon_view->details->sort_reversed); +} + +static int +compare_files (FMDirectoryView *icon_view, + CajaFile *a, + CajaFile *b) +{ + return fm_icon_view_compare_files ((FMIconView *)icon_view, a, b); +} + + +void +fm_icon_view_filter_by_screen (FMIconView *icon_view, + gboolean filter) +{ + icon_view->details->filter_by_screen = filter; + icon_view->details->num_screens = gdk_display_get_n_screens (gtk_widget_get_display (GTK_WIDGET (icon_view))); +} + +static void +fm_icon_view_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen) +{ + FMDirectoryView *view; + GList *files, *l; + CajaFile *file; + CajaDirectory *directory; + CajaIconContainer *icon_container; + + if (GTK_WIDGET_CLASS (fm_icon_view_parent_class)->screen_changed) + { + GTK_WIDGET_CLASS (fm_icon_view_parent_class)->screen_changed (widget, previous_screen); + } + + view = FM_DIRECTORY_VIEW (widget); + if (FM_ICON_VIEW (view)->details->filter_by_screen) + { + icon_container = get_icon_container (FM_ICON_VIEW (view)); + + directory = fm_directory_view_get_model (view); + files = caja_directory_get_file_list (directory); + + for (l = files; l != NULL; l = l->next) + { + file = l->data; + + if (!should_show_file_on_screen (view, file)) + { + fm_icon_view_remove_file (view, file, directory); + } + else + { + if (caja_icon_container_add (icon_container, + CAJA_ICON_CONTAINER_ICON_DATA (file))) + { + caja_file_ref (file); + } + } + } + + caja_file_list_unref (files); + g_list_free (files); + } +} + +static gboolean +fm_icon_view_scroll_event (GtkWidget *widget, + GdkEventScroll *scroll_event) +{ + FMIconView *icon_view; + GdkEvent *event_copy; + GdkEventScroll *scroll_event_copy; + gboolean ret; + + icon_view = FM_ICON_VIEW (widget); + + if (icon_view->details->compact && + (scroll_event->direction == GDK_SCROLL_UP || + scroll_event->direction == GDK_SCROLL_DOWN)) + { + ret = fm_directory_view_handle_scroll_event (FM_DIRECTORY_VIEW (icon_view), scroll_event); + if (!ret) + { + /* in column-wise layout, re-emit vertical mouse scroll events as horizontal ones, + * if they don't bump zoom */ + event_copy = gdk_event_copy ((GdkEvent *) scroll_event); + + scroll_event_copy = (GdkEventScroll *) event_copy; + if (scroll_event_copy->direction == GDK_SCROLL_UP) + { + scroll_event_copy->direction = GDK_SCROLL_LEFT; + } + else + { + scroll_event_copy->direction = GDK_SCROLL_RIGHT; + } + + ret = gtk_widget_event (widget, event_copy); + gdk_event_free (event_copy); + } + + return ret; + } + + return GTK_WIDGET_CLASS (fm_icon_view_parent_class)->scroll_event (widget, scroll_event); +} + +static void +selection_changed_callback (CajaIconContainer *container, + FMIconView *icon_view) +{ + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + fm_directory_view_notify_selection_changed (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +icon_container_context_click_selection_callback (CajaIconContainer *container, + GdkEventButton *event, + FMIconView *icon_view) +{ + g_assert (CAJA_IS_ICON_CONTAINER (container)); + g_assert (FM_IS_ICON_VIEW (icon_view)); + + fm_directory_view_pop_up_selection_context_menu + (FM_DIRECTORY_VIEW (icon_view), event); +} + +static void +icon_container_context_click_background_callback (CajaIconContainer *container, + GdkEventButton *event, + FMIconView *icon_view) +{ + g_assert (CAJA_IS_ICON_CONTAINER (container)); + g_assert (FM_IS_ICON_VIEW (icon_view)); + + fm_directory_view_pop_up_background_context_menu + (FM_DIRECTORY_VIEW (icon_view), event); +} + +static gboolean +fm_icon_view_react_to_icon_change_idle_callback (gpointer data) +{ + FMIconView *icon_view; + + g_assert (FM_IS_ICON_VIEW (data)); + + icon_view = FM_ICON_VIEW (data); + icon_view->details->react_to_icon_change_idle_id = 0; + + /* Rebuild the menus since some of them (e.g. Restore Stretched Icons) + * may be different now. + */ + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (icon_view)); + + /* Don't call this again (unless rescheduled) */ + return FALSE; +} + +static void +icon_position_changed_callback (CajaIconContainer *container, + CajaFile *file, + const CajaIconPosition *position, + FMIconView *icon_view) +{ + char *position_string; + char scale_string[G_ASCII_DTOSTR_BUF_SIZE]; + + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + g_assert (CAJA_IS_FILE (file)); + + /* Schedule updating menus for the next idle. Doing it directly here + * noticeably slows down icon stretching. The other work here to + * store the icon position and scale does not seem to noticeably + * slow down icon stretching. It would be trickier to move to an + * idle call, because we'd have to keep track of potentially multiple + * sets of file/geometry info. + */ + if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (icon_view)) && + icon_view->details->react_to_icon_change_idle_id == 0) + { + icon_view->details->react_to_icon_change_idle_id + = g_idle_add (fm_icon_view_react_to_icon_change_idle_callback, + icon_view); + } + + /* Store the new position of the icon in the metadata. */ + if (!fm_icon_view_using_auto_layout (icon_view)) + { + position_string = g_strdup_printf + ("%d,%d", position->x, position->y); + caja_file_set_metadata + (file, CAJA_METADATA_KEY_ICON_POSITION, + NULL, position_string); + g_free (position_string); + } + + + g_ascii_dtostr (scale_string, sizeof (scale_string), position->scale); + caja_file_set_metadata + (file, CAJA_METADATA_KEY_ICON_SCALE, + "1.0", scale_string); +} + +/* Attempt to change the filename to the new text. Notify user if operation fails. */ +static void +fm_icon_view_icon_text_changed_callback (CajaIconContainer *container, + CajaFile *file, + char *new_name, + FMIconView *icon_view) +{ + g_assert (CAJA_IS_FILE (file)); + g_assert (new_name != NULL); + + /* Don't allow a rename with an empty string. Revert to original + * without notifying the user. + */ + if (new_name[0] == '\0') + { + return; + } + fm_rename_file (file, new_name, NULL, NULL); +} + +static char * +get_icon_uri_callback (CajaIconContainer *container, + CajaFile *file, + FMIconView *icon_view) +{ + g_assert (CAJA_IS_ICON_CONTAINER (container)); + g_assert (CAJA_IS_FILE (file)); + g_assert (FM_IS_ICON_VIEW (icon_view)); + + return caja_file_get_uri (file); +} + +static char * +get_icon_drop_target_uri_callback (CajaIconContainer *container, + CajaFile *file, + FMIconView *icon_view) +{ + g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL); + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + g_return_val_if_fail (FM_IS_ICON_VIEW (icon_view), NULL); + + return caja_file_get_drop_target_uri (file); +} + +/* Preferences changed callbacks */ +static void +fm_icon_view_text_attribute_names_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_embedded_text_policy_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_image_display_policy_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_click_policy_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + fm_icon_view_update_click_mode (FM_ICON_VIEW (directory_view)); +} + +static void +fm_icon_view_emblems_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +default_sort_order_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + char *sort_name; + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + sort_name = fm_icon_view_get_directory_sort_by (icon_view, file); + set_sort_criterion (icon_view, get_sort_criterion_by_metadata_text (sort_name)); + g_free (sort_name); + + icon_container = get_icon_container (icon_view); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + + caja_icon_container_request_update_all (icon_container); +} + +static void +default_sort_in_reverse_order_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + set_sort_reversed (icon_view, fm_icon_view_get_directory_sort_reversed (icon_view, file)); + icon_container = get_icon_container (icon_view); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + + caja_icon_container_request_update_all (icon_container); +} + +static void +default_use_tighter_layout_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + icon_container = get_icon_container (icon_view); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + + caja_icon_container_set_tighter_layout ( + icon_container, + fm_icon_view_get_directory_tighter_layout (icon_view, file)); + + caja_icon_container_request_update_all (icon_container); +} + +static void +default_use_manual_layout_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + icon_container = get_icon_container (icon_view); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + + caja_icon_container_set_auto_layout ( + icon_container, + fm_icon_view_get_directory_auto_layout (icon_view, file)); + + caja_icon_container_request_update_all (icon_container); +} + +static void +default_zoom_level_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + int level; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (icon_view))) + { + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + + if (fm_icon_view_is_compact (icon_view)) + { + level = caja_file_get_integer_metadata (file, + CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, + get_default_zoom_level (icon_view)); + } + else + { + level = caja_file_get_integer_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, + get_default_zoom_level (icon_view)); + } + fm_directory_view_zoom_to_level (FM_DIRECTORY_VIEW (icon_view), level); + } +} + +static void +labels_beside_icons_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + set_labels_beside_icons (icon_view); +} + +static void +all_columns_same_width_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + + g_assert (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + set_columns_same_width (icon_view); +} + + +static void +fm_icon_view_sort_directories_first_changed (FMDirectoryView *directory_view) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (directory_view); + + if (fm_icon_view_using_auto_layout (icon_view)) + { + caja_icon_container_sort + (get_icon_container (icon_view)); + } +} + +/* GtkObject methods. */ + +static gboolean +icon_view_can_accept_item (CajaIconContainer *container, + CajaFile *target_item, + const char *item_uri, + FMDirectoryView *view) +{ + return fm_directory_view_can_accept_item (target_item, item_uri, view); +} + +static char * +icon_view_get_container_uri (CajaIconContainer *container, + FMDirectoryView *view) +{ + return fm_directory_view_get_uri (view); +} + +static void +icon_view_move_copy_items (CajaIconContainer *container, + const GList *item_uris, + GArray *relative_item_points, + const char *target_dir, + int copy_action, + int x, int y, + FMDirectoryView *view) +{ + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + item_uris, + fm_directory_view_get_copied_files_atom (view)); + fm_directory_view_move_copy_items (item_uris, relative_item_points, target_dir, + copy_action, x, y, view); +} + +static void +fm_icon_view_update_click_mode (FMIconView *icon_view) +{ + CajaIconContainer *icon_container; + int click_mode; + + icon_container = get_icon_container (icon_view); + g_assert (icon_container != NULL); + + click_mode = eel_preferences_get_enum (CAJA_PREFERENCES_CLICK_POLICY); + + caja_icon_container_set_single_click_mode (icon_container, + click_mode == CAJA_CLICK_POLICY_SINGLE); +} + +static gboolean +get_stored_layout_timestamp (CajaIconContainer *container, + CajaIconData *icon_data, + time_t *timestamp, + FMIconView *view) +{ + CajaFile *file; + CajaDirectory *directory; + + if (icon_data == NULL) + { + directory = fm_directory_view_get_model (FM_DIRECTORY_VIEW (view)); + if (directory == NULL) + { + return FALSE; + } + + file = caja_directory_get_corresponding_file (directory); + *timestamp = caja_file_get_time_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP); + caja_file_unref (file); + } + else + { + *timestamp = caja_file_get_time_metadata (CAJA_FILE (icon_data), + CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP); + } + + return TRUE; +} + +static gboolean +store_layout_timestamp (CajaIconContainer *container, + CajaIconData *icon_data, + const time_t *timestamp, + FMIconView *view) +{ + CajaFile *file; + CajaDirectory *directory; + + if (icon_data == NULL) + { + directory = fm_directory_view_get_model (FM_DIRECTORY_VIEW (view)); + if (directory == NULL) + { + return FALSE; + } + + file = caja_directory_get_corresponding_file (directory); + caja_file_set_time_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP, + (time_t) *timestamp); + caja_file_unref (file); + } + else + { + caja_file_set_time_metadata (CAJA_FILE (icon_data), + CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP, + (time_t) *timestamp); + } + + return TRUE; +} + +static gboolean +focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) +{ + CajaWindowSlotInfo *slot_info; + FMIconView *icon_view = FM_ICON_VIEW (user_data); + + /* make the corresponding slot (and the pane that contains it) active */ + slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (icon_view)); + caja_window_slot_info_make_hosting_pane_active (slot_info); + + return FALSE; +} + +static CajaIconContainer * +create_icon_container (FMIconView *icon_view) +{ + CajaIconContainer *icon_container; + + icon_container = fm_icon_container_new (icon_view); + + gtk_widget_set_can_focus (GTK_WIDGET (icon_container), TRUE); + + g_signal_connect_object (icon_container, "focus_in_event", + G_CALLBACK (focus_in_event_callback), icon_view, 0); + g_signal_connect_object (icon_container, "activate", + G_CALLBACK (icon_container_activate_callback), icon_view, 0); + g_signal_connect_object (icon_container, "activate_alternate", + G_CALLBACK (icon_container_activate_alternate_callback), icon_view, 0); + g_signal_connect_object (icon_container, "band_select_started", + G_CALLBACK (band_select_started_callback), icon_view, 0); + g_signal_connect_object (icon_container, "band_select_ended", + G_CALLBACK (band_select_ended_callback), icon_view, 0); + g_signal_connect_object (icon_container, "context_click_selection", + G_CALLBACK (icon_container_context_click_selection_callback), icon_view, 0); + g_signal_connect_object (icon_container, "context_click_background", + G_CALLBACK (icon_container_context_click_background_callback), icon_view, 0); + g_signal_connect_object (icon_container, "icon_position_changed", + G_CALLBACK (icon_position_changed_callback), icon_view, 0); + g_signal_connect_object (icon_container, "icon_text_changed", + G_CALLBACK (fm_icon_view_icon_text_changed_callback), icon_view, 0); + g_signal_connect_object (icon_container, "selection_changed", + G_CALLBACK (selection_changed_callback), icon_view, 0); + /* FIXME: many of these should move into fm-icon-container as virtual methods */ + g_signal_connect_object (icon_container, "get_icon_uri", + G_CALLBACK (get_icon_uri_callback), icon_view, 0); + g_signal_connect_object (icon_container, "get_icon_drop_target_uri", + G_CALLBACK (get_icon_drop_target_uri_callback), icon_view, 0); + g_signal_connect_object (icon_container, "move_copy_items", + G_CALLBACK (icon_view_move_copy_items), icon_view, 0); + g_signal_connect_object (icon_container, "get_container_uri", + G_CALLBACK (icon_view_get_container_uri), icon_view, 0); + g_signal_connect_object (icon_container, "can_accept_item", + G_CALLBACK (icon_view_can_accept_item), icon_view, 0); + g_signal_connect_object (icon_container, "get_stored_icon_position", + G_CALLBACK (get_stored_icon_position_callback), icon_view, 0); + g_signal_connect_object (icon_container, "layout_changed", + G_CALLBACK (layout_changed_callback), icon_view, 0); + g_signal_connect_object (icon_container, "preview", + G_CALLBACK (icon_container_preview_callback), icon_view, 0); + g_signal_connect_object (icon_container, "renaming_icon", + G_CALLBACK (renaming_icon_callback), icon_view, 0); + g_signal_connect_object (icon_container, "icon_stretch_started", + G_CALLBACK (fm_directory_view_update_menus), icon_view, + G_CONNECT_SWAPPED); + g_signal_connect_object (icon_container, "icon_stretch_ended", + G_CALLBACK (fm_directory_view_update_menus), icon_view, + G_CONNECT_SWAPPED); + + g_signal_connect_object (icon_container, "get_stored_layout_timestamp", + G_CALLBACK (get_stored_layout_timestamp), icon_view, 0); + g_signal_connect_object (icon_container, "store_layout_timestamp", + G_CALLBACK (store_layout_timestamp), icon_view, 0); + + gtk_container_add (GTK_CONTAINER (icon_view), + GTK_WIDGET (icon_container)); + + fm_icon_view_update_click_mode (icon_view); + + gtk_widget_show (GTK_WIDGET (icon_container)); + + return icon_container; +} + +/* Handles an URL received from Mozilla */ +static void +icon_view_handle_netscape_url (CajaIconContainer *container, const char *encoded_url, + const char *target_uri, + GdkDragAction action, int x, int y, FMIconView *view) +{ + fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view), + encoded_url, target_uri, action, x, y); +} + +static void +icon_view_handle_uri_list (CajaIconContainer *container, const char *item_uris, + const char *target_uri, + GdkDragAction action, int x, int y, FMIconView *view) +{ + fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view), + item_uris, target_uri, action, x, y); +} + +static void +icon_view_handle_text (CajaIconContainer *container, const char *text, + const char *target_uri, + GdkDragAction action, int x, int y, FMIconView *view) +{ + fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view), + text, target_uri, action, x, y); +} + +static void +icon_view_handle_raw (CajaIconContainer *container, const char *raw_data, + int length, const char *target_uri, const char *direct_save_uri, + GdkDragAction action, int x, int y, FMIconView *view) +{ + fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view), + raw_data, length, target_uri, direct_save_uri, action, x, y); +} + +static char * +icon_view_get_first_visible_file (CajaView *view) +{ + CajaFile *file; + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (view); + + file = CAJA_FILE (caja_icon_container_get_first_visible_icon (get_icon_container (icon_view))); + + if (file) + { + return caja_file_get_uri (file); + } + + return NULL; +} + +static void +icon_view_scroll_to_file (CajaView *view, + const char *uri) +{ + CajaFile *file; + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (view); + + if (uri != NULL) + { + /* Only if existing, since we don't want to add the file to + the directory if it has been removed since then */ + file = caja_file_get_existing_by_uri (uri); + if (file != NULL) + { + caja_icon_container_scroll_to_icon (get_icon_container (icon_view), + CAJA_ICON_CONTAINER_ICON_DATA (file)); + caja_file_unref (file); + } + } +} + +static void +fm_icon_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (object); + + switch (prop_id) + { + case PROP_COMPACT: + icon_view->details->compact = g_value_get_boolean (value); + if (icon_view->details->compact) + { + caja_icon_container_set_layout_mode (get_icon_container (icon_view), + gtk_widget_get_direction (GTK_WIDGET(icon_view)) == GTK_TEXT_DIR_RTL ? + CAJA_ICON_LAYOUT_T_B_R_L : + CAJA_ICON_LAYOUT_T_B_L_R); + caja_icon_container_set_forced_icon_size (get_icon_container (icon_view), + CAJA_ICON_SIZE_SMALLEST); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +fm_icon_view_class_init (FMIconViewClass *klass) +{ + FMDirectoryViewClass *fm_directory_view_class; + + fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (klass); + + G_OBJECT_CLASS (klass)->set_property = fm_icon_view_set_property; + G_OBJECT_CLASS (klass)->finalize = fm_icon_view_finalize; + + GTK_OBJECT_CLASS (klass)->destroy = fm_icon_view_destroy; + + GTK_WIDGET_CLASS (klass)->screen_changed = fm_icon_view_screen_changed; + GTK_WIDGET_CLASS (klass)->scroll_event = fm_icon_view_scroll_event; + + fm_directory_view_class->add_file = fm_icon_view_add_file; + fm_directory_view_class->flush_added_files = fm_icon_view_flush_added_files; + fm_directory_view_class->begin_loading = fm_icon_view_begin_loading; + fm_directory_view_class->bump_zoom_level = fm_icon_view_bump_zoom_level; + fm_directory_view_class->can_rename_file = fm_icon_view_can_rename_file; + fm_directory_view_class->can_zoom_in = fm_icon_view_can_zoom_in; + fm_directory_view_class->can_zoom_out = fm_icon_view_can_zoom_out; + fm_directory_view_class->clear = fm_icon_view_clear; + fm_directory_view_class->end_loading = fm_icon_view_end_loading; + fm_directory_view_class->file_changed = fm_icon_view_file_changed; + fm_directory_view_class->get_background_widget = fm_icon_view_get_background_widget; + fm_directory_view_class->get_selected_icon_locations = fm_icon_view_get_selected_icon_locations; + fm_directory_view_class->get_selection = fm_icon_view_get_selection; + fm_directory_view_class->get_selection_for_file_transfer = fm_icon_view_get_selection; + fm_directory_view_class->get_item_count = fm_icon_view_get_item_count; + fm_directory_view_class->is_empty = fm_icon_view_is_empty; + fm_directory_view_class->remove_file = fm_icon_view_remove_file; + fm_directory_view_class->reset_to_defaults = fm_icon_view_reset_to_defaults; + fm_directory_view_class->restore_default_zoom_level = fm_icon_view_restore_default_zoom_level; + fm_directory_view_class->reveal_selection = fm_icon_view_reveal_selection; + fm_directory_view_class->select_all = fm_icon_view_select_all; + fm_directory_view_class->set_selection = fm_icon_view_set_selection; + fm_directory_view_class->invert_selection = fm_icon_view_invert_selection; + fm_directory_view_class->compare_files = compare_files; + fm_directory_view_class->zoom_to_level = fm_icon_view_zoom_to_level; + fm_directory_view_class->get_zoom_level = fm_icon_view_get_zoom_level; + fm_directory_view_class->click_policy_changed = fm_icon_view_click_policy_changed; + fm_directory_view_class->embedded_text_policy_changed = fm_icon_view_embedded_text_policy_changed; + fm_directory_view_class->emblems_changed = fm_icon_view_emblems_changed; + fm_directory_view_class->image_display_policy_changed = fm_icon_view_image_display_policy_changed; + fm_directory_view_class->merge_menus = fm_icon_view_merge_menus; + fm_directory_view_class->unmerge_menus = fm_icon_view_unmerge_menus; + fm_directory_view_class->sort_directories_first_changed = fm_icon_view_sort_directories_first_changed; + fm_directory_view_class->start_renaming_file = fm_icon_view_start_renaming_file; + fm_directory_view_class->text_attribute_names_changed = fm_icon_view_text_attribute_names_changed; + fm_directory_view_class->update_menus = fm_icon_view_update_menus; + fm_directory_view_class->using_manual_layout = fm_icon_view_using_manual_layout; + fm_directory_view_class->widget_to_file_operation_position = fm_icon_view_widget_to_file_operation_position; + + klass->clean_up = fm_icon_view_real_clean_up; + klass->supports_auto_layout = real_supports_auto_layout; + klass->supports_scaling = real_supports_scaling; + klass->supports_manual_layout = real_supports_manual_layout; + klass->supports_keep_aligned = real_supports_keep_aligned; + klass->supports_labels_beside_icons = real_supports_labels_beside_icons; + klass->get_directory_auto_layout = fm_icon_view_real_get_directory_auto_layout; + klass->get_directory_sort_by = fm_icon_view_real_get_directory_sort_by; + klass->get_directory_sort_reversed = fm_icon_view_real_get_directory_sort_reversed; + klass->get_directory_tighter_layout = fm_icon_view_real_get_directory_tighter_layout; + klass->set_directory_auto_layout = fm_icon_view_real_set_directory_auto_layout; + klass->set_directory_sort_by = fm_icon_view_real_set_directory_sort_by; + klass->set_directory_sort_reversed = fm_icon_view_real_set_directory_sort_reversed; + klass->set_directory_tighter_layout = fm_icon_view_real_set_directory_tighter_layout; + + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_COMPACT, + g_param_spec_boolean ("compact", + "Compact", + "Whether this view provides a compact listing", + FALSE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + +} + +static const char * +fm_icon_view_get_id (CajaView *view) +{ + if (FM_IS_DESKTOP_ICON_VIEW (view)) + { + return FM_DESKTOP_ICON_VIEW_ID; + } + + if (fm_icon_view_is_compact (FM_ICON_VIEW (view))) + { + return FM_COMPACT_VIEW_ID; + } + + return FM_ICON_VIEW_ID; +} + +static void +fm_icon_view_iface_init (CajaViewIface *iface) +{ + fm_directory_view_init_view_iface (iface); + + iface->get_view_id = fm_icon_view_get_id; + iface->get_first_visible_file = icon_view_get_first_visible_file; + iface->scroll_to_file = icon_view_scroll_to_file; + iface->get_title = NULL; +} + +static void +fm_icon_view_init (FMIconView *icon_view) +{ + static gboolean setup_sound_preview = FALSE; + CajaIconContainer *icon_container; + + g_return_if_fail (gtk_bin_get_child (GTK_BIN (icon_view)) == NULL); + + icon_view->details = g_new0 (FMIconViewDetails, 1); + icon_view->details->sort = &sort_criteria[0]; + icon_view->details->filter_by_screen = FALSE; + + icon_container = create_icon_container (icon_view); + + /* Set our default layout mode */ + caja_icon_container_set_layout_mode (icon_container, + gtk_widget_get_direction (GTK_WIDGET(icon_container)) == GTK_TEXT_DIR_RTL ? + CAJA_ICON_LAYOUT_R_L_T_B : + CAJA_ICON_LAYOUT_L_R_T_B); + + if (!setup_sound_preview) + { + eel_preferences_add_auto_enum (CAJA_PREFERENCES_PREVIEW_SOUND, + &preview_sound_auto_value); + + setup_sound_preview = TRUE; + } + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER, + default_sort_order_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + default_sort_in_reverse_order_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT, + default_use_tighter_layout_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT, + default_use_manual_layout_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS, + labels_beside_icons_changed_callback, + icon_view, G_OBJECT (icon_view)); + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH, + all_columns_same_width_changed_callback, + icon_view, G_OBJECT (icon_view)); + + g_signal_connect_object (get_icon_container (icon_view), "handle_netscape_url", + G_CALLBACK (icon_view_handle_netscape_url), icon_view, 0); + g_signal_connect_object (get_icon_container (icon_view), "handle_uri_list", + G_CALLBACK (icon_view_handle_uri_list), icon_view, 0); + g_signal_connect_object (get_icon_container (icon_view), "handle_text", + G_CALLBACK (icon_view_handle_text), icon_view, 0); + g_signal_connect_object (get_icon_container (icon_view), "handle_raw", + G_CALLBACK (icon_view_handle_raw), icon_view, 0); + + icon_view->details->clipboard_handler_id = + g_signal_connect (caja_clipboard_monitor_get (), + "clipboard_info", + G_CALLBACK (icon_view_notify_clipboard_info), icon_view); +} + +static CajaView * +fm_icon_view_create (CajaWindowSlotInfo *slot) +{ + FMIconView *view; + + view = g_object_new (FM_TYPE_ICON_VIEW, + "window-slot", slot, + "compact", FALSE, + NULL); + return CAJA_VIEW (view); +} + +static CajaView * +fm_compact_view_create (CajaWindowSlotInfo *slot) +{ + FMIconView *view; + + view = g_object_new (FM_TYPE_ICON_VIEW, + "window-slot", slot, + "compact", TRUE, + NULL); + return CAJA_VIEW (view); +} + +static gboolean +fm_icon_view_supports_uri (const char *uri, + GFileType file_type, + const char *mime_type) +{ + if (file_type == G_FILE_TYPE_DIRECTORY) + { + return TRUE; + } + if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) + { + return TRUE; + } + if (g_str_has_prefix (uri, "trash:")) + { + return TRUE; + } + if (g_str_has_prefix (uri, EEL_SEARCH_URI)) + { + return TRUE; + } + + return FALSE; +} + +#define TRANSLATE_VIEW_INFO(view_info) \ + view_info.view_combo_label = _(view_info.view_combo_label); \ + view_info.view_menu_label_with_mnemonic = _(view_info.view_menu_label_with_mnemonic); \ + view_info.error_label = _(view_info.error_label); \ + view_info.startup_error_label = _(view_info.startup_error_label); \ + view_info.display_location_label = _(view_info.display_location_label); \ + + +static CajaViewInfo fm_icon_view = +{ + FM_ICON_VIEW_ID, + /* translators: this is used in the view selection dropdown + * of navigation windows and in the preferences dialog */ + N_("Icon View"), + /* translators: this is used in the view menu */ + N_("_Icons"), + N_("The icon view encountered an error."), + N_("The icon view encountered an error while starting up."), + N_("Display this location with the icon view."), + fm_icon_view_create, + fm_icon_view_supports_uri +}; + +static CajaViewInfo fm_compact_view = +{ + FM_COMPACT_VIEW_ID, + /* translators: this is used in the view selection dropdown + * of navigation windows and in the preferences dialog */ + N_("Compact View"), + /* translators: this is used in the view menu */ + N_("_Compact"), + N_("The compact view encountered an error."), + N_("The compact view encountered an error while starting up."), + N_("Display this location with the compact view."), + fm_compact_view_create, + fm_icon_view_supports_uri +}; + +gboolean +fm_icon_view_is_compact (FMIconView *view) +{ + return view->details->compact; +} + +void +fm_icon_view_register (void) +{ + TRANSLATE_VIEW_INFO (fm_icon_view) + caja_view_factory_register (&fm_icon_view); +} + +void +fm_compact_view_register (void) +{ + TRANSLATE_VIEW_INFO (fm_compact_view) + caja_view_factory_register (&fm_compact_view); +} + diff --git a/src/file-manager/fm-icon-view.h b/src/file-manager/fm-icon-view.h new file mode 100644 index 00000000..2ef11a56 --- /dev/null +++ b/src/file-manager/fm-icon-view.h @@ -0,0 +1,135 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-view.h - interface for icon view of directory. + + Copyright (C) 2000 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: John Sullivan <[email protected]> +*/ + +#ifndef FM_ICON_VIEW_H +#define FM_ICON_VIEW_H + +#include "fm-directory-view.h" + +typedef struct FMIconView FMIconView; +typedef struct FMIconViewClass FMIconViewClass; + +#define FM_TYPE_ICON_VIEW fm_icon_view_get_type() +#define FM_ICON_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_ICON_VIEW, FMIconView)) +#define FM_ICON_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_ICON_VIEW, FMIconViewClass)) +#define FM_IS_ICON_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_ICON_VIEW)) +#define FM_IS_ICON_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_ICON_VIEW)) +#define FM_ICON_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_ICON_VIEW, FMIconViewClass)) + +#define FM_ICON_VIEW_ID "OAFIID:Caja_File_Manager_Icon_View" +#define FM_COMPACT_VIEW_ID "OAFIID:Caja_File_Manager_Compact_View" + +typedef struct FMIconViewDetails FMIconViewDetails; + +struct FMIconView +{ + FMDirectoryView parent; + FMIconViewDetails *details; +}; + +struct FMIconViewClass +{ + FMDirectoryViewClass parent_class; + + /* Methods that can be overriden for settings you don't want to come from metadata. + */ + + /* Note: get_directory_sort_by must return a string that can/will be g_freed. + */ + char * (* get_directory_sort_by) (FMIconView *icon_view, + CajaFile *file); + void (* set_directory_sort_by) (FMIconView *icon_view, + CajaFile *file, + const char* sort_by); + + gboolean (* get_directory_sort_reversed) (FMIconView *icon_view, + CajaFile *file); + void (* set_directory_sort_reversed) (FMIconView *icon_view, + CajaFile *file, + gboolean sort_reversed); + + gboolean (* get_directory_auto_layout) (FMIconView *icon_view, + CajaFile *file); + void (* set_directory_auto_layout) (FMIconView *icon_view, + CajaFile *file, + gboolean auto_layout); + + gboolean (* get_directory_tighter_layout) (FMIconView *icon_view, + CajaFile *file); + void (* set_directory_tighter_layout) (FMIconView *icon_view, + CajaFile *file, + gboolean tighter_layout); + + /* Override "clean_up" if your subclass has its own notion of where icons should be positioned */ + void (* clean_up) (FMIconView *icon_view); + + /* supports_auto_layout is a function pointer that subclasses may + * override to control whether or not the automatic layout options + * should be enabled. The default implementation returns TRUE. + */ + gboolean (* supports_auto_layout) (FMIconView *view); + + /* supports_manual_layout is a function pointer that subclasses may + * override to control whether or not the manual layout options + * should be enabled. The default implementation returns TRUE iff + * not in compact mode. + */ + gboolean (* supports_manual_layout) (FMIconView *view); + + /* supports_scaling is a function pointer that subclasses may + * override to control whether or not the manual layout supports + * scaling. The default implementation returns FALSE + */ + gboolean (* supports_scaling) (FMIconView *view); + + /* supports_auto_layout is a function pointer that subclasses may + * override to control whether snap-to-grid mode + * should be enabled. The default implementation returns FALSE. + */ + gboolean (* supports_keep_aligned) (FMIconView *view); + + /* supports_auto_layout is a function pointer that subclasses may + * override to control whether snap-to-grid mode + * should be enabled. The default implementation returns FALSE. + */ + gboolean (* supports_labels_beside_icons) (FMIconView *view); +}; + +/* GObject support */ +GType fm_icon_view_get_type (void); +int fm_icon_view_compare_files (FMIconView *icon_view, + CajaFile *a, + CajaFile *b); +void fm_icon_view_filter_by_screen (FMIconView *icon_view, gboolean filter); +gboolean fm_icon_view_is_compact (FMIconView *icon_view); + +void fm_icon_view_register (void); +void fm_compact_view_register (void); + +#endif /* FM_ICON_VIEW_H */ diff --git a/src/file-manager/fm-list-model.c b/src/file-manager/fm-list-model.c new file mode 100644 index 00000000..b5e12da2 --- /dev/null +++ b/src/file-manager/fm-list-model.c @@ -0,0 +1,1882 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-model.h - a GtkTreeModel for file lists. + + Copyright (C) 2001, 2002 Anders Carlsson + Copyright (C) 2003, Soeren Sandmann + Copyright (C) 2004, Novell, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Anders Carlsson <[email protected]>, Soeren Sandmann ([email protected]), Dave Camp <[email protected]> +*/ + +#include <config.h> +#include "fm-list-model.h" +#include <libegg/eggtreemultidnd.h> + +#include <string.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-dnd.h> +#include <glib.h> + + +enum +{ + SUBDIRECTORY_UNLOADED, + LAST_SIGNAL +}; + +static GQuark attribute_name_q, + attribute_modification_date_q, + attribute_date_modified_q; + +/* msec delay after Loading... dummy row turns into (empty) */ +#define LOADING_TO_EMPTY_DELAY 100 + +static guint list_model_signals[LAST_SIGNAL] = { 0 }; + +static int fm_list_model_file_entry_compare_func (gconstpointer a, + gconstpointer b, + gpointer user_data); +static void fm_list_model_tree_model_init (GtkTreeModelIface *iface); +static void fm_list_model_sortable_init (GtkTreeSortableIface *iface); +static void fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface); + +struct FMListModelDetails +{ + GSequence *files; + GHashTable *directory_reverse_map; /* map from directory to GSequenceIter's */ + GHashTable *top_reverse_map; /* map from files in top dir to GSequenceIter's */ + + int stamp; + + GQuark sort_attribute; + GtkSortType order; + + gboolean sort_directories_first; + + GtkTreeView *drag_view; + int drag_begin_x; + int drag_begin_y; + + GPtrArray *columns; + + GList *highlight_files; +}; + +typedef struct +{ + FMListModel *model; + + GList *path_list; +} DragDataGetInfo; + +typedef struct FileEntry FileEntry; + +struct FileEntry +{ + CajaFile *file; + GHashTable *reverse_map; /* map from files to GSequenceIter's */ + CajaDirectory *subdirectory; + FileEntry *parent; + GSequence *files; + GSequenceIter *ptr; + guint loaded : 1; +}; + +G_DEFINE_TYPE_WITH_CODE (FMListModel, fm_list_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, + fm_list_model_tree_model_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE, + fm_list_model_sortable_init) + G_IMPLEMENT_INTERFACE (EGG_TYPE_TREE_MULTI_DRAG_SOURCE, + fm_list_model_multi_drag_source_init)); + +static const GtkTargetEntry drag_types [] = +{ + { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST }, + { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST }, +}; + +static GtkTargetList *drag_target_list = NULL; + +static void +file_entry_free (FileEntry *file_entry) +{ + caja_file_unref (file_entry->file); + if (file_entry->reverse_map) + { + g_hash_table_destroy (file_entry->reverse_map); + file_entry->reverse_map = NULL; + } + if (file_entry->subdirectory != NULL) + { + caja_directory_unref (file_entry->subdirectory); + } + if (file_entry->files != NULL) + { + g_sequence_free (file_entry->files); + } + g_free (file_entry); +} + +static GtkTreeModelFlags +fm_list_model_get_flags (GtkTreeModel *tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static int +fm_list_model_get_n_columns (GtkTreeModel *tree_model) +{ + return FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len; +} + +static GType +fm_list_model_get_column_type (GtkTreeModel *tree_model, int index) +{ + switch (index) + { + case FM_LIST_MODEL_FILE_COLUMN: + return CAJA_TYPE_FILE; + case FM_LIST_MODEL_SUBDIRECTORY_COLUMN: + return CAJA_TYPE_DIRECTORY; + case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: + case FM_LIST_MODEL_SMALLER_ICON_COLUMN: + case FM_LIST_MODEL_SMALL_ICON_COLUMN: + case FM_LIST_MODEL_STANDARD_ICON_COLUMN: + case FM_LIST_MODEL_LARGE_ICON_COLUMN: + case FM_LIST_MODEL_LARGER_ICON_COLUMN: + case FM_LIST_MODEL_LARGEST_ICON_COLUMN: + case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: + case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: + case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: + case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN: + return G_TYPE_BOOLEAN; + default: + if (index < FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len) + { + return G_TYPE_STRING; + } + else + { + return G_TYPE_INVALID; + } + } +} + +static void +fm_list_model_ptr_to_iter (FMListModel *model, GSequenceIter *ptr, GtkTreeIter *iter) +{ + g_assert (!g_sequence_iter_is_end (ptr)); + if (iter != NULL) + { + iter->stamp = model->details->stamp; + iter->user_data = ptr; + } +} + +static gboolean +fm_list_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path) +{ + FMListModel *model; + GSequence *files; + GSequenceIter *ptr; + FileEntry *file_entry; + int i, d; + + model = (FMListModel *)tree_model; + ptr = NULL; + + files = model->details->files; + for (d = 0; d < gtk_tree_path_get_depth (path); d++) + { + i = gtk_tree_path_get_indices (path)[d]; + + if (files == NULL || i >= g_sequence_get_length (files)) + { + return FALSE; + } + + ptr = g_sequence_get_iter_at_pos (files, i); + file_entry = g_sequence_get (ptr); + files = file_entry->files; + } + + fm_list_model_ptr_to_iter (model, ptr, iter); + + return TRUE; +} + +static GtkTreePath * +fm_list_model_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + GtkTreePath *path; + FMListModel *model; + GSequenceIter *ptr; + FileEntry *file_entry; + + + model = (FMListModel *)tree_model; + + g_return_val_if_fail (iter->stamp == model->details->stamp, NULL); + + if (g_sequence_iter_is_end (iter->user_data)) + { + /* FIXME is this right? */ + return NULL; + } + + path = gtk_tree_path_new (); + ptr = iter->user_data; + while (ptr != NULL) + { + gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (ptr)); + file_entry = g_sequence_get (ptr); + if (file_entry->parent != NULL) + { + ptr = file_entry->parent->ptr; + } + else + { + ptr = NULL; + } + } + + return path; +} + +static void +fm_list_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, int column, GValue *value) +{ + FMListModel *model; + FileEntry *file_entry; + CajaFile *file; + char *str; + GdkPixbuf *icon, *rendered_icon; + int icon_size; + guint emblem_size; + CajaZoomLevel zoom_level; + GList *emblem_pixbufs; + CajaFile *parent_file; + char *emblems_to_ignore[3]; + int i; + CajaFileIconFlags flags; + + model = (FMListModel *)tree_model; + + g_return_if_fail (model->details->stamp == iter->stamp); + g_return_if_fail (!g_sequence_iter_is_end (iter->user_data)); + + file_entry = g_sequence_get (iter->user_data); + file = file_entry->file; + + switch (column) + { + case FM_LIST_MODEL_FILE_COLUMN: + g_value_init (value, CAJA_TYPE_FILE); + + g_value_set_object (value, file); + break; + case FM_LIST_MODEL_SUBDIRECTORY_COLUMN: + g_value_init (value, CAJA_TYPE_DIRECTORY); + + g_value_set_object (value, file_entry->subdirectory); + break; + case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: + case FM_LIST_MODEL_SMALLER_ICON_COLUMN: + case FM_LIST_MODEL_SMALL_ICON_COLUMN: + case FM_LIST_MODEL_STANDARD_ICON_COLUMN: + case FM_LIST_MODEL_LARGE_ICON_COLUMN: + case FM_LIST_MODEL_LARGER_ICON_COLUMN: + case FM_LIST_MODEL_LARGEST_ICON_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + + if (file != NULL) + { + zoom_level = fm_list_model_get_zoom_level_from_column_id (column); + icon_size = caja_get_icon_size_for_zoom_level (zoom_level); + + flags = CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | + CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE | + CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM; + if (model->details->drag_view != NULL) + { + GtkTreePath *path_a, *path_b; + + gtk_tree_view_get_drag_dest_row (model->details->drag_view, + &path_a, + NULL); + if (path_a != NULL) + { + path_b = gtk_tree_model_get_path (tree_model, iter); + + if (gtk_tree_path_compare (path_a, path_b) == 0) + { + flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT; + } + + gtk_tree_path_free (path_a); + gtk_tree_path_free (path_b); + } + } + + icon = caja_file_get_icon_pixbuf (file, icon_size, TRUE, flags); + + if (model->details->highlight_files != NULL && + g_list_find_custom (model->details->highlight_files, + file, (GCompareFunc) caja_file_compare_location)) + { + rendered_icon = eel_gdk_pixbuf_render (icon, 1, 255, 255, 0, 0); + + if (rendered_icon != NULL) + { + g_object_unref (icon); + icon = rendered_icon; + } + } + + g_value_set_object (value, icon); + g_object_unref (icon); + } + break; + case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: + case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: + case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: + case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + + if (file != NULL) + { + parent_file = caja_file_get_parent (file); + i = 0; + emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH; + if (parent_file) + { + if (!caja_file_can_write (parent_file)) + { + emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE; + } + caja_file_unref (parent_file); + } + emblems_to_ignore[i++] = NULL; + + zoom_level = fm_list_model_get_zoom_level_from_emblem_column_id (column); + icon_size = caja_get_icon_size_for_zoom_level (zoom_level); + emblem_size = caja_icon_get_emblem_size_for_icon_size (icon_size); + if (emblem_size != 0) + { + emblem_pixbufs = caja_file_get_emblem_pixbufs (file, + emblem_size, + TRUE, + emblems_to_ignore); + if (emblem_pixbufs != NULL) + { + icon = emblem_pixbufs->data; + g_value_set_object (value, icon); + } + eel_gdk_pixbuf_list_free (emblem_pixbufs); + } + } + break; + case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN: + g_value_init (value, G_TYPE_BOOLEAN); + + g_value_set_boolean (value, file != NULL && caja_file_can_rename (file)); + break; + default: + if (column >= FM_LIST_MODEL_NUM_COLUMNS || column < FM_LIST_MODEL_NUM_COLUMNS + model->details->columns->len) + { + CajaColumn *caja_column; + GQuark attribute; + caja_column = model->details->columns->pdata[column - FM_LIST_MODEL_NUM_COLUMNS]; + + g_value_init (value, G_TYPE_STRING); + g_object_get (caja_column, + "attribute_q", &attribute, + NULL); + if (file != NULL) + { + str = caja_file_get_string_attribute_with_default_q (file, + attribute); + g_value_take_string (value, str); + } + else if (attribute == attribute_name_q) + { + if (file_entry->parent->loaded) + { + g_value_set_string (value, _("(Empty)")); + } + else + { + g_value_set_string (value, _("Loading...")); + } + } + } + else + { + g_assert_not_reached (); + } + } +} + +static gboolean +fm_list_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + FMListModel *model; + + model = (FMListModel *)tree_model; + + g_return_val_if_fail (model->details->stamp == iter->stamp, FALSE); + + iter->user_data = g_sequence_iter_next (iter->user_data); + + return !g_sequence_iter_is_end (iter->user_data); +} + +static gboolean +fm_list_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) +{ + FMListModel *model; + GSequence *files; + FileEntry *file_entry; + + model = (FMListModel *)tree_model; + + if (parent == NULL) + { + files = model->details->files; + } + else + { + file_entry = g_sequence_get (parent->user_data); + files = file_entry->files; + } + + if (files == NULL || g_sequence_get_length (files) == 0) + { + return FALSE; + } + + iter->stamp = model->details->stamp; + iter->user_data = g_sequence_get_begin_iter (files); + + return TRUE; +} + +static gboolean +fm_list_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + FileEntry *file_entry; + + if (iter == NULL) + { + return !fm_list_model_is_empty (FM_LIST_MODEL (tree_model)); + } + + file_entry = g_sequence_get (iter->user_data); + + return (file_entry->files != NULL && g_sequence_get_length (file_entry->files) > 0); +} + +static int +fm_list_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + FMListModel *model; + GSequence *files; + FileEntry *file_entry; + + model = (FMListModel *)tree_model; + + if (iter == NULL) + { + files = model->details->files; + } + else + { + file_entry = g_sequence_get (iter->user_data); + files = file_entry->files; + } + + return g_sequence_get_length (files); +} + +static gboolean +fm_list_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, int n) +{ + FMListModel *model; + GSequenceIter *child; + GSequence *files; + FileEntry *file_entry; + + model = (FMListModel *)tree_model; + + if (parent != NULL) + { + file_entry = g_sequence_get (parent->user_data); + files = file_entry->files; + } + else + { + files = model->details->files; + } + + child = g_sequence_get_iter_at_pos (files, n); + + if (g_sequence_iter_is_end (child)) + { + return FALSE; + } + + iter->stamp = model->details->stamp; + iter->user_data = child; + + return TRUE; +} + +static gboolean +fm_list_model_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child) +{ + FMListModel *model; + FileEntry *file_entry; + + model = (FMListModel *)tree_model; + + file_entry = g_sequence_get (child->user_data); + + if (file_entry->parent == NULL) + { + return FALSE; + } + + iter->stamp = model->details->stamp; + iter->user_data = file_entry->parent->ptr; + + return TRUE; +} + +static GSequenceIter * +lookup_file (FMListModel *model, CajaFile *file, + CajaDirectory *directory) +{ + FileEntry *file_entry; + GSequenceIter *ptr, *parent_ptr; + + parent_ptr = NULL; + if (directory) + { + parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, + directory); + } + + if (parent_ptr) + { + file_entry = g_sequence_get (parent_ptr); + ptr = g_hash_table_lookup (file_entry->reverse_map, file); + } + else + { + ptr = g_hash_table_lookup (model->details->top_reverse_map, file); + } + + if (ptr) + { + g_assert (((FileEntry *)g_sequence_get (ptr))->file == file); + } + + return ptr; +} + + +struct GetIters +{ + FMListModel *model; + CajaFile *file; + GList *iters; +}; + +static void +dir_to_iters (struct GetIters *data, + GHashTable *reverse_map) +{ + GSequenceIter *ptr; + + ptr = g_hash_table_lookup (reverse_map, data->file); + if (ptr) + { + GtkTreeIter *iter; + iter = g_new0 (GtkTreeIter, 1); + fm_list_model_ptr_to_iter (data->model, ptr, iter); + data->iters = g_list_prepend (data->iters, iter); + } +} + +static void +file_to_iter_cb (gpointer key, + gpointer value, + gpointer user_data) +{ + struct GetIters *data; + FileEntry *dir_file_entry; + + data = user_data; + dir_file_entry = g_sequence_get ((GSequenceIter *)value); + dir_to_iters (data, dir_file_entry->reverse_map); +} + +GList * +fm_list_model_get_all_iters_for_file (FMListModel *model, CajaFile *file) +{ + struct GetIters data; + + data.file = file; + data.model = model; + data.iters = NULL; + + dir_to_iters (&data, model->details->top_reverse_map); + g_hash_table_foreach (model->details->directory_reverse_map, + file_to_iter_cb, &data); + + return g_list_reverse (data.iters); +} + +gboolean +fm_list_model_get_first_iter_for_file (FMListModel *model, + CajaFile *file, + GtkTreeIter *iter) +{ + GList *list; + gboolean res; + + res = FALSE; + + list = fm_list_model_get_all_iters_for_file (model, file); + if (list != NULL) + { + res = TRUE; + *iter = *(GtkTreeIter *)list->data; + } + eel_g_list_free_deep (list); + + return res; +} + + +gboolean +fm_list_model_get_tree_iter_from_file (FMListModel *model, CajaFile *file, + CajaDirectory *directory, + GtkTreeIter *iter) +{ + GSequenceIter *ptr; + + ptr = lookup_file (model, file, directory); + if (!ptr) + { + return FALSE; + } + + fm_list_model_ptr_to_iter (model, ptr, iter); + + return TRUE; +} + +static int +fm_list_model_file_entry_compare_func (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + FileEntry *file_entry1; + FileEntry *file_entry2; + FMListModel *model; + int result; + + model = (FMListModel *)user_data; + + file_entry1 = (FileEntry *)a; + file_entry2 = (FileEntry *)b; + + if (file_entry1->file != NULL && file_entry2->file != NULL) + { + result = caja_file_compare_for_sort_by_attribute_q (file_entry1->file, file_entry2->file, + model->details->sort_attribute, + model->details->sort_directories_first, + (model->details->order == GTK_SORT_DESCENDING)); + } + else if (file_entry1->file == NULL) + { + return -1; + } + else + { + return 1; + } + + return result; +} + +int +fm_list_model_compare_func (FMListModel *model, + CajaFile *file1, + CajaFile *file2) +{ + int result; + + result = caja_file_compare_for_sort_by_attribute_q (file1, file2, + model->details->sort_attribute, + model->details->sort_directories_first, + (model->details->order == GTK_SORT_DESCENDING)); + + return result; +} + +static void +fm_list_model_sort_file_entries (FMListModel *model, GSequence *files, GtkTreePath *path) +{ + GSequenceIter **old_order; + GtkTreeIter iter; + int *new_order; + int length; + int i; + FileEntry *file_entry; + gboolean has_iter; + + length = g_sequence_get_length (files); + + if (length <= 1) + { + return; + } + + /* generate old order of GSequenceIter's */ + old_order = g_new (GSequenceIter *, length); + for (i = 0; i < length; ++i) + { + GSequenceIter *ptr = g_sequence_get_iter_at_pos (files, i); + + file_entry = g_sequence_get (ptr); + if (file_entry->files != NULL) + { + gtk_tree_path_append_index (path, i); + fm_list_model_sort_file_entries (model, file_entry->files, path); + gtk_tree_path_up (path); + } + + old_order[i] = ptr; + } + + /* sort */ + g_sequence_sort (files, fm_list_model_file_entry_compare_func, model); + + /* generate new order */ + new_order = g_new (int, length); + /* Note: new_order[newpos] = oldpos */ + for (i = 0; i < length; ++i) + { + new_order[g_sequence_iter_get_position (old_order[i])] = i; + } + + /* Let the world know about our new order */ + + g_assert (new_order != NULL); + + has_iter = FALSE; + if (gtk_tree_path_get_depth (path) != 0) + { + gboolean get_iter_result; + has_iter = TRUE; + get_iter_result = gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path); + g_assert (get_iter_result); + } + + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), + path, has_iter ? &iter : NULL, new_order); + + g_free (old_order); + g_free (new_order); +} + +static void +fm_list_model_sort (FMListModel *model) +{ + GtkTreePath *path; + + path = gtk_tree_path_new (); + + fm_list_model_sort_file_entries (model, model->details->files, path); + + gtk_tree_path_free (path); +} + +static gboolean +fm_list_model_get_sort_column_id (GtkTreeSortable *sortable, + gint *sort_column_id, + GtkSortType *order) +{ + FMListModel *model; + int id; + + model = (FMListModel *)sortable; + + id = fm_list_model_get_sort_column_id_from_attribute + (model, model->details->sort_attribute); + + if (id == -1) + { + return FALSE; + } + + if (sort_column_id != NULL) + { + *sort_column_id = id; + } + + if (order != NULL) + { + *order = model->details->order; + } + + return TRUE; +} + +static void +fm_list_model_set_sort_column_id (GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order) +{ + FMListModel *model; + + model = (FMListModel *)sortable; + + model->details->sort_attribute = fm_list_model_get_attribute_from_sort_column_id (model, sort_column_id); + + model->details->order = order; + + fm_list_model_sort (model); + gtk_tree_sortable_sort_column_changed (sortable); +} + +static gboolean +fm_list_model_has_default_sort_func (GtkTreeSortable *sortable) +{ + return FALSE; +} + +static gboolean +fm_list_model_multi_row_draggable (EggTreeMultiDragSource *drag_source, GList *path_list) +{ + return TRUE; +} + +static void +each_path_get_data_binder (CajaDragEachSelectedItemDataGet data_get, + gpointer context, + gpointer data) +{ + DragDataGetInfo *info; + GList *l; + CajaFile *file; + GtkTreeRowReference *row; + GtkTreePath *path; + char *uri; + GdkRectangle cell_area; + GtkTreeViewColumn *column; + + info = context; + + g_return_if_fail (info->model->details->drag_view); + + column = gtk_tree_view_get_column (info->model->details->drag_view, 0); + + for (l = info->path_list; l != NULL; l = l->next) + { + row = l->data; + + path = gtk_tree_row_reference_get_path (row); + file = fm_list_model_file_for_path (info->model, path); + if (file) + { + gtk_tree_view_get_cell_area + (info->model->details->drag_view, + path, + column, + &cell_area); + + uri = caja_file_get_uri (file); + + (*data_get) (uri, + 0, + cell_area.y - info->model->details->drag_begin_y, + cell_area.width, cell_area.height, + data); + + g_free (uri); + + caja_file_unref (file); + } + + gtk_tree_path_free (path); + } +} + +static gboolean +fm_list_model_multi_drag_data_get (EggTreeMultiDragSource *drag_source, + GList *path_list, + GtkSelectionData *selection_data) +{ + FMListModel *model; + DragDataGetInfo context; + guint target_info; + + model = FM_LIST_MODEL (drag_source); + + context.model = model; + context.path_list = path_list; + + if (!drag_target_list) + { + drag_target_list = fm_list_model_get_drag_target_list (); + } + + if (gtk_target_list_find (drag_target_list, + gtk_selection_data_get_target (selection_data), + &target_info)) + { + caja_drag_drag_data_get (NULL, + NULL, + selection_data, + target_info, + GDK_CURRENT_TIME, + &context, + each_path_get_data_binder); + return TRUE; + } + else + { + return FALSE; + } +} + +static gboolean +fm_list_model_multi_drag_data_delete (EggTreeMultiDragSource *drag_source, GList *path_list) +{ + return TRUE; +} + +static void +add_dummy_row (FMListModel *model, FileEntry *parent_entry) +{ + FileEntry *dummy_file_entry; + GtkTreeIter iter; + GtkTreePath *path; + + dummy_file_entry = g_new0 (FileEntry, 1); + dummy_file_entry->parent = parent_entry; + dummy_file_entry->ptr = g_sequence_insert_sorted (parent_entry->files, dummy_file_entry, + fm_list_model_file_entry_compare_func, model); + iter.stamp = model->details->stamp; + iter.user_data = dummy_file_entry->ptr; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); +} + +gboolean +fm_list_model_add_file (FMListModel *model, CajaFile *file, + CajaDirectory *directory) +{ + GtkTreeIter iter; + GtkTreePath *path; + FileEntry *file_entry; + GSequenceIter *ptr, *parent_ptr; + GSequence *files; + gboolean replace_dummy; + GHashTable *parent_hash; + + parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, + directory); + if (parent_ptr) + { + file_entry = g_sequence_get (parent_ptr); + ptr = g_hash_table_lookup (file_entry->reverse_map, file); + } + else + { + file_entry = NULL; + ptr = g_hash_table_lookup (model->details->top_reverse_map, file); + } + + if (ptr != NULL) + { + g_warning ("file already in tree (parent_ptr: %p)!!!\n", parent_ptr); + return FALSE; + } + + file_entry = g_new0 (FileEntry, 1); + file_entry->file = caja_file_ref (file); + file_entry->parent = NULL; + file_entry->subdirectory = NULL; + file_entry->files = NULL; + + files = model->details->files; + parent_hash = model->details->top_reverse_map; + + replace_dummy = FALSE; + + if (parent_ptr != NULL) + { + file_entry->parent = g_sequence_get (parent_ptr); + /* At this point we set loaded. Either we saw + * "done" and ignored it waiting for this, or we do this + * earlier, but then we replace the dummy row anyway, + * so it doesn't matter */ + file_entry->parent->loaded = 1; + parent_hash = file_entry->parent->reverse_map; + files = file_entry->parent->files; + if (g_sequence_get_length (files) == 1) + { + GSequenceIter *dummy_ptr = g_sequence_get_iter_at_pos (files, 0); + FileEntry *dummy_entry = g_sequence_get (dummy_ptr); + if (dummy_entry->file == NULL) + { + /* replace the dummy loading entry */ + model->details->stamp++; + g_sequence_remove (dummy_ptr); + + replace_dummy = TRUE; + } + } + } + + + file_entry->ptr = g_sequence_insert_sorted (files, file_entry, + fm_list_model_file_entry_compare_func, model); + + g_hash_table_insert (parent_hash, file, file_entry->ptr); + + iter.stamp = model->details->stamp; + iter.user_data = file_entry->ptr; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + if (replace_dummy) + { + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); + } + else + { + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); + } + + if (caja_file_is_directory (file)) + { + file_entry->files = g_sequence_new ((GDestroyNotify)file_entry_free); + + add_dummy_row (model, file_entry); + + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), + path, &iter); + } + gtk_tree_path_free (path); + + return TRUE; +} + +void +fm_list_model_file_changed (FMListModel *model, CajaFile *file, + CajaDirectory *directory) +{ + FileEntry *parent_file_entry; + GtkTreeIter iter; + GtkTreePath *path, *parent_path; + GSequenceIter *ptr; + int pos_before, pos_after, length, i, old; + int *new_order; + gboolean has_iter; + GSequence *files; + + ptr = lookup_file (model, file, directory); + if (!ptr) + { + return; + } + + + pos_before = g_sequence_iter_get_position (ptr); + + g_sequence_sort_changed (ptr, fm_list_model_file_entry_compare_func, model); + + pos_after = g_sequence_iter_get_position (ptr); + + if (pos_before != pos_after) + { + /* The file moved, we need to send rows_reordered */ + + parent_file_entry = ((FileEntry *)g_sequence_get (ptr))->parent; + + if (parent_file_entry == NULL) + { + has_iter = FALSE; + parent_path = gtk_tree_path_new (); + files = model->details->files; + } + else + { + has_iter = TRUE; + fm_list_model_ptr_to_iter (model, parent_file_entry->ptr, &iter); + parent_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + files = parent_file_entry->files; + } + + length = g_sequence_get_length (files); + new_order = g_new (int, length); + /* Note: new_order[newpos] = oldpos */ + for (i = 0, old = 0; i < length; ++i) + { + if (i == pos_after) + { + new_order[i] = pos_before; + } + else + { + if (old == pos_before) + old++; + new_order[i] = old++; + } + } + + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), + parent_path, has_iter ? &iter : NULL, new_order); + + gtk_tree_path_free (parent_path); + g_free (new_order); + } + + fm_list_model_ptr_to_iter (model, ptr, &iter); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); +} + +gboolean +fm_list_model_is_empty (FMListModel *model) +{ + return (g_sequence_get_length (model->details->files) == 0); +} + +guint +fm_list_model_get_length (FMListModel *model) +{ + return g_sequence_get_length (model->details->files); +} + +static void +fm_list_model_remove (FMListModel *model, GtkTreeIter *iter) +{ + GSequenceIter *ptr, *child_ptr; + FileEntry *file_entry, *child_file_entry, *parent_file_entry; + GtkTreePath *path; + GtkTreeIter parent_iter; + + ptr = iter->user_data; + file_entry = g_sequence_get (ptr); + + if (file_entry->files != NULL) + { + while (g_sequence_get_length (file_entry->files) > 0) + { + child_ptr = g_sequence_get_begin_iter (file_entry->files); + child_file_entry = g_sequence_get (child_ptr); + if (child_file_entry->file != NULL) + { + fm_list_model_remove_file (model, + child_file_entry->file, + file_entry->subdirectory); + } + else + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_path_append_index (path, 0); + model->details->stamp++; + g_sequence_remove (child_ptr); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + } + + /* the parent iter didn't actually change */ + iter->stamp = model->details->stamp; + } + + } + + if (file_entry->file != NULL) /* Don't try to remove dummy row */ + { + if (file_entry->parent != NULL) + { + g_hash_table_remove (file_entry->parent->reverse_map, file_entry->file); + } + else + { + g_hash_table_remove (model->details->top_reverse_map, file_entry->file); + } + } + + parent_file_entry = file_entry->parent; + if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 1 && + file_entry->file != NULL) + { + /* this is the last non-dummy child, add a dummy node */ + /* We need to do this before removing the last file to avoid + * collapsing the row. + */ + add_dummy_row (model, parent_file_entry); + } + + if (file_entry->subdirectory != NULL) + { + g_signal_emit (model, + list_model_signals[SUBDIRECTORY_UNLOADED], 0, + file_entry->subdirectory); + g_hash_table_remove (model->details->directory_reverse_map, + file_entry->subdirectory); + } + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + + g_sequence_remove (ptr); + model->details->stamp++; + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + + gtk_tree_path_free (path); + + if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 0) + { + parent_iter.stamp = model->details->stamp; + parent_iter.user_data = parent_file_entry->ptr; + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &parent_iter); + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), + path, &parent_iter); + gtk_tree_path_free (path); + } +} + +void +fm_list_model_remove_file (FMListModel *model, CajaFile *file, + CajaDirectory *directory) +{ + GtkTreeIter iter; + + if (fm_list_model_get_tree_iter_from_file (model, file, directory, &iter)) + { + fm_list_model_remove (model, &iter); + } +} + +static void +fm_list_model_clear_directory (FMListModel *model, GSequence *files) +{ + GtkTreeIter iter; + FileEntry *file_entry; + + while (g_sequence_get_length (files) > 0) + { + iter.user_data = g_sequence_get_begin_iter (files); + + file_entry = g_sequence_get (iter.user_data); + if (file_entry->files != NULL) + { + fm_list_model_clear_directory (model, file_entry->files); + } + + iter.stamp = model->details->stamp; + fm_list_model_remove (model, &iter); + } +} + +void +fm_list_model_clear (FMListModel *model) +{ + g_return_if_fail (model != NULL); + + fm_list_model_clear_directory (model, model->details->files); +} + +CajaFile * +fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path) +{ + CajaFile *file; + GtkTreeIter iter; + + file = NULL; + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), + &iter, path)) + { + gtk_tree_model_get (GTK_TREE_MODEL (model), + &iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + } + return file; +} + +gboolean +fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory) +{ + GtkTreeIter iter; + FileEntry *file_entry; + CajaDirectory *subdirectory; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) + { + return FALSE; + } + + file_entry = g_sequence_get (iter.user_data); + if (file_entry->file == NULL || + file_entry->subdirectory != NULL) + { + return FALSE; + } + + subdirectory = caja_directory_get_for_file (file_entry->file); + + if (g_hash_table_lookup (model->details->directory_reverse_map, + subdirectory) != NULL) + { + caja_directory_unref (subdirectory); + g_warning ("Already in directory_reverse_map, failing\n"); + return FALSE; + } + + file_entry->subdirectory = subdirectory, + g_hash_table_insert (model->details->directory_reverse_map, + subdirectory, file_entry->ptr); + file_entry->reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); + + /* Return a ref too */ + caja_directory_ref (subdirectory); + *directory = subdirectory; + + return TRUE; +} + +/* removes all children of the subfolder and unloads the subdirectory */ +void +fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter) +{ + GSequenceIter *child_ptr; + FileEntry *file_entry, *child_file_entry; + GtkTreeIter child_iter; + + file_entry = g_sequence_get (iter->user_data); + if (file_entry->file == NULL || + file_entry->subdirectory == NULL) + { + return; + } + + file_entry->loaded = 0; + + /* Remove all children */ + while (g_sequence_get_length (file_entry->files) > 0) + { + child_ptr = g_sequence_get_begin_iter (file_entry->files); + child_file_entry = g_sequence_get (child_ptr); + if (child_file_entry->file == NULL) + { + /* Don't delete the dummy node */ + break; + } + else + { + fm_list_model_ptr_to_iter (model, child_ptr, &child_iter); + fm_list_model_remove (model, &child_iter); + } + } + + /* Emit unload signal */ + g_signal_emit (model, + list_model_signals[SUBDIRECTORY_UNLOADED], 0, + file_entry->subdirectory); + + /* actually unload */ + g_hash_table_remove (model->details->directory_reverse_map, + file_entry->subdirectory); + caja_directory_unref (file_entry->subdirectory); + file_entry->subdirectory = NULL; + + g_assert (g_hash_table_size (file_entry->reverse_map) == 0); + g_hash_table_destroy (file_entry->reverse_map); + file_entry->reverse_map = NULL; +} + + + +void +fm_list_model_set_should_sort_directories_first (FMListModel *model, gboolean sort_directories_first) +{ + if (model->details->sort_directories_first == sort_directories_first) + { + return; + } + + model->details->sort_directories_first = sort_directories_first; + fm_list_model_sort (model); +} + +int +fm_list_model_get_sort_column_id_from_attribute (FMListModel *model, + GQuark attribute) +{ + guint i; + + if (attribute == 0) + { + return -1; + } + + /* Hack - the preferences dialog sets modification_date for some + * rather than date_modified for some reason. Make sure that + * works. */ + if (attribute == attribute_modification_date_q) + { + attribute = attribute_date_modified_q; + } + + for (i = 0; i < model->details->columns->len; i++) + { + CajaColumn *column; + GQuark column_attribute; + + column = + CAJA_COLUMN (model->details->columns->pdata[i]); + g_object_get (G_OBJECT (column), + "attribute_q", &column_attribute, + NULL); + if (column_attribute == attribute) + { + return FM_LIST_MODEL_NUM_COLUMNS + i; + } + } + + return -1; +} + +GQuark +fm_list_model_get_attribute_from_sort_column_id (FMListModel *model, + int sort_column_id) +{ + CajaColumn *column; + int index; + GQuark attribute; + + index = sort_column_id - FM_LIST_MODEL_NUM_COLUMNS; + + if (index < 0 || index >= model->details->columns->len) + { + g_warning ("unknown sort column id: %d", sort_column_id); + return 0; + } + + column = CAJA_COLUMN (model->details->columns->pdata[index]); + g_object_get (G_OBJECT (column), "attribute_q", &attribute, NULL); + + return attribute; +} + +CajaZoomLevel +fm_list_model_get_zoom_level_from_column_id (int column) +{ + switch (column) + { + case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_SMALLEST; + case FM_LIST_MODEL_SMALLER_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_SMALLER; + case FM_LIST_MODEL_SMALL_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_SMALL; + case FM_LIST_MODEL_STANDARD_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_STANDARD; + case FM_LIST_MODEL_LARGE_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_LARGE; + case FM_LIST_MODEL_LARGER_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_LARGER; + case FM_LIST_MODEL_LARGEST_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_LARGEST; + } + + g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD); +} + +int +fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level) +{ + switch (zoom_level) + { + case CAJA_ZOOM_LEVEL_SMALLEST: + return FM_LIST_MODEL_SMALLEST_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_SMALLER: + return FM_LIST_MODEL_SMALLER_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_SMALL: + return FM_LIST_MODEL_SMALL_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_STANDARD: + return FM_LIST_MODEL_STANDARD_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_LARGE: + return FM_LIST_MODEL_LARGE_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_LARGER: + return FM_LIST_MODEL_LARGER_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_LARGEST: + return FM_LIST_MODEL_LARGEST_ICON_COLUMN; + } + + g_return_val_if_reached (FM_LIST_MODEL_STANDARD_ICON_COLUMN); +} + +CajaZoomLevel +fm_list_model_get_zoom_level_from_emblem_column_id (int column) +{ + switch (column) + { + case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_SMALLEST; + case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_SMALLER; + case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_SMALL; + case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_STANDARD; + case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_LARGE; + case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_LARGER; + case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_LARGEST; + } + + g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD); +} + +int +fm_list_model_get_emblem_column_id_from_zoom_level (CajaZoomLevel zoom_level) +{ + switch (zoom_level) + { + case CAJA_ZOOM_LEVEL_SMALLEST: + return FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_SMALLER: + return FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_SMALL: + return FM_LIST_MODEL_SMALL_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_STANDARD: + return FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_LARGE: + return FM_LIST_MODEL_LARGE_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_LARGER: + return FM_LIST_MODEL_LARGER_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_LARGEST: + return FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN; + } + + g_return_val_if_reached (FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN); +} + +void +fm_list_model_set_drag_view (FMListModel *model, + GtkTreeView *view, + int drag_begin_x, + int drag_begin_y) +{ + g_return_if_fail (model != NULL); + g_return_if_fail (FM_IS_LIST_MODEL (model)); + g_return_if_fail (!view || GTK_IS_TREE_VIEW (view)); + + model->details->drag_view = view; + model->details->drag_begin_x = drag_begin_x; + model->details->drag_begin_y = drag_begin_y; +} + +GtkTargetList * +fm_list_model_get_drag_target_list () +{ + GtkTargetList *target_list; + + target_list = gtk_target_list_new (drag_types, G_N_ELEMENTS (drag_types)); + gtk_target_list_add_text_targets (target_list, CAJA_ICON_DND_TEXT); + + return target_list; +} + +int +fm_list_model_add_column (FMListModel *model, + CajaColumn *column) +{ + g_ptr_array_add (model->details->columns, column); + g_object_ref (column); + + return FM_LIST_MODEL_NUM_COLUMNS + (model->details->columns->len - 1); +} + +int +fm_list_model_get_column_number (FMListModel *model, + const char *column_name) +{ + int i; + + for (i = 0; i < model->details->columns->len; i++) + { + CajaColumn *column; + char *name; + + column = model->details->columns->pdata[i]; + + g_object_get (G_OBJECT (column), "name", &name, NULL); + + if (!strcmp (name, column_name)) + { + g_free (name); + return FM_LIST_MODEL_NUM_COLUMNS + i; + } + g_free (name); + } + + return -1; +} + +static void +fm_list_model_dispose (GObject *object) +{ + FMListModel *model; + int i; + + model = FM_LIST_MODEL (object); + + if (model->details->columns) + { + for (i = 0; i < model->details->columns->len; i++) + { + g_object_unref (model->details->columns->pdata[i]); + } + g_ptr_array_free (model->details->columns, TRUE); + model->details->columns = NULL; + } + + if (model->details->files) + { + g_sequence_free (model->details->files); + model->details->files = NULL; + } + + if (model->details->top_reverse_map) + { + g_hash_table_destroy (model->details->top_reverse_map); + model->details->top_reverse_map = NULL; + } + if (model->details->directory_reverse_map) + { + g_hash_table_destroy (model->details->directory_reverse_map); + model->details->directory_reverse_map = NULL; + } + + G_OBJECT_CLASS (fm_list_model_parent_class)->dispose (object); +} + +static void +fm_list_model_finalize (GObject *object) +{ + FMListModel *model; + + model = FM_LIST_MODEL (object); + + if (model->details->highlight_files != NULL) + { + caja_file_list_free (model->details->highlight_files); + model->details->highlight_files = NULL; + } + + g_free (model->details); + + G_OBJECT_CLASS (fm_list_model_parent_class)->finalize (object); +} + +static void +fm_list_model_init (FMListModel *model) +{ + model->details = g_new0 (FMListModelDetails, 1); + model->details->files = g_sequence_new ((GDestroyNotify)file_entry_free); + model->details->top_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); + model->details->directory_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); + model->details->stamp = g_random_int (); + model->details->sort_attribute = 0; + model->details->columns = g_ptr_array_new (); +} + +static void +fm_list_model_class_init (FMListModelClass *klass) +{ + GObjectClass *object_class; + + attribute_name_q = g_quark_from_static_string ("name"); + attribute_modification_date_q = g_quark_from_static_string ("modification_date"); + attribute_date_modified_q = g_quark_from_static_string ("date_modified"); + + object_class = (GObjectClass *)klass; + object_class->finalize = fm_list_model_finalize; + object_class->dispose = fm_list_model_dispose; + + list_model_signals[SUBDIRECTORY_UNLOADED] = + g_signal_new ("subdirectory_unloaded", + FM_TYPE_LIST_MODEL, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FMListModelClass, subdirectory_unloaded), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + CAJA_TYPE_DIRECTORY); +} + +static void +fm_list_model_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = fm_list_model_get_flags; + iface->get_n_columns = fm_list_model_get_n_columns; + iface->get_column_type = fm_list_model_get_column_type; + iface->get_iter = fm_list_model_get_iter; + iface->get_path = fm_list_model_get_path; + iface->get_value = fm_list_model_get_value; + iface->iter_next = fm_list_model_iter_next; + iface->iter_children = fm_list_model_iter_children; + iface->iter_has_child = fm_list_model_iter_has_child; + iface->iter_n_children = fm_list_model_iter_n_children; + iface->iter_nth_child = fm_list_model_iter_nth_child; + iface->iter_parent = fm_list_model_iter_parent; +} + +static void +fm_list_model_sortable_init (GtkTreeSortableIface *iface) +{ + iface->get_sort_column_id = fm_list_model_get_sort_column_id; + iface->set_sort_column_id = fm_list_model_set_sort_column_id; + iface->has_default_sort_func = fm_list_model_has_default_sort_func; +} + +static void +fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface) +{ + iface->row_draggable = fm_list_model_multi_row_draggable; + iface->drag_data_get = fm_list_model_multi_drag_data_get; + iface->drag_data_delete = fm_list_model_multi_drag_data_delete; +} + +void +fm_list_model_subdirectory_done_loading (FMListModel *model, CajaDirectory *directory) +{ + GtkTreeIter iter; + GtkTreePath *path; + FileEntry *file_entry, *dummy_entry; + GSequenceIter *parent_ptr, *dummy_ptr; + GSequence *files; + + if (model == NULL || model->details->directory_reverse_map == NULL) + { + return; + } + parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, + directory); + if (parent_ptr == NULL) + { + return; + } + + file_entry = g_sequence_get (parent_ptr); + files = file_entry->files; + + /* Only swap loading -> empty if we saw no files yet at "done", + * otherwise, toggle loading at first added file to the model. + */ + if (!caja_directory_is_not_empty (directory) && + g_sequence_get_length (files) == 1) + { + dummy_ptr = g_sequence_get_iter_at_pos (file_entry->files, 0); + dummy_entry = g_sequence_get (dummy_ptr); + if (dummy_entry->file == NULL) + { + /* was the dummy file */ + file_entry->loaded = 1; + + iter.stamp = model->details->stamp; + iter.user_data = dummy_ptr; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); + } + } +} + +static void +refresh_row (gpointer data, + gpointer user_data) +{ + CajaFile *file; + FMListModel *model; + GList *iters, *l; + GtkTreePath *path; + + model = user_data; + file = data; + + iters = fm_list_model_get_all_iters_for_file (model, file); + for (l = iters; l != NULL; l = l->next) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), l->data); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, l->data); + + gtk_tree_path_free (path); + } + + eel_g_list_free_deep (iters); +} + +void +fm_list_model_set_highlight_for_files (FMListModel *model, + GList *files) +{ + if (model->details->highlight_files != NULL) + { + g_list_foreach (model->details->highlight_files, + refresh_row, model); + caja_file_list_free (model->details->highlight_files); + model->details->highlight_files = NULL; + } + + if (files != NULL) + { + model->details->highlight_files = caja_file_list_copy (files); + g_list_foreach (model->details->highlight_files, + refresh_row, model); + + } +} diff --git a/src/file-manager/fm-list-model.h b/src/file-manager/fm-list-model.h new file mode 100644 index 00000000..49f39078 --- /dev/null +++ b/src/file-manager/fm-list-model.h @@ -0,0 +1,148 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-model.h - a GtkTreeModel for file lists. + + Copyright (C) 2001, 2002 Anders Carlsson + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Anders Carlsson <[email protected]> +*/ + +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-extension/caja-column.h> + +#ifndef FM_LIST_MODEL_H +#define FM_LIST_MODEL_H + +#define FM_TYPE_LIST_MODEL fm_list_model_get_type() +#define FM_LIST_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_LIST_MODEL, FMListModel)) +#define FM_LIST_MODEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_LIST_MODEL, FMListModelClass)) +#define FM_IS_LIST_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_LIST_MODEL)) +#define FM_IS_LIST_MODEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_LIST_MODEL)) +#define FM_LIST_MODEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_LIST_MODEL, FMListModelClass)) + +enum +{ + FM_LIST_MODEL_FILE_COLUMN, + FM_LIST_MODEL_SUBDIRECTORY_COLUMN, + FM_LIST_MODEL_SMALLEST_ICON_COLUMN, + FM_LIST_MODEL_SMALLER_ICON_COLUMN, + FM_LIST_MODEL_SMALL_ICON_COLUMN, + FM_LIST_MODEL_STANDARD_ICON_COLUMN, + FM_LIST_MODEL_LARGE_ICON_COLUMN, + FM_LIST_MODEL_LARGER_ICON_COLUMN, + FM_LIST_MODEL_LARGEST_ICON_COLUMN, + FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN, + FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN, + FM_LIST_MODEL_SMALL_EMBLEM_COLUMN, + FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN, + FM_LIST_MODEL_LARGE_EMBLEM_COLUMN, + FM_LIST_MODEL_LARGER_EMBLEM_COLUMN, + FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN, + FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN, + FM_LIST_MODEL_NUM_COLUMNS +}; + +typedef struct FMListModelDetails FMListModelDetails; + +typedef struct FMListModel +{ + GObject parent_instance; + FMListModelDetails *details; +} FMListModel; + +typedef struct +{ + GObjectClass parent_class; + + void (* subdirectory_unloaded)(FMListModel *model, + CajaDirectory *subdirectory); +} FMListModelClass; + +GType fm_list_model_get_type (void); +gboolean fm_list_model_add_file (FMListModel *model, + CajaFile *file, + CajaDirectory *directory); +void fm_list_model_file_changed (FMListModel *model, + CajaFile *file, + CajaDirectory *directory); +gboolean fm_list_model_is_empty (FMListModel *model); +guint fm_list_model_get_length (FMListModel *model); +void fm_list_model_remove_file (FMListModel *model, + CajaFile *file, + CajaDirectory *directory); +void fm_list_model_clear (FMListModel *model); +gboolean fm_list_model_get_tree_iter_from_file (FMListModel *model, + CajaFile *file, + CajaDirectory *directory, + GtkTreeIter *iter); +GList * fm_list_model_get_all_iters_for_file (FMListModel *model, + CajaFile *file); +gboolean fm_list_model_get_first_iter_for_file (FMListModel *model, + CajaFile *file, + GtkTreeIter *iter); +void fm_list_model_set_should_sort_directories_first (FMListModel *model, + gboolean sort_directories_first); + +int fm_list_model_get_sort_column_id_from_attribute (FMListModel *model, + GQuark attribute); +GQuark fm_list_model_get_attribute_from_sort_column_id (FMListModel *model, + int sort_column_id); +void fm_list_model_sort_files (FMListModel *model, + GList **files); + +CajaZoomLevel fm_list_model_get_zoom_level_from_column_id (int column); +int fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level); +CajaZoomLevel fm_list_model_get_zoom_level_from_emblem_column_id (int column); +int fm_list_model_get_emblem_column_id_from_zoom_level (CajaZoomLevel zoom_level); + +CajaFile * fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path); +gboolean fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory); +void fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter); + +void fm_list_model_set_drag_view (FMListModel *model, + GtkTreeView *view, + int begin_x, + int begin_y); + +GtkTargetList * fm_list_model_get_drag_target_list (void); + +int fm_list_model_compare_func (FMListModel *model, + CajaFile *file1, + CajaFile *file2); + + +int fm_list_model_add_column (FMListModel *model, + CajaColumn *column); +int fm_list_model_get_column_number (FMListModel *model, + const char *column_name); + +void fm_list_model_subdirectory_done_loading (FMListModel *model, + CajaDirectory *directory); + +void fm_list_model_set_highlight_for_files (FMListModel *model, + GList *files); + +#endif /* FM_LIST_MODEL_H */ diff --git a/src/file-manager/fm-list-view-private.h b/src/file-manager/fm-list-view-private.h new file mode 100644 index 00000000..2225cfca --- /dev/null +++ b/src/file-manager/fm-list-view-private.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view-private.h - Private functions for both the list and search list + view to share + + Copyright (C) 2000 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Rebecca Schulman <[email protected]> + +*/ + +struct FMListViewColumn +{ + const char *attribute; + const char *title; + CajaFileSortType sort_criterion; + int minimum_width, default_width, maximum_width; + gboolean right_justified; +}; + +void fm_list_view_column_set (FMListViewColumn *column, + const char *attribute, + const char *title, + CajaFileSortType sort_criterion, + int minimum_width, + int default_width, + int maximum_width, + gboolean right_justified); diff --git a/src/file-manager/fm-list-view.c b/src/file-manager/fm-list-view.c new file mode 100644 index 00000000..1c82af9b --- /dev/null +++ b/src/file-manager/fm-list-view.c @@ -0,0 +1,3415 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view.c - implementation of list view of directory. + + Copyright (C) 2000 Eazel, Inc. + Copyright (C) 2001, 2002 Anders Carlsson <[email protected]> + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: John Sullivan <[email protected]> + Anders Carlsson <[email protected]> + David Emory Watson <[email protected]> +*/ + +#include <config.h> +#include "fm-list-view.h" + +#include <string.h> +#include "fm-error-reporting.h" +#include "fm-list-model.h" +#include <string.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-gdk-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <libegg/eggtreemultidnd.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <libcaja-extension/caja-column-provider.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-column-chooser.h> +#include <libcaja-private/caja-column-utilities.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-dnd.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-dnd.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-tree-view-drag-dest.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h> +#include <libcaja-private/caja-cell-renderer-text-ellipsized.h> + +struct FMListViewDetails +{ + GtkTreeView *tree_view; + FMListModel *model; + GtkActionGroup *list_action_group; + guint list_merge_id; + + GtkTreeViewColumn *file_name_column; + int file_name_column_num; + + GtkCellRendererPixbuf *pixbuf_cell; + GtkCellRendererText *file_name_cell; + GList *cells; + GtkCellEditable *editable_widget; + + CajaZoomLevel zoom_level; + + CajaTreeViewDragDest *drag_dest; + + GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */ + + GtkTreePath *new_selection_path; /* Path of the new selection after removing a file */ + + GtkTreePath *hover_path; + + guint drag_button; + int drag_x; + int drag_y; + + gboolean drag_started; + + gboolean ignore_button_release; + + gboolean row_selected_on_button_down; + + gboolean menus_ready; + + GHashTable *columns; + GtkWidget *column_editor; + + char *original_name; + + CajaFile *renaming_file; + gboolean rename_done; + guint renaming_file_activate_timeout; + + gulong clipboard_handler_id; + + GQuark last_sort_attr; +}; + +struct SelectionForeachData +{ + GList *list; + GtkTreeSelection *selection; +}; + +/* + * The row height should be large enough to not clip emblems. + * Computing this would be costly, so we just choose a number + * that works well with the set of emblems we've designed. + */ +#define LIST_VIEW_MINIMUM_ROW_HEIGHT 28 + +/* We wait two seconds after row is collapsed to unload the subdirectory */ +#define COLLAPSE_TO_UNLOAD_DELAY 2 + +/* Wait for the rename to end when activating a file being renamed */ +#define WAIT_FOR_RENAME_ON_ACTIVATE 200 + +static int click_policy_auto_value; +static char * default_sort_order_auto_value; +static gboolean default_sort_reversed_auto_value; +static CajaZoomLevel default_zoom_level_auto_value; +static char ** default_visible_columns_auto_value; +static char ** default_column_order_auto_value; +static GdkCursor * hand_cursor = NULL; + +static GtkTargetList * source_target_list = NULL; + +static GList *fm_list_view_get_selection (FMDirectoryView *view); +static GList *fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view); +static void fm_list_view_set_zoom_level (FMListView *view, + CajaZoomLevel new_level, + gboolean always_set_level); +static void fm_list_view_scale_font_size (FMListView *view, + CajaZoomLevel new_level); +static void fm_list_view_scroll_to_file (FMListView *view, + CajaFile *file); +static void fm_list_view_iface_init (CajaViewIface *iface); +static void fm_list_view_rename_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data); + + +G_DEFINE_TYPE_WITH_CODE (FMListView, fm_list_view, FM_TYPE_DIRECTORY_VIEW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, + fm_list_view_iface_init)); + +static const char * default_trash_visible_columns[] = +{ + "name", "size", "type", "trashed_on", "trash_orig_path", NULL +}; + +static const char * default_trash_columns_order[] = +{ + "name", "size", "type", "trashed_on", "trash_orig_path", NULL +}; + +/* for EEL_CALL_PARENT */ +#define parent_class fm_list_view_parent_class + +static const gchar* +get_default_sort_order (CajaFile *file, gboolean *reversed) +{ + const gchar *retval; + + retval = caja_file_get_default_sort_attribute (file, reversed); + + if (retval == NULL) + { + retval = default_sort_order_auto_value; + *reversed = default_sort_reversed_auto_value; + } + + return retval; +} + +static void +list_selection_changed_callback (GtkTreeSelection *selection, gpointer user_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (user_data); + + fm_directory_view_notify_selection_changed (view); +} + +/* Move these to eel? */ + +static void +tree_selection_foreach_set_boolean (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer callback_data) +{ + * (gboolean *) callback_data = TRUE; +} + +static gboolean +tree_selection_not_empty (GtkTreeSelection *selection) +{ + gboolean not_empty; + + not_empty = FALSE; + gtk_tree_selection_selected_foreach (selection, + tree_selection_foreach_set_boolean, + ¬_empty); + return not_empty; +} + +static gboolean +tree_view_has_selection (GtkTreeView *view) +{ + return tree_selection_not_empty (gtk_tree_view_get_selection (view)); +} + +static void +activate_selected_items (FMListView *view) +{ + GList *file_list; + + file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + + + if (view->details->renaming_file) + { + /* We're currently renaming a file, wait until the rename is + finished, or the activation uri will be wrong */ + if (view->details->renaming_file_activate_timeout == 0) + { + view->details->renaming_file_activate_timeout = + g_timeout_add (WAIT_FOR_RENAME_ON_ACTIVATE, (GSourceFunc) activate_selected_items, view); + } + return; + } + + if (view->details->renaming_file_activate_timeout != 0) + { + g_source_remove (view->details->renaming_file_activate_timeout); + view->details->renaming_file_activate_timeout = 0; + } + + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + 0, + TRUE); + caja_file_list_free (file_list); + +} + +static void +activate_selected_items_alternate (FMListView *view, + CajaFile *file, + gboolean open_in_tab) +{ + GList *file_list; + CajaWindowOpenFlags flags; + + flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND; + + if (open_in_tab) + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB; + } + else + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW; + } + + if (file != NULL) + { + caja_file_ref (file); + file_list = g_list_prepend (NULL, file); + } + else + { + file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + } + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + flags, + TRUE); + caja_file_list_free (file_list); + +} + +static gboolean +button_event_modifies_selection (GdkEventButton *event) +{ + return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; +} + +static void +fm_list_view_did_not_drag (FMListView *view, + GdkEventButton *event) +{ + GtkTreeView *tree_view; + GtkTreeSelection *selection; + GtkTreePath *path; + + tree_view = view->details->tree_view; + selection = gtk_tree_view_get_selection (tree_view); + + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) + { + if ((event->button == 1 || event->button == 2) + && ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0) + && view->details->row_selected_on_button_down) + { + if (!button_event_modifies_selection (event)) + { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + } + else + { + gtk_tree_selection_unselect_path (selection, path); + } + } + + if ((click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + && !button_event_modifies_selection(event)) + { + if (event->button == 1) + { + activate_selected_items (view); + } + else if (event->button == 2) + { + activate_selected_items_alternate (view, NULL, TRUE); + } + } + gtk_tree_path_free (path); + } + +} + +static void +drag_data_get_callback (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + GtkTreeView *tree_view; + GtkTreeModel *model; + GList *ref_list; + + tree_view = GTK_TREE_VIEW (widget); + + model = gtk_tree_view_get_model (tree_view); + + if (model == NULL) + { + return; + } + + ref_list = g_object_get_data (G_OBJECT (context), "drag-info"); + + if (ref_list == NULL) + { + return; + } + + if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model)) + { + egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model), + ref_list, + selection_data); + } +} + +static void +filtered_selection_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct SelectionForeachData *selection_data; + GtkTreeIter parent; + GtkTreeIter child; + + selection_data = data; + + /* If the parent folder is also selected, don't include this file in the + * file operation, since that would copy it to the toplevel target instead + * of keeping it as a child of the copied folder + */ + child = *iter; + while (gtk_tree_model_iter_parent (model, &parent, &child)) + { + if (gtk_tree_selection_iter_is_selected (selection_data->selection, + &parent)) + { + return; + } + child = parent; + } + + selection_data->list = g_list_prepend (selection_data->list, + gtk_tree_row_reference_new (model, path)); +} + +static GList * +get_filtered_selection_refs (GtkTreeView *tree_view) +{ + struct SelectionForeachData selection_data; + + selection_data.list = NULL; + selection_data.selection = gtk_tree_view_get_selection (tree_view); + + gtk_tree_selection_selected_foreach (selection_data.selection, + filtered_selection_foreach, + &selection_data); + return g_list_reverse (selection_data.list); +} + +static void +ref_list_free (GList *ref_list) +{ + g_list_foreach (ref_list, (GFunc) gtk_tree_row_reference_free, NULL); + g_list_free (ref_list); +} + +static void +stop_drag_check (FMListView *view) +{ + view->details->drag_button = 0; +} + +static GdkPixbuf * +get_drag_pixbuf (FMListView *view) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GdkPixbuf *ret; + GdkRectangle cell_area; + + ret = NULL; + + if (gtk_tree_view_get_path_at_pos (view->details->tree_view, + view->details->drag_x, + view->details->drag_y, + &path, NULL, NULL, NULL)) + { + model = gtk_tree_view_get_model (view->details->tree_view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + fm_list_model_get_column_id_from_zoom_level (view->details->zoom_level), + &ret, + -1); + + gtk_tree_view_get_cell_area (view->details->tree_view, + path, + view->details->file_name_column, + &cell_area); + + gtk_tree_path_free (path); + } + + return ret; +} + +static void +drag_begin_callback (GtkWidget *widget, + GdkDragContext *context, + FMListView *view) +{ + GList *ref_list; + GdkPixbuf *pixbuf; + + pixbuf = get_drag_pixbuf (view); + if (pixbuf) + { + gtk_drag_set_icon_pixbuf (context, + pixbuf, + 0, 0); + g_object_unref (pixbuf); + } + else + { + gtk_drag_set_icon_default (context); + } + + stop_drag_check (view); + view->details->drag_started = TRUE; + + ref_list = get_filtered_selection_refs (GTK_TREE_VIEW (widget)); + g_object_set_data_full (G_OBJECT (context), + "drag-info", + ref_list, + (GDestroyNotify)ref_list_free); +} + +static gboolean +motion_notify_callback (GtkWidget *widget, + GdkEventMotion *event, + gpointer callback_data) +{ + FMListView *view; + GdkDragContext *context; + + view = FM_LIST_VIEW (callback_data); + + if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget))) + { + return FALSE; + } + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + GtkTreePath *old_hover_path; + + old_hover_path = view->details->hover_path; + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &view->details->hover_path, + NULL, NULL, NULL); + + if ((old_hover_path != NULL) != (view->details->hover_path != NULL)) + { + if (view->details->hover_path != NULL) + { + gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor); + } + else + { + gdk_window_set_cursor (gtk_widget_get_window (widget), NULL); + } + } + + if (old_hover_path != NULL) + { + gtk_tree_path_free (old_hover_path); + } + } + + if (view->details->drag_button != 0) + { + if (!source_target_list) + { + source_target_list = fm_list_model_get_drag_target_list (); + } + + if (gtk_drag_check_threshold (widget, + view->details->drag_x, + view->details->drag_y, + event->x, + event->y)) + { + context = gtk_drag_begin + (widget, + source_target_list, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK, + view->details->drag_button, + (GdkEvent*)event); + } + return TRUE; + } + + return FALSE; +} + +static gboolean +leave_notify_callback (GtkWidget *widget, + GdkEventCrossing *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && + view->details->hover_path != NULL) + { + gtk_tree_path_free (view->details->hover_path); + view->details->hover_path = NULL; + } + + return FALSE; +} + +static gboolean +enter_notify_callback (GtkWidget *widget, + GdkEventCrossing *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + if (view->details->hover_path != NULL) + { + gtk_tree_path_free (view->details->hover_path); + } + + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &view->details->hover_path, + NULL, NULL, NULL); + + if (view->details->hover_path != NULL) + { + gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor); + } + } + + return FALSE; +} + +static void +do_popup_menu (GtkWidget *widget, FMListView *view, GdkEventButton *event) +{ + if (tree_view_has_selection (GTK_TREE_VIEW (widget))) + { + fm_directory_view_pop_up_selection_context_menu (FM_DIRECTORY_VIEW (view), event); + } + else + { + fm_directory_view_pop_up_background_context_menu (FM_DIRECTORY_VIEW (view), event); + } +} + +static gboolean +button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callback_data) +{ + FMListView *view; + GtkTreeView *tree_view; + GtkTreePath *path; + gboolean call_parent; + gboolean allow_drag; + GtkTreeSelection *selection; + GtkWidgetClass *tree_view_class; + gint64 current_time; + static gint64 last_click_time = 0; + static int click_count = 0; + int double_click_time; + int expander_size, horizontal_separator; + gboolean on_expander; + + view = FM_LIST_VIEW (callback_data); + tree_view = GTK_TREE_VIEW (widget); + tree_view_class = GTK_WIDGET_GET_CLASS (tree_view); + selection = gtk_tree_view_get_selection (tree_view); + + if (event->window != gtk_tree_view_get_bin_window (tree_view)) + { + return FALSE; + } + + fm_list_model_set_drag_view + (FM_LIST_MODEL (gtk_tree_view_get_model (tree_view)), + tree_view, + event->x, event->y); + + g_object_get (G_OBJECT (gtk_widget_get_settings (widget)), + "gtk-double-click-time", &double_click_time, + NULL); + + /* Determine click count */ + current_time = eel_get_system_time (); + if (current_time - last_click_time < double_click_time * 1000) + { + click_count++; + } + else + { + click_count = 0; + } + + /* Stash time for next compare */ + last_click_time = current_time; + + /* Ignore double click if we are in single click mode */ + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && click_count >= 2) + { + return TRUE; + } + + view->details->ignore_button_release = FALSE; + + call_parent = TRUE; + allow_drag = FALSE; + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) + { + gtk_widget_style_get (widget, + "expander-size", &expander_size, + "horizontal-separator", &horizontal_separator, + NULL); + /* TODO we should not hardcode this extra padding. It is + * EXPANDER_EXTRA_PADDING from GtkTreeView. + */ + expander_size += 4; + on_expander = (event->x <= horizontal_separator / 2 + + gtk_tree_path_get_depth (path) * expander_size); + + /* Keep track of path of last click so double clicks only happen + * on the same item */ + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) + { + if (view->details->double_click_path[1]) + { + gtk_tree_path_free (view->details->double_click_path[1]); + } + view->details->double_click_path[1] = view->details->double_click_path[0]; + view->details->double_click_path[0] = gtk_tree_path_copy (path); + } + if (event->type == GDK_2BUTTON_PRESS) + { + /* Double clicking does not trigger a D&D action. */ + view->details->drag_button = 0; + if (view->details->double_click_path[1] && + gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0 && + !on_expander) + { + /* NOTE: Activation can actually destroy the view if we're switching */ + if (!button_event_modifies_selection (event)) + { + if ((event->button == 1 || event->button == 3)) + { + activate_selected_items (view); + } + else if (event->button == 2) + { + activate_selected_items_alternate (view, NULL, TRUE); + } + } + else if (event->button == 1 && + (event->state & GDK_SHIFT_MASK) != 0) + { + CajaFile *file; + file = fm_list_model_file_for_path (view->details->model, path); + if (file != NULL) + { + activate_selected_items_alternate (view, file, TRUE); + caja_file_unref (file); + } + } + } + else + { + tree_view_class->button_press_event (widget, event); + } + } + else + { + + /* We're going to filter out some situations where + * we can't let the default code run because all + * but one row would be would be deselected. We don't + * want that; we want the right click menu or single + * click to apply to everything that's currently selected. */ + + if (event->button == 3 && gtk_tree_selection_path_is_selected (selection, path)) + { + call_parent = FALSE; + } + + if ((event->button == 1 || event->button == 2) && + ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0)) + { + view->details->row_selected_on_button_down = gtk_tree_selection_path_is_selected (selection, path); + if (view->details->row_selected_on_button_down) + { + call_parent = on_expander; + view->details->ignore_button_release = call_parent; + } + else if ((event->state & GDK_CONTROL_MASK) != 0) + { + GList *selected_rows; + GList *l; + + call_parent = FALSE; + if ((event->state & GDK_SHIFT_MASK) != 0) + { + GtkTreePath *cursor; + gtk_tree_view_get_cursor (tree_view, &cursor, NULL); + if (cursor != NULL) + { + gtk_tree_selection_select_range (selection, cursor, path); + } + else + { + gtk_tree_selection_select_path (selection, path); + } + } + else + { + gtk_tree_selection_select_path (selection, path); + } + selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL); + + /* This unselects everything */ + gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE); + + /* So select it again */ + l = selected_rows; + while (l != NULL) + { + GtkTreePath *p = l->data; + l = l->next; + gtk_tree_selection_select_path (selection, p); + gtk_tree_path_free (p); + } + g_list_free (selected_rows); + } + else + { + view->details->ignore_button_release = on_expander; + } + } + + if (call_parent) + { + tree_view_class->button_press_event (widget, event); + } + else if (gtk_tree_selection_path_is_selected (selection, path)) + { + gtk_widget_grab_focus (widget); + } + + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) + { + view->details->drag_started = FALSE; + view->details->drag_button = event->button; + view->details->drag_x = event->x; + view->details->drag_y = event->y; + } + + if (event->button == 3) + { + do_popup_menu (widget, view, event); + } + } + + gtk_tree_path_free (path); + } + else + { + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) + { + if (view->details->double_click_path[1]) + { + gtk_tree_path_free (view->details->double_click_path[1]); + } + view->details->double_click_path[1] = view->details->double_click_path[0]; + view->details->double_click_path[0] = NULL; + } + /* Deselect if people click outside any row. It's OK to + let default code run; it won't reselect anything. */ + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view)); + tree_view_class->button_press_event (widget, event); + + if (event->button == 3) + { + do_popup_menu (widget, view, event); + } + } + + /* We chained to the default handler in this method, so never + * let the default handler run */ + return TRUE; +} + +static gboolean +button_release_callback (GtkWidget *widget, + GdkEventButton *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (event->button == view->details->drag_button) + { + stop_drag_check (view); + if (!view->details->drag_started && + !view->details->ignore_button_release) + { + fm_list_view_did_not_drag (view, event); + } + } + return FALSE; +} + +static gboolean +popup_menu_callback (GtkWidget *widget, gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + do_popup_menu (widget, view, NULL); + + return TRUE; +} + +static void +subdirectory_done_loading_callback (CajaDirectory *directory, FMListView *view) +{ + fm_list_model_subdirectory_done_loading (view->details->model, directory); +} + +static void +row_expanded_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data) +{ + FMListView *view; + CajaDirectory *directory; + + view = FM_LIST_VIEW (callback_data); + + if (fm_list_model_load_subdirectory (view->details->model, path, &directory)) + { + char *uri; + + uri = caja_directory_get_uri (directory); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "list view row expanded window=%p: %s", + fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)), + uri); + g_free (uri); + + fm_directory_view_add_subdirectory (FM_DIRECTORY_VIEW (view), directory); + + if (caja_directory_are_all_files_seen (directory)) + { + fm_list_model_subdirectory_done_loading (view->details->model, + directory); + } + else + { + g_signal_connect_object (directory, "done_loading", + G_CALLBACK (subdirectory_done_loading_callback), + view, 0); + } + + caja_directory_unref (directory); + } +} + +struct UnloadDelayData +{ + CajaFile *file; + CajaDirectory *directory; + FMListView *view; +}; + +static gboolean +unload_file_timeout (gpointer data) +{ + struct UnloadDelayData *unload_data = data; + GtkTreeIter iter; + FMListModel *model; + GtkTreePath *path; + + if (unload_data->view != NULL) + { + model = unload_data->view->details->model; + if (fm_list_model_get_tree_iter_from_file (model, + unload_data->file, + unload_data->directory, + &iter)) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + if (!gtk_tree_view_row_expanded (unload_data->view->details->tree_view, + path)) + { + fm_list_model_unload_subdirectory (model, &iter); + } + gtk_tree_path_free (path); + } + } + + eel_remove_weak_pointer (&unload_data->view); + + if (unload_data->directory) + { + caja_directory_unref (unload_data->directory); + } + caja_file_unref (unload_data->file); + g_free (unload_data); + return FALSE; +} + +static void +row_collapsed_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data) +{ + FMListView *view; + CajaFile *file; + CajaDirectory *directory; + GtkTreeIter parent; + struct UnloadDelayData *unload_data; + GtkTreeModel *model; + char *uri; + + view = FM_LIST_VIEW (callback_data); + model = GTK_TREE_MODEL (view->details->model); + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + directory = NULL; + if (gtk_tree_model_iter_parent (model, &parent, iter)) + { + gtk_tree_model_get (model, &parent, + FM_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory, + -1); + } + + + uri = caja_file_get_uri (file); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "list view row collapsed window=%p: %s", + fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)), + uri); + g_free (uri); + + unload_data = g_new (struct UnloadDelayData, 1); + unload_data->view = view; + unload_data->file = file; + unload_data->directory = directory; + + eel_add_weak_pointer (&unload_data->view); + + g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY, + unload_file_timeout, + unload_data); +} + +static void +row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, FMListView *view) +{ + activate_selected_items (view); +} + +static void +subdirectory_unloaded_callback (FMListModel *model, + CajaDirectory *directory, + gpointer callback_data) +{ + FMListView *view; + + g_return_if_fail (FM_IS_LIST_MODEL (model)); + g_return_if_fail (CAJA_IS_DIRECTORY (directory)); + + view = FM_LIST_VIEW(callback_data); + + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (subdirectory_done_loading_callback), + view); + fm_directory_view_remove_subdirectory (FM_DIRECTORY_VIEW (view), directory); +} + +static gboolean +key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_data) +{ + FMDirectoryView *view; + GdkEventButton button_event = { 0 }; + gboolean handled; + GtkTreeView *tree_view; + GtkTreePath *path; + + tree_view = GTK_TREE_VIEW (widget); + + view = FM_DIRECTORY_VIEW (callback_data); + handled = FALSE; + + switch (event->keyval) + { + case GDK_F10: + if (event->state & GDK_CONTROL_MASK) + { + fm_directory_view_pop_up_background_context_menu (view, &button_event); + handled = TRUE; + } + break; + case GDK_Right: + gtk_tree_view_get_cursor (tree_view, &path, NULL); + if (path) + { + gtk_tree_view_expand_row (tree_view, path, FALSE); + gtk_tree_path_free (path); + } + handled = TRUE; + break; + case GDK_Left: + gtk_tree_view_get_cursor (tree_view, &path, NULL); + if (path) + { + gtk_tree_view_collapse_row (tree_view, path); + gtk_tree_path_free (path); + } + handled = TRUE; + break; + case GDK_space: + if (event->state & GDK_CONTROL_MASK) + { + handled = FALSE; + break; + } + if (!gtk_widget_has_focus (GTK_WIDGET (FM_LIST_VIEW (view)->details->tree_view))) + { + handled = FALSE; + break; + } + if ((event->state & GDK_SHIFT_MASK) != 0) + { + activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE); + } + else + { + activate_selected_items (FM_LIST_VIEW (view)); + } + handled = TRUE; + break; + case GDK_Return: + case GDK_KP_Enter: + if ((event->state & GDK_SHIFT_MASK) != 0) + { + activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE); + } + else + { + activate_selected_items (FM_LIST_VIEW (view)); + } + handled = TRUE; + break; + case GDK_v: + /* Eat Control + v to not enable type ahead */ + if ((event->state & GDK_CONTROL_MASK) != 0) + { + handled = TRUE; + } + break; + + default: + handled = FALSE; + } + + return handled; +} + +static void +fm_list_view_reveal_selection (FMDirectoryView *view) +{ + GList *selection; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + /* Make sure at least one of the selected items is scrolled into view */ + if (selection != NULL) + { + FMListView *list_view; + CajaFile *file; + GtkTreeIter iter; + GtkTreePath *path; + + list_view = FM_LIST_VIEW (view); + file = selection->data; + if (fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter); + + gtk_tree_view_scroll_to_cell (list_view->details->tree_view, path, NULL, FALSE, 0.0, 0.0); + + gtk_tree_path_free (path); + } + } + + caja_file_list_free (selection); +} + +static gboolean +sort_criterion_changes_due_to_user (GtkTreeView *tree_view) +{ + GList *columns, *p; + GtkTreeViewColumn *column; + GSignalInvocationHint *ihint; + unsigned int sort_signal_id; + gboolean ret; + + sort_signal_id = g_signal_lookup ("clicked", gtk_tree_view_column_get_type ()); + + ret = FALSE; + + columns = gtk_tree_view_get_columns (tree_view); + for (p = columns; p != NULL; p = p->next) + { + column = p->data; + ihint = g_signal_get_invocation_hint (column); + if (ihint != NULL) + { + ret = TRUE; + break; + } + } + g_list_free (columns); + + return ret; +} + +static void +sort_column_changed_callback (GtkTreeSortable *sortable, + FMListView *view) +{ + CajaFile *file; + gint sort_column_id, default_sort_column_id; + GtkSortType reversed; + GQuark sort_attr, default_sort_attr; + char *reversed_attr, *default_reversed_attr; + gboolean default_sort_reversed; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + + gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed); + sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id); + + default_sort_column_id = fm_list_model_get_sort_column_id_from_attribute (view->details->model, + g_quark_from_string (get_default_sort_order (file, &default_sort_reversed))); + default_sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, + g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr)); + + default_reversed_attr = (default_sort_reversed ? "true" : "false"); + + if (view->details->last_sort_attr != sort_attr && + sort_criterion_changes_due_to_user (view->details->tree_view)) + { + /* at this point, the sort order is always GTK_SORT_ASCENDING, if the sort column ID + * switched. Invert the sort order, if it's the default criterion with a reversed preference, + * or if it makes sense for the attribute (i.e. date). */ + if (sort_attr == default_sort_attr) + { + /* use value from preferences */ + reversed = eel_preferences_get_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER); + } + else + { + reversed = caja_file_is_date_sort_attribute_q (sort_attr); + } + + if (reversed) + { + g_signal_handlers_block_by_func (sortable, sort_column_changed_callback, view); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (view->details->model), + sort_column_id, + GTK_SORT_DESCENDING); + g_signal_handlers_unblock_by_func (sortable, sort_column_changed_callback, view); + } + } + + + reversed_attr = (reversed ? "true" : "false"); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, + default_reversed_attr, reversed_attr); + + /* Make sure selected item(s) is visible after sort */ + fm_list_view_reveal_selection (FM_DIRECTORY_VIEW (view)); + + view->details->last_sort_attr = sort_attr; +} + +static void +cell_renderer_editing_started_cb (GtkCellRenderer *renderer, + GtkCellEditable *editable, + const gchar *path_str, + FMListView *list_view) +{ + GtkEntry *entry; + gint start_offset, end_offset; + + entry = GTK_ENTRY (editable); + list_view->details->editable_widget = editable; + + /* Free a previously allocated original_name */ + g_free (list_view->details->original_name); + + list_view->details->original_name = g_strdup (gtk_entry_get_text (entry)); + eel_filename_get_rename_region (list_view->details->original_name, + &start_offset, &end_offset); + gtk_editable_select_region (GTK_EDITABLE (entry), start_offset, end_offset); + + caja_clipboard_set_up_editable + (GTK_EDITABLE (entry), + fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (list_view)), + FALSE); +} + +static void +cell_renderer_editing_canceled (GtkCellRendererText *cell, + FMListView *view) +{ + view->details->editable_widget = NULL; + + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +} + +static void +cell_renderer_edited (GtkCellRendererText *cell, + const char *path_str, + const char *new_text, + FMListView *view) +{ + GtkTreePath *path; + CajaFile *file; + GtkTreeIter iter; + + view->details->editable_widget = NULL; + + /* Don't allow a rename with an empty string. Revert to original + * without notifying the user. + */ + if (new_text[0] == '\0') + { + g_object_set (G_OBJECT (view->details->file_name_cell), + "editable", FALSE, + NULL); + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); + return; + } + + path = gtk_tree_path_new_from_string (path_str); + + gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model), + &iter, path); + + gtk_tree_path_free (path); + + gtk_tree_model_get (GTK_TREE_MODEL (view->details->model), + &iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + /* Only rename if name actually changed */ + if (strcmp (new_text, view->details->original_name) != 0) + { + view->details->renaming_file = caja_file_ref (file); + view->details->rename_done = FALSE; + fm_rename_file (file, new_text, fm_list_view_rename_callback, g_object_ref (view)); + g_free (view->details->original_name); + view->details->original_name = g_strdup (new_text); + } + + caja_file_unref (file); + + /*We're done editing - make the filename-cells readonly again.*/ + g_object_set (G_OBJECT (view->details->file_name_cell), + "editable", FALSE, + NULL); + + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +} + +static char * +get_root_uri_callback (CajaTreeViewDragDest *dest, + gpointer user_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (user_data); + + return fm_directory_view_get_uri (FM_DIRECTORY_VIEW (view)); +} + +static CajaFile * +get_file_for_path_callback (CajaTreeViewDragDest *dest, + GtkTreePath *path, + gpointer user_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (user_data); + + return fm_list_model_file_for_path (view->details->model, path); +} + +/* Handles an URL received from Mozilla */ +static void +list_view_handle_netscape_url (CajaTreeViewDragDest *dest, const char *encoded_url, + const char *target_uri, GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view), + encoded_url, target_uri, action, x, y); +} + +static void +list_view_handle_uri_list (CajaTreeViewDragDest *dest, const char *item_uris, + const char *target_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view), + item_uris, target_uri, action, x, y); +} + +static void +list_view_handle_text (CajaTreeViewDragDest *dest, const char *text, + const char *target_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view), + text, target_uri, action, x, y); +} + +static void +list_view_handle_raw (CajaTreeViewDragDest *dest, const char *raw_data, + int length, const char *target_uri, const char *direct_save_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view), + raw_data, length, target_uri, direct_save_uri, + action, x, y); +} + +static void +move_copy_items_callback (CajaTreeViewDragDest *dest, + const GList *item_uris, + const char *target_uri, + guint action, + int x, + int y, + gpointer user_data) + +{ + FMDirectoryView *view = user_data; + + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + item_uris, + fm_directory_view_get_copied_files_atom (view)); + fm_directory_view_move_copy_items (item_uris, + NULL, + target_uri, + action, + x, y, + view); +} + +static void +apply_columns_settings (FMListView *list_view, + char **column_order, + char **visible_columns) +{ + GList *all_columns; + CajaFile *file; + GList *old_view_columns, *view_columns; + GHashTable *visible_columns_hash; + GtkTreeViewColumn *prev_view_column; + GList *l; + int i; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + /* prepare ordered list of view columns using column_order and visible_columns */ + view_columns = NULL; + + all_columns = caja_get_columns_for_file (file); + all_columns = caja_sort_columns (all_columns, column_order); + + /* hash table to lookup if a given column should be visible */ + visible_columns_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + for (i = 0; visible_columns[i] != NULL; ++i) + { + g_hash_table_insert (visible_columns_hash, + g_ascii_strdown (visible_columns[i], -1), + g_ascii_strdown (visible_columns[i], -1)); + } + + for (l = all_columns; l != NULL; l = l->next) + { + char *name; + char *lowercase; + + g_object_get (G_OBJECT (l->data), "name", &name, NULL); + lowercase = g_ascii_strdown (name, -1); + + if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL) + { + GtkTreeViewColumn *view_column; + + view_column = g_hash_table_lookup (list_view->details->columns, name); + if (view_column != NULL) + { + view_columns = g_list_prepend (view_columns, view_column); + } + } + + g_free (name); + g_free (lowercase); + } + + g_hash_table_destroy (visible_columns_hash); + caja_column_list_free (all_columns); + + view_columns = g_list_reverse (view_columns); + + /* remove columns that are not present in the configuration */ + old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view); + for (l = old_view_columns; l != NULL; l = l->next) + { + if (g_list_find (view_columns, l->data) == NULL) + { + gtk_tree_view_remove_column (list_view->details->tree_view, l->data); + } + } + g_list_free (old_view_columns); + + /* append new columns from the configuration */ + old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view); + for (l = view_columns; l != NULL; l = l->next) + { + if (g_list_find (old_view_columns, l->data) == NULL) + { + gtk_tree_view_append_column (list_view->details->tree_view, l->data); + } + } + g_list_free (old_view_columns); + + /* place columns in the correct order */ + prev_view_column = NULL; + for (l = view_columns; l != NULL; l = l->next) + { + gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column); + prev_view_column = l->data; + } + g_list_free (view_columns); +} + +static void +filename_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + FMListView *view) +{ + char *text; + GtkTreePath *path; + PangoUnderline underline; + + gtk_tree_model_get (model, iter, + view->details->file_name_column_num, &text, + -1); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + path = gtk_tree_model_get_path (model, iter); + + if (view->details->hover_path == NULL || + gtk_tree_path_compare (path, view->details->hover_path)) + { + underline = PANGO_UNDERLINE_NONE; + } + else + { + underline = PANGO_UNDERLINE_SINGLE; + } + + gtk_tree_path_free (path); + } + else + { + underline = PANGO_UNDERLINE_NONE; + } + + g_object_set (G_OBJECT (renderer), + "text", text, + "underline", underline, + NULL); + g_free (text); +} + +static gboolean +focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) +{ + CajaWindowSlotInfo *slot_info; + FMListView *list_view = FM_LIST_VIEW (user_data); + + /* make the corresponding slot (and the pane that contains it) active */ + slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (list_view)); + caja_window_slot_info_make_hosting_pane_active (slot_info); + + return FALSE; +} + +static void +create_and_set_up_tree_view (FMListView *view) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + GtkBindingSet *binding_set; + AtkObject *atk_obj; + GList *caja_columns; + GList *l; + + view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ()); + view->details->columns = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify) g_object_unref); + gtk_tree_view_set_enable_search (view->details->tree_view, TRUE); + + /* Don't handle backspace key. It's used to open the parent folder. */ + binding_set = gtk_binding_set_by_class (GTK_WIDGET_GET_CLASS (view->details->tree_view)); + gtk_binding_entry_remove (binding_set, GDK_BackSpace, 0); + + view->details->drag_dest = + caja_tree_view_drag_dest_new (view->details->tree_view); + + g_signal_connect_object (view->details->drag_dest, + "get_root_uri", + G_CALLBACK (get_root_uri_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "get_file_for_path", + G_CALLBACK (get_file_for_path_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "move_copy_items", + G_CALLBACK (move_copy_items_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_netscape_url", + G_CALLBACK (list_view_handle_netscape_url), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_uri_list", + G_CALLBACK (list_view_handle_uri_list), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_text", + G_CALLBACK (list_view_handle_text), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_raw", + G_CALLBACK (list_view_handle_raw), view, 0); + + g_signal_connect_object (gtk_tree_view_get_selection (view->details->tree_view), + "changed", + G_CALLBACK (list_selection_changed_callback), view, 0); + + g_signal_connect_object (view->details->tree_view, "drag_begin", + G_CALLBACK (drag_begin_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "drag_data_get", + G_CALLBACK (drag_data_get_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "motion_notify_event", + G_CALLBACK (motion_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "enter_notify_event", + G_CALLBACK (enter_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "leave_notify_event", + G_CALLBACK (leave_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "button_press_event", + G_CALLBACK (button_press_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "button_release_event", + G_CALLBACK (button_release_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "key_press_event", + G_CALLBACK (key_press_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "popup_menu", + G_CALLBACK (popup_menu_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "row_expanded", + G_CALLBACK (row_expanded_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "row_collapsed", + G_CALLBACK (row_collapsed_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "row-activated", + G_CALLBACK (row_activated_callback), view, 0); + + g_signal_connect_object (view->details->tree_view, "focus_in_event", + G_CALLBACK(focus_in_event_callback), view, 0); + + view->details->model = g_object_new (FM_TYPE_LIST_MODEL, NULL); + gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model)); + /* Need the model for the dnd drop icon "accept" change */ + fm_list_model_set_drag_view (FM_LIST_MODEL (view->details->model), + view->details->tree_view, 0, 0); + + g_signal_connect_object (view->details->model, "sort_column_changed", + G_CALLBACK (sort_column_changed_callback), view, 0); + + g_signal_connect_object (view->details->model, "subdirectory_unloaded", + G_CALLBACK (subdirectory_unloaded_callback), view, 0); + + gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view->details->tree_view), GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_rules_hint (view->details->tree_view, TRUE); + + caja_columns = caja_get_all_columns (); + + for (l = caja_columns; l != NULL; l = l->next) + { + CajaColumn *caja_column; + int column_num; + char *name; + char *label; + float xalign; + + caja_column = CAJA_COLUMN (l->data); + + g_object_get (caja_column, + "name", &name, + "label", &label, + "xalign", &xalign, NULL); + + column_num = fm_list_model_add_column (view->details->model, + caja_column); + + /* Created the name column specially, because it + * has the icon in it.*/ + if (!strcmp (name, "name")) + { + /* Create the file name column */ + cell = caja_cell_renderer_pixbuf_emblem_new (); + view->details->pixbuf_cell = (GtkCellRendererPixbuf *)cell; + + view->details->file_name_column = gtk_tree_view_column_new (); + g_object_ref_sink (view->details->file_name_column); + view->details->file_name_column_num = column_num; + + g_hash_table_insert (view->details->columns, + g_strdup ("name"), + view->details->file_name_column); + + gtk_tree_view_set_search_column (view->details->tree_view, column_num); + + gtk_tree_view_column_set_sort_column_id (view->details->file_name_column, column_num); + gtk_tree_view_column_set_title (view->details->file_name_column, _("Name")); + gtk_tree_view_column_set_resizable (view->details->file_name_column, TRUE); + + gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE); + gtk_tree_view_column_set_attributes (view->details->file_name_column, + cell, + "pixbuf", FM_LIST_MODEL_SMALLEST_ICON_COLUMN, + "pixbuf_emblem", FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN, + NULL); + + cell = caja_cell_renderer_text_ellipsized_new (); + view->details->file_name_cell = (GtkCellRendererText *)cell; + g_signal_connect (cell, "edited", G_CALLBACK (cell_renderer_edited), view); + g_signal_connect (cell, "editing-canceled", G_CALLBACK (cell_renderer_editing_canceled), view); + g_signal_connect (cell, "editing-started", G_CALLBACK (cell_renderer_editing_started_cb), view); + + gtk_tree_view_column_pack_start (view->details->file_name_column, cell, TRUE); + gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell, + (GtkTreeCellDataFunc) filename_cell_data_func, + view, NULL); + } + else + { + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "xalign", xalign, NULL); + view->details->cells = g_list_append (view->details->cells, + cell); + column = gtk_tree_view_column_new_with_attributes (label, + cell, + "text", column_num, + NULL); + g_object_ref_sink (column); + gtk_tree_view_column_set_sort_column_id (column, column_num); + g_hash_table_insert (view->details->columns, + g_strdup (name), + column); + + gtk_tree_view_column_set_resizable (column, TRUE); + gtk_tree_view_column_set_visible (column, TRUE); + } + g_free (name); + g_free (label); + } + caja_column_list_free (caja_columns); + + /* Apply the default column order and visible columns, to get it + * right most of the time. The metadata will be checked when a + * folder is loaded */ + apply_columns_settings (view, + default_column_order_auto_value, + default_visible_columns_auto_value); + + gtk_widget_show (GTK_WIDGET (view->details->tree_view)); + gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (view->details->tree_view)); + + + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view)); + atk_object_set_name (atk_obj, _("List View")); +} + +static void +fm_list_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMListModel *model; + + model = FM_LIST_VIEW (view)->details->model; + fm_list_model_add_file (model, file, directory); +} + +static char ** +get_visible_columns (FMListView *list_view) +{ + CajaFile *file; + GList *visible_columns; + char **ret; + + ret = NULL; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + visible_columns = caja_file_get_metadata_list + (file, + CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS); + + if (visible_columns) + { + GPtrArray *res; + GList *l; + + res = g_ptr_array_new (); + for (l = visible_columns; l != NULL; l = l->next) + { + g_ptr_array_add (res, l->data); + } + g_ptr_array_add (res, NULL); + + ret = (char **) g_ptr_array_free (res, FALSE); + g_list_free (visible_columns); + } + + if (ret != NULL) + { + return ret; + } + + return caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_visible_columns) : + g_strdupv (default_visible_columns_auto_value); +} + +static char ** +get_column_order (FMListView *list_view) +{ + CajaFile *file; + GList *column_order; + char **ret; + + ret = NULL; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + column_order = caja_file_get_metadata_list + (file, + CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER); + + if (column_order) + { + GPtrArray *res; + GList *l; + + res = g_ptr_array_new (); + for (l = column_order; l != NULL; l = l->next) + { + g_ptr_array_add (res, l->data); + } + g_ptr_array_add (res, NULL); + + ret = (char **) g_ptr_array_free (res, FALSE); + g_list_free (column_order); + } + + if (ret != NULL) + { + return ret; + } + + return caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_columns_order) : + g_strdupv (default_column_order_auto_value); +} + +static void +set_columns_settings_from_metadata_and_preferences (FMListView *list_view) +{ + char **column_order; + char **visible_columns; + + column_order = get_column_order (list_view); + visible_columns = get_visible_columns (list_view); + + apply_columns_settings (list_view, column_order, visible_columns); + + g_strfreev (column_order); + g_strfreev (visible_columns); +} + +static void +set_sort_order_from_metadata_and_preferences (FMListView *list_view) +{ + char *sort_attribute; + int sort_column_id; + CajaFile *file; + gboolean sort_reversed, default_sort_reversed; + const gchar *default_sort_order; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + sort_attribute = caja_file_get_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, + NULL); + sort_column_id = fm_list_model_get_sort_column_id_from_attribute (list_view->details->model, + g_quark_from_string (sort_attribute)); + g_free (sort_attribute); + + default_sort_order = get_default_sort_order (file, &default_sort_reversed); + + if (sort_column_id == -1) + { + sort_column_id = + fm_list_model_get_sort_column_id_from_attribute (list_view->details->model, + g_quark_from_string (default_sort_order)); + } + + sort_reversed = caja_file_get_boolean_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, + default_sort_reversed); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_view->details->model), + sort_column_id, + sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); +} + +static gboolean +list_view_changed_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + gtk_tree_model_row_changed (model, path, iter); + return FALSE; +} + +static CajaZoomLevel +get_default_zoom_level (void) +{ + CajaZoomLevel default_zoom_level; + + default_zoom_level = default_zoom_level_auto_value; + + if (default_zoom_level < CAJA_ZOOM_LEVEL_SMALLEST + || CAJA_ZOOM_LEVEL_LARGEST < default_zoom_level) + { + default_zoom_level = CAJA_ZOOM_LEVEL_SMALL; + } + + return default_zoom_level; +} + +static void +set_zoom_level_from_metadata_and_preferences (FMListView *list_view) +{ + CajaFile *file; + int level; + + if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (list_view))) + { + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + level = caja_file_get_integer_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, + get_default_zoom_level ()); + fm_list_view_set_zoom_level (list_view, level, TRUE); + + /* updated the rows after updating the font size */ + gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model), + list_view_changed_foreach, NULL); + } +} + +static void +fm_list_view_begin_loading (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + set_sort_order_from_metadata_and_preferences (list_view); + set_zoom_level_from_metadata_and_preferences (list_view); + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +stop_cell_editing (FMListView *list_view) +{ + GtkTreeViewColumn *column; + + /* Stop an ongoing rename to commit the name changes when the user + * changes directories without exiting cell edit mode. It also prevents + * the edited handler from being called on the cleared list model. + */ + column = list_view->details->file_name_column; + if (column != NULL && list_view->details->editable_widget != NULL && + GTK_IS_CELL_EDITABLE (list_view->details->editable_widget)) + { + gtk_cell_editable_editing_done (list_view->details->editable_widget); + } +} + +static void +fm_list_view_clear (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (list_view->details->model != NULL) + { + stop_cell_editing (list_view); + fm_list_model_clear (list_view->details->model); + } +} + +static void +fm_list_view_rename_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (view->details->renaming_file) + { + view->details->rename_done = TRUE; + + if (error != NULL) + { + /* If the rename failed (or was cancelled), kill renaming_file. + * We won't get a change event for the rename, so otherwise + * it would stay around forever. + */ + caja_file_unref (view->details->renaming_file); + view->details->renaming_file = NULL; + } + } + + g_object_unref (view); +} + + +static void +fm_list_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMListView *listview; + GtkTreeIter iter; + GtkTreePath *file_path; + + listview = FM_LIST_VIEW (view); + + fm_list_model_file_changed (listview->details->model, file, directory); + + if (listview->details->renaming_file != NULL && + file == listview->details->renaming_file && + listview->details->rename_done) + { + /* This is (probably) the result of the rename operation, and + * the tree-view changes above could have resorted the list, so + * scroll to the new position + */ + if (fm_list_model_get_tree_iter_from_file (listview->details->model, file, directory, &iter)) + { + file_path = gtk_tree_model_get_path (GTK_TREE_MODEL (listview->details->model), &iter); + gtk_tree_view_scroll_to_cell (listview->details->tree_view, + file_path, NULL, + FALSE, 0.0, 0.0); + gtk_tree_path_free (file_path); + } + + caja_file_unref (listview->details->renaming_file); + listview->details->renaming_file = NULL; + } +} + +static GtkWidget * +fm_list_view_get_background_widget (FMDirectoryView *view) +{ + return GTK_WIDGET (view); +} + +static void +fm_list_view_get_selection_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + GList **list; + CajaFile *file; + + list = data; + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + if (file != NULL) + { + (* list) = g_list_prepend ((* list), file); + } +} + +static GList * +fm_list_view_get_selection (FMDirectoryView *view) +{ + GList *list; + + list = NULL; + + gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view), + fm_list_view_get_selection_foreach_func, &list); + + return g_list_reverse (list); +} + +static void +fm_list_view_get_selection_for_file_transfer_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + CajaFile *file; + struct SelectionForeachData *selection_data; + GtkTreeIter parent, child; + + selection_data = data; + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + if (file != NULL) + { + /* If the parent folder is also selected, don't include this file in the + * file operation, since that would copy it to the toplevel target instead + * of keeping it as a child of the copied folder + */ + child = *iter; + while (gtk_tree_model_iter_parent (model, &parent, &child)) + { + if (gtk_tree_selection_iter_is_selected (selection_data->selection, + &parent)) + { + return; + } + child = parent; + } + + caja_file_ref (file); + selection_data->list = g_list_prepend (selection_data->list, file); + } +} + + +static GList * +fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ + struct SelectionForeachData selection_data; + + selection_data.list = NULL; + selection_data.selection = gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view); + + gtk_tree_selection_selected_foreach (selection_data.selection, + fm_list_view_get_selection_for_file_transfer_foreach_func, &selection_data); + + return g_list_reverse (selection_data.list); +} + + + + +static guint +fm_list_view_get_item_count (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), 0); + + return fm_list_model_get_length (FM_LIST_VIEW (view)->details->model); +} + +static gboolean +fm_list_view_is_empty (FMDirectoryView *view) +{ + return fm_list_model_is_empty (FM_LIST_VIEW (view)->details->model); +} + +static void +fm_list_view_end_file_changes (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (list_view->details->new_selection_path) + { + gtk_tree_view_set_cursor (list_view->details->tree_view, + list_view->details->new_selection_path, + NULL, FALSE); + gtk_tree_path_free (list_view->details->new_selection_path); + list_view->details->new_selection_path = NULL; + } +} + +static void +fm_list_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + GtkTreePath *path; + GtkTreePath *file_path; + GtkTreeIter iter; + GtkTreeIter temp_iter; + GtkTreeRowReference* row_reference; + FMListView *list_view; + GtkTreeModel* tree_model; + GtkTreeSelection *selection; + + path = NULL; + row_reference = NULL; + list_view = FM_LIST_VIEW (view); + tree_model = GTK_TREE_MODEL(list_view->details->model); + + if (fm_list_model_get_tree_iter_from_file (list_view->details->model, file, directory, &iter)) + { + selection = gtk_tree_view_get_selection (list_view->details->tree_view); + file_path = gtk_tree_model_get_path (tree_model, &iter); + + if (gtk_tree_selection_path_is_selected (selection, file_path)) + { + /* get reference for next element in the list view. If the element to be deleted is the + * last one, get reference to previous element. If there is only one element in view + * no need to select anything. + */ + temp_iter = iter; + + if (gtk_tree_model_iter_next (tree_model, &iter)) + { + path = gtk_tree_model_get_path (tree_model, &iter); + row_reference = gtk_tree_row_reference_new (tree_model, path); + } + else + { + path = gtk_tree_model_get_path (tree_model, &temp_iter); + if (gtk_tree_path_prev (path)) + { + row_reference = gtk_tree_row_reference_new (tree_model, path); + } + } + gtk_tree_path_free (path); + } + + gtk_tree_path_free (file_path); + + fm_list_model_remove_file (list_view->details->model, file, directory); + + if (gtk_tree_row_reference_valid (row_reference)) + { + if (list_view->details->new_selection_path) + { + gtk_tree_path_free (list_view->details->new_selection_path); + } + list_view->details->new_selection_path = gtk_tree_row_reference_get_path (row_reference); + } + + if (row_reference) + { + gtk_tree_row_reference_free (row_reference); + } + } + + +} + +static void +fm_list_view_set_selection (FMDirectoryView *view, GList *selection) +{ + FMListView *list_view; + GtkTreeSelection *tree_selection; + GList *node; + GList *iters, *l; + CajaFile *file; + + list_view = FM_LIST_VIEW (view); + tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); + + g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view); + + gtk_tree_selection_unselect_all (tree_selection); + for (node = selection; node != NULL; node = node->next) + { + file = node->data; + iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file); + + for (l = iters; l != NULL; l = l->next) + { + gtk_tree_selection_select_iter (tree_selection, + (GtkTreeIter *)l->data); + } + eel_g_list_free_deep (iters); + } + + g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); + fm_directory_view_notify_selection_changed (view); +} + +static void +fm_list_view_invert_selection (FMDirectoryView *view) +{ + FMListView *list_view; + GtkTreeSelection *tree_selection; + GList *node; + GList *iters, *l; + CajaFile *file; + GList *selection = NULL; + + list_view = FM_LIST_VIEW (view); + tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); + + g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view); + + gtk_tree_selection_selected_foreach (tree_selection, + fm_list_view_get_selection_foreach_func, &selection); + + gtk_tree_selection_select_all (tree_selection); + + for (node = selection; node != NULL; node = node->next) + { + file = node->data; + iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file); + + for (l = iters; l != NULL; l = l->next) + { + gtk_tree_selection_unselect_iter (tree_selection, + (GtkTreeIter *)l->data); + } + eel_g_list_free_deep (iters); + } + + g_list_free (selection); + + g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); + fm_directory_view_notify_selection_changed (view); +} + +static void +fm_list_view_select_all (FMDirectoryView *view) +{ + gtk_tree_selection_select_all (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view)); +} + +static void +column_editor_response_callback (GtkWidget *dialog, + int response_id, + gpointer user_data) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +column_chooser_changed_callback (CajaColumnChooser *chooser, + FMListView *view) +{ + CajaFile *file; + char **visible_columns; + char **column_order; + GList *list; + int i; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + + caja_column_chooser_get_settings (chooser, + &visible_columns, + &column_order); + + list = NULL; + for (i = 0; visible_columns[i] != NULL; ++i) + { + list = g_list_prepend (list, visible_columns[i]); + } + list = g_list_reverse (list); + caja_file_set_metadata_list (file, + CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, + list); + g_list_free (list); + + list = NULL; + for (i = 0; column_order[i] != NULL; ++i) + { + list = g_list_prepend (list, column_order[i]); + } + list = g_list_reverse (list); + caja_file_set_metadata_list (file, + CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, + list); + g_list_free (list); + + apply_columns_settings (view, column_order, visible_columns); + + g_strfreev (visible_columns); + g_strfreev (column_order); +} + +static void +column_chooser_set_from_arrays (CajaColumnChooser *chooser, + FMListView *view, + char **visible_columns, + char **column_order) +{ + g_signal_handlers_block_by_func + (chooser, G_CALLBACK (column_chooser_changed_callback), view); + + caja_column_chooser_set_settings (chooser, + visible_columns, + column_order); + + g_signal_handlers_unblock_by_func + (chooser, G_CALLBACK (column_chooser_changed_callback), view); +} + +static void +column_chooser_set_from_settings (CajaColumnChooser *chooser, + FMListView *view) +{ + char **visible_columns; + char **column_order; + + visible_columns = get_visible_columns (view); + column_order = get_column_order (view); + + column_chooser_set_from_arrays (chooser, view, + visible_columns, column_order); + + g_strfreev (visible_columns); + g_strfreev (column_order); +} + +static void +column_chooser_use_default_callback (CajaColumnChooser *chooser, + FMListView *view) +{ + CajaFile *file; + char **default_columns; + char **default_order; + + file = fm_directory_view_get_directory_as_file + (FM_DIRECTORY_VIEW (view)); + + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + + /* set view values ourselves, as new metadata could not have been + * updated yet. + */ + default_columns = caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_visible_columns) : + g_strdupv (default_visible_columns_auto_value); + + default_order = caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_columns_order) : + g_strdupv (default_column_order_auto_value); + + apply_columns_settings (view, default_order, default_columns); + column_chooser_set_from_arrays (chooser, view, + default_columns, default_order); + + g_strfreev (default_columns); + g_strfreev (default_order); +} + +static GtkWidget * +create_column_editor (FMListView *view) +{ + GtkWidget *window; + GtkWidget *label; + GtkWidget *box; + GtkWidget *column_chooser; + GtkWidget *alignment; + CajaFile *file; + char *str; + char *name; + const char *label_text; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + name = caja_file_get_display_name (file); + str = g_strdup_printf (_("%s Visible Columns"), name); + g_free (name); + + window = gtk_dialog_new_with_buttons (str, + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + g_free (str); + g_signal_connect (window, "response", + G_CALLBACK (column_editor_response_callback), NULL); + + gtk_window_set_default_size (GTK_WINDOW (window), 300, 400); + + box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (box), 12); + gtk_widget_show (box); + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), box); + + label_text = _("Choose the order of information to appear in this folder:"); + str = g_strconcat ("<b>", label_text, "</b>", NULL); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), str); + gtk_label_set_line_wrap (GTK_LABEL (label), FALSE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + + g_free (str); + + alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), + 0, 0, 12, 0); + gtk_widget_show (alignment); + gtk_box_pack_start (GTK_BOX (box), alignment, TRUE, TRUE, 0); + + column_chooser = caja_column_chooser_new (file); + gtk_widget_show (column_chooser); + gtk_container_add (GTK_CONTAINER (alignment), column_chooser); + + g_signal_connect (column_chooser, "changed", + G_CALLBACK (column_chooser_changed_callback), + view); + g_signal_connect (column_chooser, "use_default", + G_CALLBACK (column_chooser_use_default_callback), + view); + + column_chooser_set_from_settings + (CAJA_COLUMN_CHOOSER (column_chooser), view); + + return window; +} + +static void +action_visible_columns_callback (GtkAction *action, + gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + if (list_view->details->column_editor) + { + gtk_window_present (GTK_WINDOW (list_view->details->column_editor)); + } + else + { + list_view->details->column_editor = create_column_editor (list_view); + eel_add_weak_pointer (&list_view->details->column_editor); + + gtk_widget_show (list_view->details->column_editor); + } +} + +static const GtkActionEntry list_view_entries[] = +{ + /* name, stock id */ { "Visible Columns", NULL, + /* label, accelerator */ N_("Visible _Columns..."), NULL, + /* tooltip */ N_("Select the columns visible in this folder"), + G_CALLBACK (action_visible_columns_callback) + }, +}; + +static void +fm_list_view_merge_menus (FMDirectoryView *view) +{ + FMListView *list_view; + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + const char *ui; + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); + + list_view = FM_LIST_VIEW (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + + action_group = gtk_action_group_new ("ListViewActions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + list_view->details->list_action_group = action_group; + gtk_action_group_add_actions (action_group, + list_view_entries, G_N_ELEMENTS (list_view_entries), + list_view); + + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); /* owned by ui manager */ + + ui = caja_ui_string_get ("caja-list-view-ui.xml"); + list_view->details->list_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + + list_view->details->menus_ready = TRUE; +} + +static void +fm_list_view_unmerge_menus (FMDirectoryView *view) +{ + FMListView *list_view; + GtkUIManager *ui_manager; + + list_view = FM_LIST_VIEW (view); + + FM_DIRECTORY_VIEW_CLASS (fm_list_view_parent_class)->unmerge_menus (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + if (ui_manager != NULL) + { + caja_ui_unmerge_ui (ui_manager, + &list_view->details->list_merge_id, + &list_view->details->list_action_group); + } +} + +static void +fm_list_view_update_menus (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + /* don't update if the menus aren't ready */ + if (!list_view->details->menus_ready) + { + return; + } + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); +} + +/* Reset sort criteria and zoom level to match defaults */ +static void +fm_list_view_reset_to_defaults (FMDirectoryView *view) +{ + CajaFile *file; + const gchar *default_sort_order; + gboolean default_sort_reversed; + + file = fm_directory_view_get_directory_as_file (view); + + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, NULL, NULL); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, NULL, NULL); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, NULL, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + + default_sort_order = get_default_sort_order (file, &default_sort_reversed); + + gtk_tree_sortable_set_sort_column_id + (GTK_TREE_SORTABLE (FM_LIST_VIEW (view)->details->model), + fm_list_model_get_sort_column_id_from_attribute (FM_LIST_VIEW (view)->details->model, + g_quark_from_string (default_sort_order)), + default_sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); + + fm_list_view_set_zoom_level (FM_LIST_VIEW (view), get_default_zoom_level (), FALSE); + set_columns_settings_from_metadata_and_preferences (FM_LIST_VIEW (view)); +} + +static void +fm_list_view_scale_font_size (FMListView *view, + CajaZoomLevel new_level) +{ + GList *l; + static gboolean first_time = TRUE; + static double pango_scale[7]; + int medium; + int i; + + g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST); + + if (first_time) + { + first_time = FALSE; + medium = CAJA_ZOOM_LEVEL_SMALLER; + pango_scale[medium] = PANGO_SCALE_MEDIUM; + for (i = medium; i > CAJA_ZOOM_LEVEL_SMALLEST; i--) + { + pango_scale[i - 1] = (1 / 1.2) * pango_scale[i]; + } + for (i = medium; i < CAJA_ZOOM_LEVEL_LARGEST; i++) + { + pango_scale[i + 1] = 1.2 * pango_scale[i]; + } + } + + g_object_set (G_OBJECT (view->details->file_name_cell), + "scale", pango_scale[new_level], + NULL); + for (l = view->details->cells; l != NULL; l = l->next) + { + g_object_set (G_OBJECT (l->data), + "scale", pango_scale[new_level], + NULL); + } +} + +static void +fm_list_view_set_zoom_level (FMListView *view, + CajaZoomLevel new_level, + gboolean always_emit) +{ + int icon_size; + int column, emblem_column; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST); + + if (view->details->zoom_level == new_level) + { + if (always_emit) + { + g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed"); + } + return; + } + + view->details->zoom_level = new_level; + g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed"); + + caja_file_set_integer_metadata + (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), + CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, + get_default_zoom_level (), + new_level); + + /* Select correctly scaled icons. */ + column = fm_list_model_get_column_id_from_zoom_level (new_level); + emblem_column = fm_list_model_get_emblem_column_id_from_zoom_level (new_level); + gtk_tree_view_column_set_attributes (view->details->file_name_column, + GTK_CELL_RENDERER (view->details->pixbuf_cell), + "pixbuf", column, + "pixbuf_emblem", emblem_column, + NULL); + + /* Scale text. */ + fm_list_view_scale_font_size (view, new_level); + + /* Make all rows the same size. */ + icon_size = caja_get_icon_size_for_zoom_level (new_level); + gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (view->details->pixbuf_cell), + -1, icon_size); + + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); +} + +static void +fm_list_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ + FMListView *list_view; + gint new_level; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + new_level = list_view->details->zoom_level + zoom_increment; + + if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST) + { + fm_list_view_set_zoom_level (list_view, new_level, FALSE); + } +} + +static CajaZoomLevel +fm_list_view_get_zoom_level (FMDirectoryView *view) +{ + FMListView *list_view; + + g_return_val_if_fail (FM_IS_LIST_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + + list_view = FM_LIST_VIEW (view); + + return list_view->details->zoom_level; +} + +static void +fm_list_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ + FMListView *list_view; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + + fm_list_view_set_zoom_level (list_view, zoom_level, FALSE); +} + +static void +fm_list_view_restore_default_zoom_level (FMDirectoryView *view) +{ + FMListView *list_view; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + + fm_list_view_set_zoom_level (list_view, get_default_zoom_level (), FALSE); +} + +static gboolean +fm_list_view_can_zoom_in (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FM_LIST_VIEW (view)->details->zoom_level < CAJA_ZOOM_LEVEL_LARGEST; +} + +static gboolean +fm_list_view_can_zoom_out (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FM_LIST_VIEW (view)->details->zoom_level > CAJA_ZOOM_LEVEL_SMALLEST; +} + +static void +fm_list_view_start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ + FMListView *list_view; + GtkTreeIter iter; + GtkTreePath *path; + + list_view = FM_LIST_VIEW (view); + + /* Select all if we are in renaming mode already */ + if (list_view->details->file_name_column && list_view->details->editable_widget) + { + gtk_editable_select_region ( + GTK_EDITABLE (list_view->details->editable_widget), + 0, + -1); + return; + } + + if (!fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) + { + return; + } + + /* Freeze updates to the view to prevent losing rename focus when the tree view updates */ + fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (view)); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter); + + /* Make filename-cells editable. */ + g_object_set (G_OBJECT (list_view->details->file_name_cell), + "editable", TRUE, + NULL); + + gtk_tree_view_scroll_to_cell (list_view->details->tree_view, + NULL, + list_view->details->file_name_column, + TRUE, 0.0, 0.0); + gtk_tree_view_set_cursor (list_view->details->tree_view, + path, + list_view->details->file_name_column, + TRUE); + + gtk_tree_path_free (path); +} + +static void +fm_list_view_click_policy_changed (FMDirectoryView *directory_view) +{ + GdkWindow *win; + GdkDisplay *display; + FMListView *view; + GtkTreeIter iter; + GtkTreeView *tree; + + view = FM_LIST_VIEW (directory_view); + + /* ensure that we unset the hand cursor and refresh underlined rows */ + if (click_policy_auto_value == CAJA_CLICK_POLICY_DOUBLE) + { + if (view->details->hover_path != NULL) + { + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model), + &iter, view->details->hover_path)) + { + gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model), + view->details->hover_path, &iter); + } + + gtk_tree_path_free (view->details->hover_path); + view->details->hover_path = NULL; + } + + tree = view->details->tree_view; + if (gtk_widget_get_realized (GTK_WIDGET (tree))) + { + win = gtk_widget_get_window (GTK_WIDGET (tree)); + gdk_window_set_cursor (win, NULL); + + display = gtk_widget_get_display (GTK_WIDGET (view)); + if (display != NULL) + { + gdk_display_flush (display); + } + } + + if (hand_cursor != NULL) + { + gdk_cursor_unref (hand_cursor); + hand_cursor = NULL; + } + } + else if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + if (hand_cursor == NULL) + { + hand_cursor = gdk_cursor_new(GDK_HAND2); + } + } +} + +static void +default_sort_order_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_sort_order_from_metadata_and_preferences (list_view); +} + +static void +default_zoom_level_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_zoom_level_from_metadata_and_preferences (list_view); +} + +static void +default_visible_columns_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +default_column_order_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +fm_list_view_sort_directories_first_changed (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + fm_list_model_set_should_sort_directories_first (list_view->details->model, + fm_directory_view_should_sort_directories_first (view)); +} + +static int +fm_list_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + return fm_list_model_compare_func (list_view->details->model, file1, file2); +} + +static gboolean +fm_list_view_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FALSE; +} + +static void +fm_list_view_dispose (GObject *object) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (object); + + if (list_view->details->model) + { + stop_cell_editing (list_view); + g_object_unref (list_view->details->model); + list_view->details->model = NULL; + } + + if (list_view->details->drag_dest) + { + g_object_unref (list_view->details->drag_dest); + list_view->details->drag_dest = NULL; + } + + if (list_view->details->renaming_file_activate_timeout != 0) + { + g_source_remove (list_view->details->renaming_file_activate_timeout); + list_view->details->renaming_file_activate_timeout = 0; + } + + if (list_view->details->clipboard_handler_id != 0) + { + g_signal_handler_disconnect (caja_clipboard_monitor_get (), + list_view->details->clipboard_handler_id); + list_view->details->clipboard_handler_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +fm_list_view_finalize (GObject *object) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (object); + + g_free (list_view->details->original_name); + list_view->details->original_name = NULL; + + if (list_view->details->double_click_path[0]) + { + gtk_tree_path_free (list_view->details->double_click_path[0]); + } + if (list_view->details->double_click_path[1]) + { + gtk_tree_path_free (list_view->details->double_click_path[1]); + } + if (list_view->details->new_selection_path) + { + gtk_tree_path_free (list_view->details->new_selection_path); + } + + g_list_free (list_view->details->cells); + g_hash_table_destroy (list_view->details->columns); + + if (list_view->details->hover_path != NULL) + { + gtk_tree_path_free (list_view->details->hover_path); + } + + if (list_view->details->column_editor != NULL) + { + gtk_widget_destroy (list_view->details->column_editor); + } + + g_free (list_view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_list_view_emblems_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_LIST_VIEW (directory_view)); + + /* FIXME: This needs to update the emblems of the icons, since + * relative emblems may have changed. + */ +} + +static char * +fm_list_view_get_first_visible_file (CajaView *view) +{ + CajaFile *file; + GtkTreePath *path; + GtkTreeIter iter; + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (gtk_tree_view_get_path_at_pos (list_view->details->tree_view, + 0, 0, + &path, NULL, NULL, NULL)) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (list_view->details->model), + &iter, path); + + gtk_tree_path_free (path); + + gtk_tree_model_get (GTK_TREE_MODEL (list_view->details->model), + &iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + if (file) + { + char *uri; + + uri = caja_file_get_uri (file); + + caja_file_unref (file); + + return uri; + } + } + + return NULL; +} + +static void +fm_list_view_scroll_to_file (FMListView *view, + CajaFile *file) +{ + GtkTreePath *path; + GtkTreeIter iter; + + if (!fm_list_model_get_first_iter_for_file (view->details->model, file, &iter)) + { + return; + } + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->model), &iter); + + gtk_tree_view_scroll_to_cell (view->details->tree_view, + path, NULL, + TRUE, 0.0, 0.0); + + gtk_tree_path_free (path); +} + +static void +list_view_scroll_to_file (CajaView *view, + const char *uri) +{ + CajaFile *file; + + if (uri != NULL) + { + /* Only if existing, since we don't want to add the file to + the directory if it has been removed since then */ + file = caja_file_get_existing_by_uri (uri); + if (file != NULL) + { + fm_list_view_scroll_to_file (FM_LIST_VIEW (view), file); + caja_file_unref (file); + } + } +} + +static void +list_view_notify_clipboard_info (CajaClipboardMonitor *monitor, + CajaClipboardInfo *info, + FMListView *view) +{ + /* this could be called as a result of _end_loading() being + * called after _dispose(), where the model is cleared. + */ + if (view->details->model == NULL) + { + return; + } + + if (info != NULL && info->cut) + { + fm_list_model_set_highlight_for_files (view->details->model, info->files); + } + else + { + fm_list_model_set_highlight_for_files (view->details->model, NULL); + } +} + +static void +fm_list_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ + CajaClipboardMonitor *monitor; + CajaClipboardInfo *info; + + monitor = caja_clipboard_monitor_get (); + info = caja_clipboard_monitor_get_clipboard_info (monitor); + + list_view_notify_clipboard_info (monitor, info, FM_LIST_VIEW (view)); +} + +static void +real_set_is_active (FMDirectoryView *view, + gboolean is_active) +{ + GtkWidget *tree_view; + GtkStyle *style; + GdkColor color; + + tree_view = GTK_WIDGET (fm_list_view_get_tree_view (FM_LIST_VIEW (view))); + + if (is_active) + { + gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, NULL); + } + else + { + style = gtk_widget_get_style (tree_view); + color = style->base[GTK_STATE_INSENSITIVE]; + gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, &color); + } + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, + set_is_active, (view, is_active)); +} + +static void +fm_list_view_class_init (FMListViewClass *class) +{ + FMDirectoryViewClass *fm_directory_view_class; + + fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class); + + G_OBJECT_CLASS (class)->dispose = fm_list_view_dispose; + G_OBJECT_CLASS (class)->finalize = fm_list_view_finalize; + + fm_directory_view_class->add_file = fm_list_view_add_file; + fm_directory_view_class->begin_loading = fm_list_view_begin_loading; + fm_directory_view_class->end_loading = fm_list_view_end_loading; + fm_directory_view_class->bump_zoom_level = fm_list_view_bump_zoom_level; + fm_directory_view_class->can_zoom_in = fm_list_view_can_zoom_in; + fm_directory_view_class->can_zoom_out = fm_list_view_can_zoom_out; + fm_directory_view_class->click_policy_changed = fm_list_view_click_policy_changed; + fm_directory_view_class->clear = fm_list_view_clear; + fm_directory_view_class->file_changed = fm_list_view_file_changed; + fm_directory_view_class->get_background_widget = fm_list_view_get_background_widget; + fm_directory_view_class->get_selection = fm_list_view_get_selection; + fm_directory_view_class->get_selection_for_file_transfer = fm_list_view_get_selection_for_file_transfer; + fm_directory_view_class->get_item_count = fm_list_view_get_item_count; + fm_directory_view_class->is_empty = fm_list_view_is_empty; + fm_directory_view_class->remove_file = fm_list_view_remove_file; + fm_directory_view_class->merge_menus = fm_list_view_merge_menus; + fm_directory_view_class->unmerge_menus = fm_list_view_unmerge_menus; + fm_directory_view_class->update_menus = fm_list_view_update_menus; + fm_directory_view_class->reset_to_defaults = fm_list_view_reset_to_defaults; + fm_directory_view_class->restore_default_zoom_level = fm_list_view_restore_default_zoom_level; + fm_directory_view_class->reveal_selection = fm_list_view_reveal_selection; + fm_directory_view_class->select_all = fm_list_view_select_all; + fm_directory_view_class->set_selection = fm_list_view_set_selection; + fm_directory_view_class->invert_selection = fm_list_view_invert_selection; + fm_directory_view_class->compare_files = fm_list_view_compare_files; + fm_directory_view_class->sort_directories_first_changed = fm_list_view_sort_directories_first_changed; + fm_directory_view_class->start_renaming_file = fm_list_view_start_renaming_file; + fm_directory_view_class->get_zoom_level = fm_list_view_get_zoom_level; + fm_directory_view_class->zoom_to_level = fm_list_view_zoom_to_level; + fm_directory_view_class->emblems_changed = fm_list_view_emblems_changed; + fm_directory_view_class->end_file_changes = fm_list_view_end_file_changes; + fm_directory_view_class->using_manual_layout = fm_list_view_using_manual_layout; + fm_directory_view_class->set_is_active = real_set_is_active; + + eel_preferences_add_auto_enum (CAJA_PREFERENCES_CLICK_POLICY, + &click_policy_auto_value); + eel_preferences_add_auto_string (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER, + (const char **) &default_sort_order_auto_value); + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + &default_sort_reversed_auto_value); + eel_preferences_add_auto_enum (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, + (int *) &default_zoom_level_auto_value); + eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, + &default_visible_columns_auto_value); + eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, + &default_column_order_auto_value); +} + +static const char * +fm_list_view_get_id (CajaView *view) +{ + return FM_LIST_VIEW_ID; +} + + +static void +fm_list_view_iface_init (CajaViewIface *iface) +{ + fm_directory_view_init_view_iface (iface); + + iface->get_view_id = fm_list_view_get_id; + iface->get_first_visible_file = fm_list_view_get_first_visible_file; + iface->scroll_to_file = list_view_scroll_to_file; + iface->get_title = NULL; +} + + +static void +fm_list_view_init (FMListView *list_view) +{ + list_view->details = g_new0 (FMListViewDetails, 1); + + create_and_set_up_tree_view (list_view); + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER, + default_sort_order_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + default_sort_order_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, + default_visible_columns_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, + default_column_order_changed_callback, + list_view, G_OBJECT (list_view)); + + fm_list_view_click_policy_changed (FM_DIRECTORY_VIEW (list_view)); + + fm_list_view_sort_directories_first_changed (FM_DIRECTORY_VIEW (list_view)); + + /* ensure that the zoom level is always set in begin_loading */ + list_view->details->zoom_level = CAJA_ZOOM_LEVEL_SMALLEST - 1; + + list_view->details->hover_path = NULL; + list_view->details->clipboard_handler_id = + g_signal_connect (caja_clipboard_monitor_get (), + "clipboard_info", + G_CALLBACK (list_view_notify_clipboard_info), list_view); +} + +static CajaView * +fm_list_view_create (CajaWindowSlotInfo *slot) +{ + FMListView *view; + + view = g_object_new (FM_TYPE_LIST_VIEW, + "window-slot", slot, + NULL); + return CAJA_VIEW (view); +} + +static gboolean +fm_list_view_supports_uri (const char *uri, + GFileType file_type, + const char *mime_type) +{ + if (file_type == G_FILE_TYPE_DIRECTORY) + { + return TRUE; + } + if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) + { + return TRUE; + } + if (g_str_has_prefix (uri, "trash:")) + { + return TRUE; + } + if (g_str_has_prefix (uri, EEL_SEARCH_URI)) + { + return TRUE; + } + + return FALSE; +} + +static CajaViewInfo fm_list_view = +{ + FM_LIST_VIEW_ID, + /* translators: this is used in the view selection dropdown + * of navigation windows and in the preferences dialog */ + N_("List View"), + /* translators: this is used in the view menu */ + N_("_List"), + N_("The list view encountered an error."), + N_("The list view encountered an error while starting up."), + N_("Display this location with the list view."), + fm_list_view_create, + fm_list_view_supports_uri +}; + +void +fm_list_view_register (void) +{ + fm_list_view.view_combo_label = _(fm_list_view.view_combo_label); + fm_list_view.view_menu_label_with_mnemonic = _(fm_list_view.view_menu_label_with_mnemonic); + fm_list_view.error_label = _(fm_list_view.error_label); + fm_list_view.startup_error_label = _(fm_list_view.startup_error_label); + fm_list_view.display_location_label = _(fm_list_view.display_location_label); + + caja_view_factory_register (&fm_list_view); +} + +GtkTreeView* +fm_list_view_get_tree_view (FMListView *list_view) +{ + return list_view->details->tree_view; +} diff --git a/src/file-manager/fm-list-view.h b/src/file-manager/fm-list-view.h new file mode 100644 index 00000000..2defc6ca --- /dev/null +++ b/src/file-manager/fm-list-view.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view.h - interface for list view of directory. + + Copyright (C) 2000 Eazel, Inc. + Copyright (C) 2001 Anders Carlsson <[email protected]> + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: John Sullivan <[email protected]> + Anders Carlsson <[email protected]> +*/ + +#ifndef FM_LIST_VIEW_H +#define FM_LIST_VIEW_H + +#include "fm-directory-view.h" + +#define FM_TYPE_LIST_VIEW fm_list_view_get_type() +#define FM_LIST_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_LIST_VIEW, FMListView)) +#define FM_LIST_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_LIST_VIEW, FMListViewClass)) +#define FM_IS_LIST_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_LIST_VIEW)) +#define FM_IS_LIST_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_LIST_VIEW)) +#define FM_LIST_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_LIST_VIEW, FMListViewClass)) + +#define FM_LIST_VIEW_ID "OAFIID:Caja_File_Manager_List_View" + +typedef struct FMListViewDetails FMListViewDetails; + +typedef struct +{ + FMDirectoryView parent_instance; + FMListViewDetails *details; +} FMListView; + +typedef struct +{ + FMDirectoryViewClass parent_class; +} FMListViewClass; + +GType fm_list_view_get_type (void); +void fm_list_view_register (void); +GtkTreeView* fm_list_view_get_tree_view (FMListView *list_view); + +#endif /* FM_LIST_VIEW_H */ diff --git a/src/file-manager/fm-properties-window.c b/src/file-manager/fm-properties-window.c new file mode 100644 index 00000000..6310b407 --- /dev/null +++ b/src/file-manager/fm-properties-window.c @@ -0,0 +1,5835 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-properties-window.c - window that lets user modify file properties + + Copyright (C) 2000 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Darin Adler <[email protected]> +*/ + +#include <config.h> +#include "fm-properties-window.h" +#include "fm-ditem-page.h" + +#define MATE_DESKTOP_USE_UNSTABLE_API + +#include "fm-error-reporting.h" +#include "libcaja-private/caja-mime-application-chooser.h" +#include <eel/eel-accessibility.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-mate-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-labeled-image.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-wrap-table.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <glib/gi18n.h> +#include <libmateui/mate-desktop-thumbnail.h> +#include <libcaja-extension/caja-property-page-provider.h> +#include <libcaja-private/caja-customization-data.h> +#include <libcaja-private/caja-entry.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-emblem-utils.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-undo-signal-handlers.h> +#include <libcaja-private/caja-mime-actions.h> +#include <libcaja-private/caja-undo.h> +#include <string.h> +#include <sys/stat.h> +#include <cairo.h> + +#if HAVE_SYS_VFS_H +#include <sys/vfs.h> +#elif HAVE_SYS_MOUNT_H +#if HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <sys/mount.h> +#endif + +#define USED_FILL_R (0.988235294 * 65535) +#define USED_FILL_G (0.91372549 * 65535) +#define USED_FILL_B (0.309803922 * 65535) + +#define FREE_FILL_R (0.447058824 * 65535) +#define FREE_FILL_G (0.623529412 * 65535) +#define FREE_FILL_B (0.811764706 * 65535) + + +#define PREVIEW_IMAGE_WIDTH 96 + +#define ROW_PAD 6 + +static GHashTable *windows; +static GHashTable *pending_lists; + +struct FMPropertiesWindowDetails { + GList *original_files; + GList *target_files; + + GtkNotebook *notebook; + + GtkTable *basic_table; + GtkTable *permissions_table; + gboolean advanced_permissions; + + GtkWidget *icon_button; + GtkWidget *icon_image; + GtkWidget *icon_chooser; + + GtkLabel *name_label; + GtkWidget *name_field; + unsigned int name_row; + char *pending_name; + + GtkLabel *directory_contents_title_field; + GtkLabel *directory_contents_value_field; + guint update_directory_contents_timeout_id; + guint update_files_timeout_id; + + GList *emblem_buttons; + GHashTable *initial_emblems; + + CajaFile *group_change_file; + char *group_change_group; + unsigned int group_change_timeout; + CajaFile *owner_change_file; + char *owner_change_owner; + unsigned int owner_change_timeout; + + GList *permission_buttons; + GList *permission_combos; + GHashTable *initial_permissions; + gboolean has_recursive_apply; + + GList *value_fields; + + GList *mime_list; + + gboolean deep_count_finished; + + guint total_count; + goffset total_size; + + guint long_operation_underway; + + GList *changed_files; + + guint64 volume_capacity; + guint64 volume_free; + + GdkColor used_color; + GdkColor free_color; + GdkColor used_stroke_color; + GdkColor free_stroke_color; +}; + +enum { + PERMISSIONS_CHECKBOXES_OWNER_ROW, + PERMISSIONS_CHECKBOXES_GROUP_ROW, + PERMISSIONS_CHECKBOXES_OTHERS_ROW, + PERMISSIONS_CHECKBOXES_ROW_COUNT +}; + +enum { + PERMISSIONS_CHECKBOXES_READ_COLUMN, + PERMISSIONS_CHECKBOXES_WRITE_COLUMN, + PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, + PERMISSIONS_CHECKBOXES_COLUMN_COUNT +}; + +enum { + TITLE_COLUMN, + VALUE_COLUMN, + COLUMN_COUNT +}; + +typedef struct { + GList *original_files; + GList *target_files; + GtkWidget *parent_widget; + char *pending_key; + GHashTable *pending_files; +} StartupData; + +/* drag and drop definitions */ + +enum { + TARGET_URI_LIST, + TARGET_MATE_URI_LIST, + TARGET_RESET_BACKGROUND +}; + +static const GtkTargetEntry target_table[] = { + { "text/uri-list", 0, TARGET_URI_LIST }, + { "x-special/mate-icon-list", 0, TARGET_MATE_URI_LIST }, + { "x-special/mate-reset-background", 0, TARGET_RESET_BACKGROUND } +}; + +#define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */ +#define FILES_UPDATE_INTERVAL 200 /* milliseconds */ +#define STANDARD_EMBLEM_HEIGHT 52 +#define EMBLEM_LABEL_SPACING 2 + +/* + * A timeout before changes through the user/group combo box will be applied. + * When quickly changing owner/groups (i.e. by keyboard or scroll wheel), + * this ensures that the GUI doesn't end up unresponsive. + * + * Both combos react on changes by scheduling a new change and unscheduling + * or cancelling old pending changes. + */ +#define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */ + +static void directory_contents_value_field_update (FMPropertiesWindow *window); +static void file_changed_callback (CajaFile *file, + gpointer user_data); +static void permission_button_update (FMPropertiesWindow *window, + GtkToggleButton *button); +static void permission_combo_update (FMPropertiesWindow *window, + GtkComboBox *combo); +static void value_field_update (FMPropertiesWindow *window, + GtkLabel *field); +static void properties_window_update (FMPropertiesWindow *window, + GList *files); +static void is_directory_ready_callback (CajaFile *file, + gpointer data); +static void cancel_group_change_callback (FMPropertiesWindow *window); +static void cancel_owner_change_callback (FMPropertiesWindow *window); +static void parent_widget_destroyed_callback (GtkWidget *widget, + gpointer callback_data); +static void select_image_button_callback (GtkWidget *widget, + FMPropertiesWindow *properties_window); +static void set_icon (const char *icon_path, + FMPropertiesWindow *properties_window); +static void remove_pending (StartupData *data, + gboolean cancel_call_when_ready, + gboolean cancel_timed_wait, + gboolean cancel_destroy_handler); +static void append_extension_pages (FMPropertiesWindow *window); + +static gboolean name_field_focus_out (CajaEntry *name_field, + GdkEventFocus *event, + gpointer callback_data); +static void name_field_activate (CajaEntry *name_field, + gpointer callback_data); +static GtkLabel *attach_ellipsizing_value_label (GtkTable *table, + int row, + int column, + const char *initial_text); + +static GtkWidget* create_pie_widget (FMPropertiesWindow *window); + +G_DEFINE_TYPE (FMPropertiesWindow, fm_properties_window, GTK_TYPE_DIALOG); +#define parent_class fm_properties_window_parent_class + +static gboolean +is_multi_file_window (FMPropertiesWindow *window) +{ + GList *l; + int count; + + count = 0; + + for (l = window->details->original_files; l != NULL; l = l->next) { + if (!caja_file_is_gone (CAJA_FILE (l->data))) { + count++; + if (count > 1) { + return TRUE; + } + } + } + + return FALSE; +} + +static int +get_not_gone_original_file_count (FMPropertiesWindow *window) +{ + GList *l; + int count; + + count = 0; + + for (l = window->details->original_files; l != NULL; l = l->next) { + if (!caja_file_is_gone (CAJA_FILE (l->data))) { + count++; + } + } + + return count; +} + +static CajaFile * +get_original_file (FMPropertiesWindow *window) +{ + g_return_val_if_fail (!is_multi_file_window (window), NULL); + + if (window->details->original_files == NULL) { + return NULL; + } + + return CAJA_FILE (window->details->original_files->data); +} + +static CajaFile * +get_target_file_for_original_file (CajaFile *file) +{ + CajaFile *target_file; + GFile *location; + char *uri_to_display; + CajaDesktopLink *link; + + target_file = NULL; + if (CAJA_IS_DESKTOP_ICON_FILE (file)) { + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); + + if (link != NULL) { + /* map to linked URI for these types of links */ + location = caja_desktop_link_get_activation_location (link); + if (location) { + target_file = caja_file_get (location); + g_object_unref (location); + } + + g_object_unref (link); + } + } else { + uri_to_display = caja_file_get_activation_uri (file); + if (uri_to_display != NULL) { + target_file = caja_file_get_by_uri (uri_to_display); + g_free (uri_to_display); + } + } + + if (target_file != NULL) { + return target_file; + } + + /* Ref passed-in file here since we've decided to use it. */ + caja_file_ref (file); + return file; +} + +static CajaFile * +get_target_file (FMPropertiesWindow *window) +{ + return CAJA_FILE (window->details->target_files->data); +} + +static void +add_prompt (GtkVBox *vbox, const char *prompt_text, gboolean pack_at_start) +{ + GtkWidget *prompt; + + prompt = gtk_label_new (prompt_text); + gtk_label_set_justify (GTK_LABEL (prompt), GTK_JUSTIFY_LEFT); + gtk_label_set_line_wrap (GTK_LABEL (prompt), TRUE); + gtk_widget_show (prompt); + if (pack_at_start) { + gtk_box_pack_start (GTK_BOX (vbox), prompt, FALSE, FALSE, 0); + } else { + gtk_box_pack_end (GTK_BOX (vbox), prompt, FALSE, FALSE, 0); + } +} + +static void +add_prompt_and_separator (GtkVBox *vbox, const char *prompt_text) +{ + GtkWidget *separator_line; + + add_prompt (vbox, prompt_text, FALSE); + + separator_line = gtk_hseparator_new (); + gtk_widget_show (separator_line); + gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, 2*ROW_PAD); +} + +static void +get_image_for_properties_window (FMPropertiesWindow *window, + char **icon_name, + GdkPixbuf **icon_pixbuf) +{ + CajaIconInfo *icon, *new_icon; + GList *l; + + icon = NULL; + for (l = window->details->original_files; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + if (!icon) { + icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING); + } else { + new_icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING); + if (!new_icon || new_icon != icon) { + g_object_unref (icon); + g_object_unref (new_icon); + icon = NULL; + break; + } + g_object_unref (new_icon); + } + } + + if (!icon) { + icon = caja_icon_info_lookup_from_name ("text-x-generic", CAJA_ICON_SIZE_STANDARD); + } + + if (icon_name != NULL) { + *icon_name = g_strdup (caja_icon_info_get_used_name (icon)); + } + + if (icon_pixbuf != NULL) { + *icon_pixbuf = caja_icon_info_get_pixbuf_at_size (icon, CAJA_ICON_SIZE_STANDARD); + } + + g_object_unref (icon); +} + + +static void +update_properties_window_icon (GtkImage *image) +{ + FMPropertiesWindow *window; + GdkPixbuf *pixbuf; + char *name; + + window = g_object_get_data (G_OBJECT (image), "properties_window"); + + get_image_for_properties_window (window, &name, &pixbuf); + + if (name != NULL) { + gtk_window_set_icon_name (GTK_WINDOW (window), name); + } else { + gtk_window_set_icon (GTK_WINDOW (window), pixbuf); + } + + gtk_image_set_from_pixbuf (image, pixbuf); + + g_free (name); + g_object_unref (pixbuf); +} + +/* utility to test if a uri refers to a local image */ +static gboolean +uri_is_local_image (const char *uri) +{ + GdkPixbuf *pixbuf; + char *image_path; + + image_path = g_filename_from_uri (uri, NULL, NULL); + if (image_path == NULL) { + return FALSE; + } + + pixbuf = gdk_pixbuf_new_from_file (image_path, NULL); + g_free (image_path); + + if (pixbuf == NULL) { + return FALSE; + } + g_object_unref (pixbuf); + return TRUE; +} + + +static void +reset_icon (FMPropertiesWindow *properties_window) +{ + GList *l; + + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + caja_file_set_metadata (file, + CAJA_METADATA_KEY_ICON_SCALE, + NULL, NULL); + caja_file_set_metadata (file, + CAJA_METADATA_KEY_CUSTOM_ICON, + NULL, NULL); + } +} + + +static void +fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context, + int x, int y, + GtkSelectionData *selection_data, + guint info, guint time) +{ + char **uris; + gboolean exactly_one; + GtkImage *image; + GtkWindow *window; + + image = GTK_IMAGE (widget); + window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image))); + + if (info == TARGET_RESET_BACKGROUND) { + reset_icon (FM_PROPERTIES_WINDOW (window)); + + return; + } + + uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); + exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + + + if (!exactly_one) { + eel_show_error_dialog + (_("You cannot assign more than one custom icon at a time!"), + _("Please drag just one image to set a custom icon."), + window); + } else { + if (uri_is_local_image (uris[0])) { + set_icon (uris[0], FM_PROPERTIES_WINDOW (window)); + } else { + GFile *f; + + f = g_file_new_for_uri (uris[0]); + if (!g_file_is_native (f)) { + eel_show_error_dialog + (_("The file that you dropped is not local."), + _("You can only use local images as custom icons."), + window); + + } else { + eel_show_error_dialog + (_("The file that you dropped is not an image."), + _("You can only use local images as custom icons."), + window); + } + g_object_unref (f); + } + } + g_strfreev (uris); +} + +static GtkWidget * +create_image_widget (FMPropertiesWindow *window, + gboolean is_customizable) +{ + GtkWidget *button; + GtkWidget *image; + GdkPixbuf *pixbuf; + + get_image_for_properties_window (window, NULL, &pixbuf); + + image = gtk_image_new (); + gtk_widget_show (image); + + button = NULL; + if (is_customizable) { + button = gtk_button_new (); + gtk_container_add (GTK_CONTAINER (button), image); + + /* prepare the image to receive dropped objects to assign custom images */ + gtk_drag_dest_set (GTK_WIDGET (image), + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (image, "drag_data_received", + G_CALLBACK (fm_properties_window_drag_data_received), NULL); + g_signal_connect (button, "clicked", + G_CALLBACK (select_image_button_callback), window); + } + + gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); + + g_object_unref (pixbuf); + + g_object_set_data (G_OBJECT (image), "properties_window", window); + + window->details->icon_image = image; + window->details->icon_button = button; + + return button != NULL ? button : image; +} + +static void +set_name_field (FMPropertiesWindow *window, const gchar *original_name, + const gchar *name) +{ + gboolean new_widget; + gboolean use_label; + + /* There are four cases here: + * 1) Changing the text of a label + * 2) Changing the text of an entry + * 3) Creating label (potentially replacing entry) + * 4) Creating entry (potentially replacing label) + */ + use_label = is_multi_file_window (window) || !caja_file_can_rename (get_original_file (window)); + new_widget = !window->details->name_field || (use_label ? CAJA_IS_ENTRY (window->details->name_field) : GTK_IS_LABEL (window->details->name_field)); + + if (new_widget) { + if (window->details->name_field) { + gtk_widget_destroy (window->details->name_field); + } + + if (use_label) { + window->details->name_field = + GTK_WIDGET (attach_ellipsizing_value_label + (window->details->basic_table, + window->details->name_row, + VALUE_COLUMN, name)); + } else { + window->details->name_field = caja_entry_new (); + gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name); + gtk_widget_show (window->details->name_field); + gtk_table_attach (window->details->basic_table, + window->details->name_field, + VALUE_COLUMN, + VALUE_COLUMN + 1, + window->details->name_row, + window->details->name_row + 1, + GTK_FILL, 0, + 0, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (window->details->name_label), window->details->name_field); + + /* FIXME bugzilla.gnome.org 42151: + * With this (and one place elsewhere in this file, not sure which is the + * trouble-causer) code in place, bug 2151 happens (crash on quit). Since + * we've removed Undo from Caja for now, I'm just ifdeffing out this + * code rather than trying to fix 2151 now. Note that it might be possible + * to fix 2151 without making Undo actually work, it's just not worth the + * trouble. + */ +#ifdef UNDO_ENABLED + /* Set up name field for undo */ + caja_undo_set_up_caja_entry_for_undo ( CAJA_ENTRY (window->details->name_field)); + caja_undo_editable_set_undo_key (GTK_EDITABLE (window->details->name_field), TRUE); +#endif + + g_signal_connect_object (window->details->name_field, "focus_out_event", + G_CALLBACK (name_field_focus_out), window, 0); + g_signal_connect_object (window->details->name_field, "activate", + G_CALLBACK (name_field_activate), window, 0); + } + + gtk_widget_show (window->details->name_field); + } + /* Only replace text if the file's name has changed. */ + else if (original_name == NULL || strcmp (original_name, name) != 0) { + + if (use_label) { + gtk_label_set_text (GTK_LABEL (window->details->name_field), name); + } else { + /* Only reset the text if it's different from what is + * currently showing. This causes minimal ripples (e.g. + * selection change). + */ + gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->details->name_field), 0, -1); + if (strcmp (displayed_name, name) != 0) { + gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name); + } + g_free (displayed_name); + } + } +} + +static void +update_name_field (FMPropertiesWindow *window) +{ + CajaFile *file; + + gtk_label_set_text_with_mnemonic (window->details->name_label, + ngettext ("_Name:", "_Names:", + get_not_gone_original_file_count (window))); + + if (is_multi_file_window (window)) { + /* Multifile property dialog, show all names */ + GString *str; + char *name; + gboolean first; + GList *l; + + str = g_string_new (""); + + first = TRUE; + + for (l = window->details->target_files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (!caja_file_is_gone (file)) { + if (!first) { + g_string_append (str, ", "); + } + first = FALSE; + + name = caja_file_get_display_name (file); + g_string_append (str, name); + g_free (name); + } + } + set_name_field (window, NULL, str->str); + g_string_free (str, TRUE); + } else { + const char *original_name = NULL; + char *current_name; + + file = get_original_file (window); + + if (file == NULL || caja_file_is_gone (file)) { + current_name = g_strdup (""); + } else { + current_name = caja_file_get_display_name (file); + } + + /* If the file name has changed since the original name was stored, + * update the text in the text field, possibly (deliberately) clobbering + * an edit in progress. If the name hasn't changed (but some other + * aspect of the file might have), then don't clobber changes. + */ + if (window->details->name_field) { + original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), "original_name"); + } + + set_name_field (window, original_name, current_name); + + if (original_name == NULL || + eel_strcmp (original_name, current_name) != 0) { + g_object_set_data_full (G_OBJECT (window->details->name_field), + "original_name", + current_name, + g_free); + } else { + g_free (current_name); + } + } +} + +static void +name_field_restore_original_name (CajaEntry *name_field) +{ + const char *original_name; + char *displayed_name; + + original_name = (const char *) g_object_get_data (G_OBJECT (name_field), + "original_name"); + + if (!original_name) { + return; + } + + displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); + + if (strcmp (original_name, displayed_name) != 0) { + gtk_entry_set_text (GTK_ENTRY (name_field), original_name); + } + caja_entry_select_all (name_field); + + g_free (displayed_name); +} + +static void +rename_callback (CajaFile *file, GFile *res_loc, GError *error, gpointer callback_data) +{ + FMPropertiesWindow *window; + char *new_name; + + window = FM_PROPERTIES_WINDOW (callback_data); + + /* Complain to user if rename failed. */ + if (error != NULL) { + new_name = window->details->pending_name; + fm_report_error_renaming_file (file, + window->details->pending_name, + error, + GTK_WINDOW (window)); + if (window->details->name_field != NULL) { + name_field_restore_original_name (CAJA_ENTRY (window->details->name_field)); + } + } + + g_object_unref (window); +} + +static void +set_pending_name (FMPropertiesWindow *window, const char *name) +{ + g_free (window->details->pending_name); + window->details->pending_name = g_strdup (name); +} + +static void +name_field_done_editing (CajaEntry *name_field, FMPropertiesWindow *window) +{ + CajaFile *file; + char *new_name; + const char *original_name; + + g_return_if_fail (CAJA_IS_ENTRY (name_field)); + + /* Don't apply if the dialog has more than one file */ + if (is_multi_file_window (window)) { + return; + } + + file = get_original_file (window); + + /* This gets called when the window is closed, which might be + * caused by the file having been deleted. + */ + if (file == NULL || caja_file_is_gone (file)) { + return; + } + + new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); + + /* Special case: silently revert text if new text is empty. */ + if (strlen (new_name) == 0) { + name_field_restore_original_name (CAJA_ENTRY (name_field)); + } else { + original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), + "original_name"); + /* Don't rename if not changed since we read the display name. + This is needed so that we don't save the display name to the + file when nothing is changed */ + if (strcmp (new_name, original_name) != 0) { + set_pending_name (window, new_name); + g_object_ref (window); + caja_file_rename (file, new_name, + rename_callback, window); + } + } + + g_free (new_name); +} + +static gboolean +name_field_focus_out (CajaEntry *name_field, + GdkEventFocus *event, + gpointer callback_data) +{ + g_assert (FM_IS_PROPERTIES_WINDOW (callback_data)); + + if (gtk_widget_get_sensitive (GTK_WIDGET (name_field))) { + name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data)); + } + + return FALSE; +} + +static void +name_field_activate (CajaEntry *name_field, gpointer callback_data) +{ + g_assert (CAJA_IS_ENTRY (name_field)); + g_assert (FM_IS_PROPERTIES_WINDOW (callback_data)); + + /* Accept changes. */ + name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data)); + + caja_entry_select_all_at_idle (name_field); +} + +static gboolean +file_has_keyword (CajaFile *file, const char *keyword) +{ + GList *keywords, *word; + + keywords = caja_file_get_keywords (file); + word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp); + eel_g_list_free_deep (keywords); + + return (word != NULL); +} + +static void +get_initial_emblem_state (FMPropertiesWindow *window, + const char *name, + GList **on, + GList **off) +{ + GList *l; + + *on = NULL; + *off = NULL; + + for (l = window->details->original_files; l != NULL; l = l->next) { + GList *initial_emblems; + + initial_emblems = g_hash_table_lookup (window->details->initial_emblems, + l->data); + + if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) { + *on = g_list_prepend (*on, l->data); + } else { + *off = g_list_prepend (*off, l->data); + } + } +} + +static void +emblem_button_toggled (GtkToggleButton *button, + FMPropertiesWindow *window) +{ + GList *l; + GList *keywords; + GList *word; + char *name; + GList *files_on; + GList *files_off; + + name = g_object_get_data (G_OBJECT (button), "caja_emblem_name"); + + files_on = NULL; + files_off = NULL; + if (gtk_toggle_button_get_active (button) + && !gtk_toggle_button_get_inconsistent (button)) { + /* Go to the initial state unless the initial state was + consistent */ + get_initial_emblem_state (window, name, + &files_on, &files_off); + + if (!(files_on && files_off)) { + g_list_free (files_on); + g_list_free (files_off); + files_on = g_list_copy (window->details->original_files); + files_off = NULL; + } + } else if (gtk_toggle_button_get_inconsistent (button) + && !gtk_toggle_button_get_active (button)) { + files_on = g_list_copy (window->details->original_files); + files_off = NULL; + } else { + files_off = g_list_copy (window->details->original_files); + files_on = NULL; + } + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + gtk_toggle_button_set_active (button, files_on != NULL); + gtk_toggle_button_set_inconsistent (button, files_on && files_off); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + for (l = files_on; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + keywords = caja_file_get_keywords (file); + + word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp); + if (!word) { + keywords = g_list_prepend (keywords, g_strdup (name)); + } + caja_file_set_keywords (file, keywords); + eel_g_list_free_deep (keywords); + } + + for (l = files_off; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + keywords = caja_file_get_keywords (file); + + word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp); + if (word) { + keywords = g_list_remove_link (keywords, word); + eel_g_list_free_deep (word); + } + caja_file_set_keywords (file, keywords); + eel_g_list_free_deep (keywords); + } + + g_list_free (files_on); + g_list_free (files_off); +} + +static void +emblem_button_update (FMPropertiesWindow *window, + GtkToggleButton *button) +{ + GList *l; + char *name; + gboolean all_set; + gboolean all_unset; + + name = g_object_get_data (G_OBJECT (button), "caja_emblem_name"); + + all_set = TRUE; + all_unset = TRUE; + for (l = window->details->original_files; l != NULL; l = l->next) { + gboolean has_keyword; + CajaFile *file; + + file = CAJA_FILE (l->data); + + has_keyword = file_has_keyword (file, name); + + if (has_keyword) { + all_unset = FALSE; + } else { + all_set = FALSE; + } + } + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + gtk_toggle_button_set_active (button, !all_unset); + gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + +} + +static void +update_properties_window_title (FMPropertiesWindow *window) +{ + char *name, *title; + CajaFile *file; + + g_return_if_fail (GTK_IS_WINDOW (window)); + + title = g_strdup_printf (_("Properties")); + + if (!is_multi_file_window (window)) { + file = get_original_file (window); + + if (file != NULL) { + g_free (title); + name = caja_file_get_display_name (file); + title = g_strdup_printf (_("%s Properties"), name); + g_free (name); + } + } + + gtk_window_set_title (GTK_WINDOW (window), title); + + g_free (title); +} + +static void +clear_extension_pages (FMPropertiesWindow *window) +{ + int i; + int num_pages; + GtkWidget *page; + + num_pages = gtk_notebook_get_n_pages + (GTK_NOTEBOOK (window->details->notebook)); + + for (i = 0; i < num_pages; i++) { + page = gtk_notebook_get_nth_page + (GTK_NOTEBOOK (window->details->notebook), i); + + if (g_object_get_data (G_OBJECT (page), "is-extension-page")) { + gtk_notebook_remove_page + (GTK_NOTEBOOK (window->details->notebook), i); + num_pages--; + i--; + } + } +} + +static void +refresh_extension_pages (FMPropertiesWindow *window) +{ + clear_extension_pages (window); + append_extension_pages (window); +} + +static void +remove_from_dialog (FMPropertiesWindow *window, + CajaFile *file) +{ + int index; + GList *original_link; + GList *target_link; + CajaFile *original_file; + CajaFile *target_file; + + index = g_list_index (window->details->target_files, file); + if (index == -1) { + index = g_list_index (window->details->original_files, file); + g_return_if_fail (index != -1); + } + + original_link = g_list_nth (window->details->original_files, index); + target_link = g_list_nth (window->details->target_files, index); + + g_return_if_fail (original_link && target_link); + + original_file = CAJA_FILE (original_link->data); + target_file = CAJA_FILE (target_link->data); + + window->details->original_files = g_list_remove_link (window->details->original_files, original_link); + g_list_free (original_link); + + window->details->target_files = g_list_remove_link (window->details->target_files, target_link); + g_list_free (target_link); + + g_hash_table_remove (window->details->initial_emblems, original_file); + g_hash_table_remove (window->details->initial_permissions, target_file); + + g_signal_handlers_disconnect_by_func (original_file, + G_CALLBACK (file_changed_callback), + window); + g_signal_handlers_disconnect_by_func (target_file, + G_CALLBACK (file_changed_callback), + window); + + caja_file_monitor_remove (original_file, &window->details->original_files); + caja_file_monitor_remove (target_file, &window->details->target_files); + + caja_file_unref (original_file); + caja_file_unref (target_file); + +} + +static gboolean +mime_list_equal (GList *a, GList *b) +{ + while (a && b) { + if (strcmp (a->data, b->data)) { + return FALSE; + } + a = a->next; + b = b->next; + } + + return (a == b); +} + +static GList * +get_mime_list (FMPropertiesWindow *window) +{ + GList *ret; + GList *l; + + ret = NULL; + for (l = window->details->target_files; l != NULL; l = l->next) { + ret = g_list_append (ret, caja_file_get_mime_type (CAJA_FILE (l->data))); + } + ret = g_list_reverse (ret); + return ret; +} + +static void +properties_window_update (FMPropertiesWindow *window, + GList *files) +{ + GList *l; + GList *mime_list; + GList *tmp; + CajaFile *changed_file; + gboolean dirty_original = FALSE; + gboolean dirty_target = FALSE; + + if (files == NULL) { + dirty_original = TRUE; + dirty_target = TRUE; + } + + for (tmp = files; tmp != NULL; tmp = tmp->next) { + changed_file = CAJA_FILE (tmp->data); + + if (changed_file && caja_file_is_gone (changed_file)) { + /* Remove the file from the property dialog */ + remove_from_dialog (window, changed_file); + changed_file = NULL; + + if (window->details->original_files == NULL) { + return; + } + } + if (changed_file == NULL || + g_list_find (window->details->original_files, changed_file)) { + dirty_original = TRUE; + } + if (changed_file == NULL || + g_list_find (window->details->target_files, changed_file)) { + dirty_target = TRUE; + } + + } + + if (dirty_original) { + update_properties_window_title (window); + update_properties_window_icon (GTK_IMAGE (window->details->icon_image)); + + update_name_field (window); + + for (l = window->details->emblem_buttons; l != NULL; l = l->next) { + emblem_button_update (window, GTK_TOGGLE_BUTTON (l->data)); + } + + /* If any of the value fields start to depend on the original + * value, value_field_updates should be added here */ + } + + if (dirty_target) { + for (l = window->details->permission_buttons; l != NULL; l = l->next) { + permission_button_update (window, GTK_TOGGLE_BUTTON (l->data)); + } + + for (l = window->details->permission_combos; l != NULL; l = l->next) { + permission_combo_update (window, GTK_COMBO_BOX (l->data)); + } + + for (l = window->details->value_fields; l != NULL; l = l->next) { + value_field_update (window, GTK_LABEL (l->data)); + } + } + + mime_list = get_mime_list (window); + + if (!window->details->mime_list) { + window->details->mime_list = mime_list; + } else { + if (!mime_list_equal (window->details->mime_list, mime_list)) { + refresh_extension_pages (window); + } + + eel_g_list_free_deep (window->details->mime_list); + window->details->mime_list = mime_list; + } +} + +static gboolean +update_files_callback (gpointer data) +{ + FMPropertiesWindow *window; + + window = FM_PROPERTIES_WINDOW (data); + + window->details->update_files_timeout_id = 0; + + properties_window_update (window, window->details->changed_files); + + if (window->details->original_files == NULL) { + /* Close the window if no files are left */ + gtk_widget_destroy (GTK_WIDGET (window)); + } else { + caja_file_list_free (window->details->changed_files); + window->details->changed_files = NULL; + } + + return FALSE; + } + +static void +schedule_files_update (FMPropertiesWindow *window) + { + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + if (window->details->update_files_timeout_id == 0) { + window->details->update_files_timeout_id + = g_timeout_add (FILES_UPDATE_INTERVAL, + update_files_callback, + window); + } + } + +static gboolean +file_list_attributes_identical (GList *file_list, const char *attribute_name) +{ + gboolean identical; + char *first_attr; + GList *l; + + first_attr = NULL; + identical = TRUE; + + for (l = file_list; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + if (caja_file_is_gone (file)) { + continue; + } + + if (first_attr == NULL) { + first_attr = caja_file_get_string_attribute_with_default (file, attribute_name); + } else { + char *attr; + attr = caja_file_get_string_attribute_with_default (file, attribute_name); + if (strcmp (attr, first_attr)) { + identical = FALSE; + g_free (attr); + break; + } + g_free (attr); + } + } + + g_free (first_attr); + return identical; +} + +static char * +file_list_get_string_attribute (GList *file_list, + const char *attribute_name, + const char *inconsistent_value) +{ + if (file_list_attributes_identical (file_list, attribute_name)) { + GList *l; + + for (l = file_list; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + if (!caja_file_is_gone (file)) { + return caja_file_get_string_attribute_with_default + (file, + attribute_name); + } + } + return g_strdup (_("unknown")); + } else { + return g_strdup (inconsistent_value); + } +} + + +static gboolean +file_list_all_directories (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + if (!caja_file_is_directory (CAJA_FILE (l->data))) { + return FALSE; + } + } + return TRUE; +} + +static void +value_field_update_internal (GtkLabel *label, + GList *file_list) +{ + const char *attribute_name; + char *attribute_value; + char *inconsistent_string; + char *mime_type, *tmp; + + g_assert (GTK_IS_LABEL (label)); + + attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute"); + inconsistent_string = g_object_get_data (G_OBJECT (label), "inconsistent_string"); + attribute_value = file_list_get_string_attribute (file_list, + attribute_name, + inconsistent_string); + if (!strcmp (attribute_name, "type") && strcmp (attribute_value, inconsistent_string)) { + mime_type = file_list_get_string_attribute (file_list, + "mime_type", + inconsistent_string); + if (strcmp (mime_type, inconsistent_string)) { + tmp = attribute_value; + attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type); + g_free (tmp); + } + g_free (mime_type); + } + + gtk_label_set_text (label, attribute_value); + g_free (attribute_value); +} + +static void +value_field_update (FMPropertiesWindow *window, GtkLabel *label) +{ + gboolean use_original; + + use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "show_original")); + + value_field_update_internal (label, + (use_original ? + window->details->original_files : + window->details->target_files)); +} + +static GtkLabel * +attach_label (GtkTable *table, + int row, + int column, + const char *initial_text, + gboolean right_aligned, + gboolean bold, + gboolean ellipsize_text, + gboolean selectable, + gboolean mnemonic) +{ + GtkWidget *label_field; + + if (ellipsize_text) { + label_field = gtk_label_new (initial_text); + gtk_label_set_ellipsize (GTK_LABEL (label_field), + right_aligned ? PANGO_ELLIPSIZE_START : + PANGO_ELLIPSIZE_END); + } else if (mnemonic) { + label_field = gtk_label_new_with_mnemonic (initial_text); + } else { + label_field = gtk_label_new (initial_text); + } + + if (selectable) { + gtk_label_set_selectable (GTK_LABEL (label_field), TRUE); + } + + if (bold) { + eel_gtk_label_make_bold (GTK_LABEL (label_field)); + } + gtk_misc_set_alignment (GTK_MISC (label_field), right_aligned ? 1 : 0, 0.5); + gtk_widget_show (label_field); + gtk_table_attach (table, label_field, + column, column + 1, + row, row + 1, + ellipsize_text + ? GTK_FILL | GTK_EXPAND + : GTK_FILL, + 0, + 0, 0); + + return GTK_LABEL (label_field); +} + +static GtkLabel * +attach_value_label (GtkTable *table, + int row, + int column, + const char *initial_text) +{ + return attach_label (table, row, column, initial_text, FALSE, FALSE, FALSE, TRUE, FALSE); +} + +static GtkLabel * +attach_ellipsizing_value_label (GtkTable *table, + int row, + int column, + const char *initial_text) +{ + return attach_label (table, row, column, initial_text, FALSE, FALSE, TRUE, TRUE, FALSE); +} + +static GtkWidget* +attach_value_field_internal (FMPropertiesWindow *window, + GtkTable *table, + int row, + int column, + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original, + gboolean ellipsize_text) +{ + GtkLabel *value_field; + + if (ellipsize_text) { + value_field = attach_ellipsizing_value_label (table, row, column, ""); + } else { + value_field = attach_value_label (table, row, column, ""); + } + + /* Stash a copy of the file attribute name in this field for the callback's sake. */ + g_object_set_data_full (G_OBJECT (value_field), "file_attribute", + g_strdup (file_attribute_name), g_free); + + g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string", + g_strdup (inconsistent_string), g_free); + + g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original)); + + window->details->value_fields = g_list_prepend (window->details->value_fields, + value_field); + return GTK_WIDGET(value_field); +} + +static GtkWidget* +attach_value_field (FMPropertiesWindow *window, + GtkTable *table, + int row, + int column, + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original) +{ + return attach_value_field_internal (window, + table, row, column, + file_attribute_name, + inconsistent_string, + show_original, + FALSE); +} + +static GtkWidget* +attach_ellipsizing_value_field (FMPropertiesWindow *window, + GtkTable *table, + int row, + int column, + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original) +{ + return attach_value_field_internal (window, + table, row, column, + file_attribute_name, + inconsistent_string, + show_original, + TRUE); +} + +static void +group_change_callback (CajaFile *file, + GFile *res_loc, + GError *error, + FMPropertiesWindow *window) +{ + char *group; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->group_change_file == file); + + group = window->details->group_change_group; + g_assert (group != NULL); + + /* Report the error if it's an error. */ + eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); + fm_report_error_setting_group (file, error, GTK_WINDOW (window)); + + caja_file_unref (file); + g_free (group); + + window->details->group_change_file = NULL; + window->details->group_change_group = NULL; + g_object_unref (G_OBJECT (window)); +} + +static void +cancel_group_change_callback (FMPropertiesWindow *window) +{ + CajaFile *file; + char *group; + + file = window->details->group_change_file; + g_assert (CAJA_IS_FILE (file)); + + group = window->details->group_change_group; + g_assert (group != NULL); + + caja_file_cancel (file, (CajaFileOperationCallback) group_change_callback, window); + + g_free (group); + caja_file_unref (file); + + window->details->group_change_file = NULL; + window->details->group_change_group = NULL; + g_object_unref (window); +} + +static gboolean +schedule_group_change_timeout (FMPropertiesWindow *window) +{ + CajaFile *file; + char *group; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + file = window->details->group_change_file; + g_assert (CAJA_IS_FILE (file)); + + group = window->details->group_change_group; + g_assert (group != NULL); + + eel_timed_wait_start + ((EelCancelCallback) cancel_group_change_callback, + window, + _("Cancel Group Change?"), + GTK_WINDOW (window)); + + caja_file_set_group + (file, group, + (CajaFileOperationCallback) group_change_callback, window); + + window->details->group_change_timeout = 0; + return FALSE; +} + +static void +schedule_group_change (FMPropertiesWindow *window, + CajaFile *file, + const char *group) +{ + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->group_change_group == NULL); + g_assert (window->details->group_change_file == NULL); + g_assert (CAJA_IS_FILE (file)); + + window->details->group_change_file = caja_file_ref (file); + window->details->group_change_group = g_strdup (group); + g_object_ref (G_OBJECT (window)); + window->details->group_change_timeout = + g_timeout_add (CHOWN_CHGRP_TIMEOUT, + (GSourceFunc) schedule_group_change_timeout, + window); +} + +static void +unschedule_or_cancel_group_change (FMPropertiesWindow *window) +{ + CajaFile *file; + char *group; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + file = window->details->group_change_file; + group = window->details->group_change_group; + + g_assert ((file == NULL && group == NULL) || + (file != NULL && group != NULL)); + + if (file != NULL) { + g_assert (CAJA_IS_FILE (file)); + + if (window->details->group_change_timeout == 0) { + caja_file_cancel (file, + (CajaFileOperationCallback) group_change_callback, window); + eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); + } + + caja_file_unref (file); + g_free (group); + + window->details->group_change_file = NULL; + window->details->group_change_group = NULL; + g_object_unref (G_OBJECT (window)); + } + + if (window->details->group_change_timeout > 0) { + g_assert (file != NULL); + g_source_remove (window->details->group_change_timeout); + window->details->group_change_timeout = 0; + } +} + +static void +changed_group_callback (GtkComboBox *combo_box, CajaFile *file) +{ + FMPropertiesWindow *window; + char *group; + char *cur_group; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (CAJA_IS_FILE (file)); + + group = gtk_combo_box_get_active_text (combo_box); + cur_group = caja_file_get_group_name (file); + + if (group != NULL && strcmp (group, cur_group) != 0) { + /* Try to change file group. If this fails, complain to user. */ + window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); + + unschedule_or_cancel_group_change (window); + schedule_group_change (window, file, group); + } + g_free (group); + g_free (cur_group); +} + +/* checks whether the given column at the first level + * of model has the specified entries in the given order. */ +static gboolean +tree_model_entries_equal (GtkTreeModel *model, + unsigned int column, + GList *entries) +{ + GtkTreeIter iter; + gboolean empty_model; + + g_assert (GTK_IS_TREE_MODEL (model)); + g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); + + empty_model = !gtk_tree_model_get_iter_first (model, &iter); + + if (!empty_model && entries != NULL) { + GList *l; + + l = entries; + + do { + char *val; + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + if ((val == NULL && l->data != NULL) || + (val != NULL && l->data == NULL) || + (val != NULL && strcmp (val, l->data))) { + g_free (val); + return FALSE; + } + + g_free (val); + l = l->next; + } while (gtk_tree_model_iter_next (model, &iter)); + + return l == NULL; + } else { + return (empty_model && entries == NULL) || + (!empty_model && entries != NULL); + } +} + +static char * +combo_box_get_active_entry (GtkComboBox *combo_box, + unsigned int column) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *val; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) { + model = gtk_combo_box_get_model (combo_box); + g_assert (GTK_IS_TREE_MODEL (model)); + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + return val; + } + + return NULL; +} + +/* returns the index of the given entry in the the given column + * at the first level of model. Returns -1 if entry can't be found + * or entry is NULL. + * */ +static int +tree_model_get_entry_index (GtkTreeModel *model, + unsigned int column, + const char *entry) +{ + GtkTreeIter iter; + int index; + gboolean empty_model; + + g_assert (GTK_IS_TREE_MODEL (model)); + g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); + + empty_model = !gtk_tree_model_get_iter_first (model, &iter); + if (!empty_model && entry != NULL) { + index = 0; + + do { + char *val; + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + if (val != NULL && !strcmp (val, entry)) { + g_free (val); + return index; + } + + g_free (val); + index++; + } while (gtk_tree_model_iter_next (model, &iter)); + } + + return -1; +} + + +static void +synch_groups_combo_box (GtkComboBox *combo_box, CajaFile *file) +{ + GList *groups; + GList *node; + GtkTreeModel *model; + GtkListStore *store; + const char *group_name; + char *current_group_name; + int group_index; + int current_group_index; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_gone (file)) { + return; + } + + groups = caja_file_get_settable_group_names (file); + + model = gtk_combo_box_get_model (combo_box); + store = GTK_LIST_STORE (model); + g_assert (GTK_IS_LIST_STORE (model)); + + if (!tree_model_entries_equal (model, 0, groups)) { + /* Clear the contents of ComboBox in a wacky way because there + * is no function to clear all items and also no function to obtain + * the number of items in a combobox. + */ + gtk_list_store_clear (store); + + for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index) { + group_name = (const char *)node->data; + gtk_combo_box_append_text (combo_box, group_name); + } + } + + current_group_name = caja_file_get_group_name (file); + current_group_index = tree_model_get_entry_index (model, 0, current_group_name); + + /* If current group wasn't in list, we prepend it (with a separator). + * This can happen if the current group is an id with no matching + * group in the groups file. + */ + if (current_group_index < 0 && current_group_name != NULL) { + if (groups != NULL) { + /* add separator */ + gtk_combo_box_prepend_text (combo_box, "-"); + } + + gtk_combo_box_prepend_text (combo_box, current_group_name); + current_group_index = 0; + } + gtk_combo_box_set_active (combo_box, current_group_index); + + g_free (current_group_name); + eel_g_list_free_deep (groups); +} + +static gboolean +combo_box_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *text; + gboolean ret; + + gtk_tree_model_get (model, iter, 0, &text, -1); + + if (text == NULL) { + return FALSE; + } + + if (strcmp (text, "-") == 0) { + ret = TRUE; + } else { + ret = FALSE; + } + + g_free (text); + return ret; +} + +static GtkComboBox * +attach_combo_box (GtkTable *table, + int row, + gboolean two_columns) +{ + GtkWidget *combo_box; + GtkWidget *aligner; + + if (!two_columns) { + combo_box = gtk_combo_box_new_text (); + } else { + GtkTreeModel *model; + GtkCellRenderer *renderer; + + model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)); + combo_box = gtk_combo_box_new_with_model (model); + g_object_unref (G_OBJECT (model)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box), renderer, + "text", 0); + + } + gtk_widget_show (combo_box); + + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), + combo_box_row_separator_func, + NULL, + NULL); + + /* Put combo box in alignment to make it left-justified + * but minimally sized. + */ + aligner = gtk_alignment_new (0, 0.5, 0, 0); + gtk_widget_show (aligner); + + gtk_container_add (GTK_CONTAINER (aligner), combo_box); + gtk_table_attach (table, aligner, + VALUE_COLUMN, VALUE_COLUMN + 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); + + return GTK_COMBO_BOX (combo_box); +} + +static GtkComboBox* +attach_group_combo_box (GtkTable *table, + int row, + CajaFile *file) +{ + GtkComboBox *combo_box; + + combo_box = attach_combo_box (table, row, FALSE); + + synch_groups_combo_box (combo_box, file); + + /* Connect to signal to update menu when file changes. */ + g_signal_connect_object (file, "changed", + G_CALLBACK (synch_groups_combo_box), + combo_box, G_CONNECT_SWAPPED); + g_signal_connect_data (combo_box, "changed", + G_CALLBACK (changed_group_callback), + caja_file_ref (file), + (GClosureNotify)caja_file_unref, 0); + + return combo_box; +} + +static void +owner_change_callback (CajaFile *file, + GFile *result_location, + GError *error, + FMPropertiesWindow *window) +{ + char *owner; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->owner_change_file == file); + + owner = window->details->owner_change_owner; + g_assert (owner != NULL); + + /* Report the error if it's an error. */ + eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); + fm_report_error_setting_owner (file, error, GTK_WINDOW (window)); + + caja_file_unref (file); + g_free (owner); + + window->details->owner_change_file = NULL; + window->details->owner_change_owner = NULL; + g_object_unref (G_OBJECT (window)); +} + +static void +cancel_owner_change_callback (FMPropertiesWindow *window) +{ + CajaFile *file; + char *owner; + + file = window->details->owner_change_file; + g_assert (CAJA_IS_FILE (file)); + + owner = window->details->owner_change_owner; + g_assert (owner != NULL); + + caja_file_cancel (file, (CajaFileOperationCallback) owner_change_callback, window); + + caja_file_unref (file); + g_free (owner); + + window->details->owner_change_file = NULL; + window->details->owner_change_owner = NULL; + g_object_unref (window); +} + +static gboolean +schedule_owner_change_timeout (FMPropertiesWindow *window) +{ + CajaFile *file; + char *owner; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + file = window->details->owner_change_file; + g_assert (CAJA_IS_FILE (file)); + + owner = window->details->owner_change_owner; + g_assert (owner != NULL); + + eel_timed_wait_start + ((EelCancelCallback) cancel_owner_change_callback, + window, + _("Cancel Owner Change?"), + GTK_WINDOW (window)); + + caja_file_set_owner + (file, owner, + (CajaFileOperationCallback) owner_change_callback, window); + + window->details->owner_change_timeout = 0; + return FALSE; +} + +static void +schedule_owner_change (FMPropertiesWindow *window, + CajaFile *file, + const char *owner) +{ + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->owner_change_owner == NULL); + g_assert (window->details->owner_change_file == NULL); + g_assert (CAJA_IS_FILE (file)); + + window->details->owner_change_file = caja_file_ref (file); + window->details->owner_change_owner = g_strdup (owner); + g_object_ref (G_OBJECT (window)); + window->details->owner_change_timeout = + g_timeout_add (CHOWN_CHGRP_TIMEOUT, + (GSourceFunc) schedule_owner_change_timeout, + window); +} + +static void +unschedule_or_cancel_owner_change (FMPropertiesWindow *window) +{ + CajaFile *file; + char *owner; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + file = window->details->owner_change_file; + owner = window->details->owner_change_owner; + + g_assert ((file == NULL && owner == NULL) || + (file != NULL && owner != NULL)); + + if (file != NULL) { + g_assert (CAJA_IS_FILE (file)); + + if (window->details->owner_change_timeout == 0) { + caja_file_cancel (file, + (CajaFileOperationCallback) owner_change_callback, window); + eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); + } + + caja_file_unref (file); + g_free (owner); + + window->details->owner_change_file = NULL; + window->details->owner_change_owner = NULL; + g_object_unref (G_OBJECT (window)); + } + + if (window->details->owner_change_timeout > 0) { + g_assert (file != NULL); + g_source_remove (window->details->owner_change_timeout); + window->details->owner_change_timeout = 0; + } +} + +static void +changed_owner_callback (GtkComboBox *combo_box, CajaFile* file) +{ + FMPropertiesWindow *window; + char *owner_text; + char **name_array; + char *new_owner; + char *cur_owner; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (CAJA_IS_FILE (file)); + + owner_text = combo_box_get_active_entry (combo_box, 0); + if (! owner_text) + return; + name_array = g_strsplit (owner_text, " - ", 2); + new_owner = name_array[0]; + g_free (owner_text); + cur_owner = caja_file_get_owner_name (file); + + if (strcmp (new_owner, cur_owner) != 0) { + /* Try to change file owner. If this fails, complain to user. */ + window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); + + unschedule_or_cancel_owner_change (window); + schedule_owner_change (window, file, new_owner); + } + g_strfreev (name_array); + g_free (cur_owner); +} + +static void +synch_user_menu (GtkComboBox *combo_box, CajaFile *file) +{ + GList *users; + GList *node; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + char *user_name; + char *owner_name; + int user_index; + int owner_index; + char **name_array; + char *combo_text; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_gone (file)) { + return; + } + + users = caja_get_user_names (); + + model = gtk_combo_box_get_model (combo_box); + store = GTK_LIST_STORE (model); + g_assert (GTK_IS_LIST_STORE (model)); + + if (!tree_model_entries_equal (model, 1, users)) { + /* Clear the contents of ComboBox in a wacky way because there + * is no function to clear all items and also no function to obtain + * the number of items in a combobox. + */ + gtk_list_store_clear (store); + + for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index) { + user_name = (char *)node->data; + + name_array = g_strsplit (user_name, "\n", 2); + if (name_array[1] != NULL) { + combo_text = g_strdup_printf ("%s - %s", name_array[0], name_array[1]); + } else { + combo_text = g_strdup (name_array[0]); + } + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, combo_text, + 1, user_name, + -1); + + g_strfreev (name_array); + g_free (combo_text); + } + } + + owner_name = caja_file_get_string_attribute (file, "owner"); + owner_index = tree_model_get_entry_index (model, 0, owner_name); + + /* If owner wasn't in list, we prepend it (with a separator). + * This can happen if the owner is an id with no matching + * identifier in the passwords file. + */ + if (owner_index < 0 && owner_name != NULL) { + if (users != NULL) { + /* add separator */ + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + 0, "-", + 1, NULL, + -1); + } + + name_array = g_strsplit (owner_name, " - ", 2); + if (name_array[1] != NULL) { + user_name = g_strdup_printf ("%s\n%s", name_array[0], name_array[1]); + } else { + user_name = g_strdup (name_array[0]); + } + owner_index = 0; + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + 0, owner_name, + 1, user_name, + -1); + + g_free (user_name); + g_strfreev (name_array); + } + + gtk_combo_box_set_active (combo_box, owner_index); + + g_free (owner_name); + eel_g_list_free_deep (users); +} + +static GtkComboBox* +attach_owner_combo_box (GtkTable *table, + int row, + CajaFile *file) +{ + GtkComboBox *combo_box; + + combo_box = attach_combo_box (table, row, TRUE); + + synch_user_menu (combo_box, file); + + /* Connect to signal to update menu when file changes. */ + g_signal_connect_object (file, "changed", + G_CALLBACK (synch_user_menu), + combo_box, G_CONNECT_SWAPPED); + g_signal_connect_data (combo_box, "changed", + G_CALLBACK (changed_owner_callback), + caja_file_ref (file), + (GClosureNotify)caja_file_unref, 0); + + return combo_box; +} + +static guint +append_row (GtkTable *table) +{ + guint new_row_count; + gint nrows, ncols; + + g_object_get (table, "n-rows", &nrows, "n-columns", &ncols, NULL); + + new_row_count = nrows + 1; + + gtk_table_resize (table, new_row_count, ncols); + gtk_table_set_row_spacing (table, new_row_count - 1, ROW_PAD); + + return new_row_count - 1; +} + +static gboolean +file_has_prefix (CajaFile *file, + GList *prefix_candidates) +{ + GList *p; + GFile *location, *candidate_location; + + location = caja_file_get_location (file); + + for (p = prefix_candidates; p != NULL; p = p->next) { + if (file == p->data) { + continue; + } + + candidate_location = caja_file_get_location (CAJA_FILE (p->data)); + if (g_file_has_prefix (location, candidate_location)) { + g_object_unref (location); + g_object_unref (candidate_location); + return TRUE; + } + g_object_unref (candidate_location); + } + + g_object_unref (location); + + return FALSE; +} + +static void +directory_contents_value_field_update (FMPropertiesWindow *window) +{ + CajaRequestStatus file_status, status; + char *text, *temp; + guint directory_count; + guint file_count; + guint total_count; + guint unreadable_directory_count; + goffset total_size; + gboolean used_two_lines; + CajaFile *file; + GList *l; + guint file_unreadable; + goffset file_size; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + status = CAJA_REQUEST_DONE; + file_status = CAJA_REQUEST_NOT_STARTED; + total_count = window->details->total_count; + total_size = window->details->total_size; + unreadable_directory_count = FALSE; + + for (l = window->details->target_files; l; l = l->next) { + file = CAJA_FILE (l->data); + + if (file_has_prefix (file, window->details->target_files)) { + /* don't count nested files twice */ + continue; + } + + if (caja_file_is_directory (file)) { + file_status = caja_file_get_deep_counts (file, + &directory_count, + &file_count, + &file_unreadable, + &file_size, + TRUE); + total_count += (file_count + directory_count); + total_size += file_size; + + if (file_unreadable) { + unreadable_directory_count = TRUE; + } + + if (file_status != CAJA_REQUEST_DONE) { + status = file_status; + } + } else { + ++total_count; + total_size += caja_file_get_size (file); + } + } + + /* If we've already displayed the total once, don't do another visible + * count-up if the deep_count happens to get invalidated. + * But still display the new total, since it might have changed. + */ + if (window->details->deep_count_finished && + status != CAJA_REQUEST_DONE) { + return; + } + + text = NULL; + used_two_lines = FALSE; + + if (total_count == 0) { + switch (status) { + case CAJA_REQUEST_DONE: + if (unreadable_directory_count == 0) { + text = g_strdup (_("nothing")); + } else { + text = g_strdup (_("unreadable")); + } + + break; + default: + text = g_strdup ("..."); + } + } else { + char *size_str; + + #if GLIB_CHECK_VERSION(2, 30, 0) + size_str = g_format_size(total_size); + #else + size_str = g_format_size_for_display(total_size); + #endif + + text = g_strdup_printf (ngettext("%'d item, with size %s", + "%'d items, totalling %s", + total_count), + total_count, size_str); + g_free (size_str); + + if (unreadable_directory_count != 0) { + temp = text; + text = g_strconcat (temp, "\n", + _("(some contents unreadable)"), + NULL); + g_free (temp); + used_two_lines = TRUE; + } + } + + gtk_label_set_text (window->details->directory_contents_value_field, + text); + g_free (text); + + /* Also set the title field here, with a trailing carriage return & + * space if the value field has two lines. This is a hack to get the + * "Contents:" title to line up with the first line of the + * 2-line value. Maybe there's a better way to do this, but I + * couldn't think of one. + */ + text = g_strdup (_("Contents:")); + if (used_two_lines) { + temp = text; + text = g_strconcat (temp, "\n ", NULL); + g_free (temp); + } + gtk_label_set_text (window->details->directory_contents_title_field, + text); + g_free (text); + + if (status == CAJA_REQUEST_DONE) { + window->details->deep_count_finished = TRUE; + } +} + +static gboolean +update_directory_contents_callback (gpointer data) +{ + FMPropertiesWindow *window; + + window = FM_PROPERTIES_WINDOW (data); + + window->details->update_directory_contents_timeout_id = 0; + directory_contents_value_field_update (window); + + return FALSE; +} + +static void +schedule_directory_contents_update (FMPropertiesWindow *window) +{ + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + if (window->details->update_directory_contents_timeout_id == 0) { + window->details->update_directory_contents_timeout_id + = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL, + update_directory_contents_callback, + window); + } +} + +static GtkLabel * +attach_directory_contents_value_field (FMPropertiesWindow *window, + GtkTable *table, + int row) +{ + GtkLabel *value_field; + GList *l; + CajaFile *file; + + value_field = attach_value_label (table, row, VALUE_COLUMN, ""); + + g_assert (window->details->directory_contents_value_field == NULL); + window->details->directory_contents_value_field = value_field; + + gtk_label_set_line_wrap (value_field, TRUE); + + /* Fill in the initial value. */ + directory_contents_value_field_update (window); + + for (l = window->details->target_files; l; l = l->next) { + file = CAJA_FILE (l->data); + caja_file_recompute_deep_counts (file); + + g_signal_connect_object (file, + "updated_deep_count_in_progress", + G_CALLBACK (schedule_directory_contents_update), + window, G_CONNECT_SWAPPED); + } + + return value_field; +} + +static GtkLabel * +attach_title_field (GtkTable *table, + int row, + const char *title) +{ + return attach_label (table, row, TITLE_COLUMN, title, FALSE, FALSE, FALSE, FALSE, TRUE); +} + +static guint +append_title_field (GtkTable *table, const char *title, GtkLabel **label) +{ + guint last_row; + GtkLabel *title_label; + + last_row = append_row (table); + title_label = attach_title_field (table, last_row, title); + + if (label) { + *label = title_label; + } + + return last_row; +} + +#define INCONSISTENT_STATE_STRING \ + "\xE2\x80\x92" + +static guint +append_title_value_pair (FMPropertiesWindow *window, + GtkTable *table, + const char *title, + const char *file_attribute_name, + const char *inconsistent_state, + gboolean show_original) +{ + guint last_row; + GtkLabel *title_label; + GtkWidget *value; + + last_row = append_title_field (table, title, &title_label); + value = attach_value_field (window, table, last_row, VALUE_COLUMN, + file_attribute_name, + inconsistent_state, + show_original); + gtk_label_set_mnemonic_widget (title_label, value); + return last_row; +} + +static guint +append_title_and_ellipsizing_value (FMPropertiesWindow *window, + GtkTable *table, + const char *title, + const char *file_attribute_name, + const char *inconsistent_state, + gboolean show_original) +{ + GtkLabel *title_label; + GtkWidget *value; + guint last_row; + + last_row = append_title_field (table, title, &title_label); + value = attach_ellipsizing_value_field (window, table, last_row, VALUE_COLUMN, + file_attribute_name, + inconsistent_state, + show_original); + gtk_label_set_mnemonic_widget (title_label, value); + + return last_row; +} + +static guint +append_directory_contents_fields (FMPropertiesWindow *window, + GtkTable *table) +{ + GtkLabel *title_field, *value_field; + guint last_row; + + last_row = append_row (table); + + title_field = attach_title_field (table, last_row, ""); + window->details->directory_contents_title_field = title_field; + gtk_label_set_line_wrap (title_field, TRUE); + + value_field = attach_directory_contents_value_field + (window, table, last_row); + + gtk_label_set_mnemonic_widget(title_field, GTK_WIDGET(value_field)); + return last_row; +} + +static GtkWidget * +create_page_with_hbox (GtkNotebook *notebook, + const char *title) +{ + GtkWidget *hbox; + + g_assert (GTK_IS_NOTEBOOK (notebook)); + g_assert (title != NULL); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_set_spacing (GTK_BOX (hbox), 12); + gtk_notebook_append_page (notebook, hbox, gtk_label_new (title)); + + return hbox; +} + +static GtkWidget * +create_page_with_vbox (GtkNotebook *notebook, + const char *title) +{ + GtkWidget *vbox; + + g_assert (GTK_IS_NOTEBOOK (notebook)); + g_assert (title != NULL); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_notebook_append_page (notebook, vbox, gtk_label_new (title)); + + return vbox; +} + +static GtkWidget * +append_blank_row (GtkTable *table) +{ + GtkWidget *separator; + + append_title_field (table, "", (GtkLabel **) &separator); + + return separator; +} + +static void +apply_standard_table_padding (GtkTable *table) +{ + gtk_table_set_row_spacings (table, ROW_PAD); + gtk_table_set_col_spacings (table, 12); +} + +static GtkWidget * +create_attribute_value_table (GtkVBox *vbox, int row_count) +{ + GtkWidget *table; + + table = gtk_table_new (row_count, COLUMN_COUNT, FALSE); + apply_standard_table_padding (GTK_TABLE (table)); + gtk_widget_show (table); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + + return table; +} + +static gboolean +is_merged_trash_directory (CajaFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = caja_file_get_uri (file); + result = strcmp (file_uri, "trash:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +is_computer_directory (CajaFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = caja_file_get_uri (file); + result = strcmp (file_uri, "computer:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +is_network_directory (CajaFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = caja_file_get_uri (file); + result = strcmp (file_uri, "network:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +is_burn_directory (CajaFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = caja_file_get_uri (file); + result = strcmp (file_uri, "burn:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +should_show_custom_icon_buttons (FMPropertiesWindow *window) +{ + if (is_multi_file_window (window)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_file_type (FMPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && (is_merged_trash_directory (get_target_file (window)) || + is_computer_directory (get_target_file (window)) || + is_network_directory (get_target_file (window)) || + is_burn_directory (get_target_file (window)))) { + return FALSE; + } + + + return TRUE; +} + +static gboolean +should_show_location_info (FMPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && (is_merged_trash_directory (get_target_file (window)) || + is_computer_directory (get_target_file (window)) || + is_network_directory (get_target_file (window)) || + is_burn_directory (get_target_file (window)))) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_accessed_date (FMPropertiesWindow *window) +{ + /* Accessed date for directory seems useless. If we some + * day decide that it is useful, we should separately + * consider whether it's useful for "trash:". + */ + if (file_list_all_directories (window->details->target_files)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_link_target (FMPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && caja_file_is_symbolic_link (get_target_file (window))) { + return TRUE; + } + + return FALSE; +} + +static gboolean +should_show_free_space (FMPropertiesWindow *window) +{ + + if (!is_multi_file_window (window) + && (is_merged_trash_directory (get_target_file (window)) || + is_computer_directory (get_target_file (window)) || + is_network_directory (get_target_file (window)) || + is_burn_directory (get_target_file (window)))) { + return FALSE; + } + + if (file_list_all_directories (window->details->target_files)) { + return TRUE; + } + + return FALSE; +} + +static gboolean +should_show_volume_usage (FMPropertiesWindow *window) +{ + CajaFile *file; + gboolean success = FALSE; + + if (is_multi_file_window (window)) { + return FALSE; + } + + file = get_original_file (window); + + if (file == NULL) { + return FALSE; + } + + if (caja_file_can_unmount (file)) { + return TRUE; + } + +#ifdef TODO_GIO + /* Look at is_mountpoint for activation uri */ +#endif + return success; +} + +static void +paint_used_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ + FMPropertiesWindow *window; + cairo_t *cr; + gint width, height; + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + + window = FM_PROPERTIES_WINDOW (data); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + cairo_rectangle (cr, + 2, + 2, + width - 4, + height - 4); + + cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535); + cairo_fill_preserve (cr); + + cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535); + cairo_stroke (cr); + + cairo_destroy (cr); +} + +static void +paint_free_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ + FMPropertiesWindow *window; + cairo_t *cr; + gint width, height; + GtkAllocation allocation; + + window = FM_PROPERTIES_WINDOW (data); + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + cairo_rectangle (cr, + 2, + 2, + width - 4, + height - 4); + + cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535, (double) window->details->free_color.blue / 65535); + cairo_fill_preserve(cr); + + cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535); + cairo_stroke (cr); + + cairo_destroy (cr); +} + +static void +paint_pie_chart (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ + + FMPropertiesWindow *window; + cairo_t *cr; + gint width, height; + double free, used; + double angle1, angle2, split, xc, yc, radius; + GtkAllocation allocation; + + window = FM_PROPERTIES_WINDOW (data); + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + + + free = (double)window->details->volume_free / (double)window->details->volume_capacity; + used = 1.0 - free; + + angle1 = free * 2 * G_PI; + angle2 = used * 2 * G_PI; + split = (2 * G_PI - angle1) * .5; + xc = width / 2; + yc = height / 2; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + if (width < height) { + radius = width / 2 - 8; + } else { + radius = height / 2 - 8; + } + + if (angle1 != 2 * G_PI && angle1 != 0) { + angle1 = angle1 + split; + } + + if (angle2 != 2 * G_PI && angle2 != 0) { + angle2 = angle2 - split; + } + + if (used > 0) { + if (free != 0) { + cairo_move_to (cr,xc,yc); + } + + cairo_arc (cr, xc, yc, radius, angle1, angle2); + + if (free != 0) { + cairo_line_to (cr,xc,yc); + } + + cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535); + cairo_fill_preserve (cr); + + cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535); + cairo_stroke (cr); + } + + if (free > 0) { + if (used != 0) { + cairo_move_to (cr,xc,yc); + } + + cairo_arc_negative (cr, xc, yc, radius, angle1, angle2); + + if (used != 0) { + cairo_line_to (cr,xc,yc); + } + + cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535,(double) window->details->free_color.blue / 65535); + cairo_fill_preserve(cr); + + cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535); + cairo_stroke (cr); + } + + cairo_destroy (cr); +} + + +/* Copied from gtk/gtkstyle.c */ + +static void +rgb_to_hls (gdouble *r, + gdouble *g, + gdouble *b) +{ + gdouble min; + gdouble max; + gdouble red; + gdouble green; + gdouble blue; + gdouble h, l, s; + gdouble delta; + + red = *r; + green = *g; + blue = *b; + + if (red > green) + { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } + else + { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + + l = (max + min) / 2; + s = 0; + h = 0; + + if (max != min) + { + if (l <= 0.5) + s = (max - min) / (max + min); + else + s = (max - min) / (2 - max - min); + + delta = max -min; + if (red == max) + h = (green - blue) / delta; + else if (green == max) + h = 2 + (blue - red) / delta; + else if (blue == max) + h = 4 + (red - green) / delta; + + h *= 60; + if (h < 0.0) + h += 360; + } + + *r = h; + *g = l; + *b = s; +} + +static void +hls_to_rgb (gdouble *h, + gdouble *l, + gdouble *s) +{ + gdouble hue; + gdouble lightness; + gdouble saturation; + gdouble m1, m2; + gdouble r, g, b; + + lightness = *l; + saturation = *s; + + if (lightness <= 0.5) + m2 = lightness * (1 + saturation); + else + m2 = lightness + saturation - lightness * saturation; + m1 = 2 * lightness - m2; + + if (saturation == 0) + { + *h = lightness; + *l = lightness; + *s = lightness; + } + else + { + hue = *h + 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + r = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + r = m2; + else if (hue < 240) + r = m1 + (m2 - m1) * (240 - hue) / 60; + else + r = m1; + + hue = *h; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + g = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + g = m2; + else if (hue < 240) + g = m1 + (m2 - m1) * (240 - hue) / 60; + else + g = m1; + + hue = *h - 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + b = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + b = m2; + else if (hue < 240) + b = m1 + (m2 - m1) * (240 - hue) / 60; + else + b = m1; + + *h = r; + *l = g; + *s = b; + } +} +static void +_pie_style_shade (GdkColor *a, + GdkColor *b, + gdouble k) +{ + gdouble red; + gdouble green; + gdouble blue; + + red = (gdouble) a->red / 65535.0; + green = (gdouble) a->green / 65535.0; + blue = (gdouble) a->blue / 65535.0; + + rgb_to_hls (&red, &green, &blue); + + green *= k; + if (green > 1.0) + green = 1.0; + else if (green < 0.0) + green = 0.0; + + blue *= k; + if (blue > 1.0) + blue = 1.0; + else if (blue < 0.0) + blue = 0.0; + + hls_to_rgb (&red, &green, &blue); + + b->red = red * 65535.0; + b->green = green * 65535.0; + b->blue = blue * 65535.0; +} + + +static GtkWidget* +create_pie_widget (FMPropertiesWindow *window) +{ + CajaFile *file; + GtkTable *table; + GtkStyle *style; + GtkWidget *pie_canvas; + GtkWidget *used_canvas; + GtkWidget *used_label; + GtkWidget *free_canvas; + GtkWidget *free_label; + GtkWidget *capacity_label; + GtkWidget *fstype_label; + gchar *capacity; + gchar *used; + gchar *free; + const char *fs_type; + gchar *uri; + GFile *location; + GFileInfo *info; + + #if GLIB_CHECK_VERSION(2, 30, 0) + capacity = g_format_size(window->details->volume_capacity); + free = g_format_size(window->details->volume_free); + used = g_format_size(window->details->volume_capacity - window->details->volume_free); + #else + capacity = g_format_size_for_display(window->details->volume_capacity); + free = g_format_size_for_display(window->details->volume_free); + used = g_format_size_for_display(window->details->volume_capacity - window->details->volume_free); + #endif + + file = get_original_file (window); + + uri = caja_file_get_activation_uri (file); + + table = GTK_TABLE (gtk_table_new (4, 3, FALSE)); + + style = gtk_rc_get_style (GTK_WIDGET(table)); + + if (!gtk_style_lookup_color (style, "chart_color_1", &window->details->used_color)) { + window->details->used_color.red = USED_FILL_R; + window->details->used_color.green = USED_FILL_G; + window->details->used_color.blue = USED_FILL_B; + } + + if (!gtk_style_lookup_color (style, "chart_color_2", &window->details->free_color)) { + window->details->free_color.red = FREE_FILL_R; + window->details->free_color.green = FREE_FILL_G; + window->details->free_color.blue = FREE_FILL_B; + } + + _pie_style_shade (&window->details->used_color, &window->details->used_stroke_color, 0.7); + _pie_style_shade (&window->details->free_color, &window->details->free_stroke_color, 0.7); + + pie_canvas = gtk_drawing_area_new (); + gtk_widget_set_size_request (pie_canvas, 200, 200); + + used_canvas = gtk_drawing_area_new (); + gtk_widget_set_size_request (used_canvas, 20, 20); + /* Translators: "used" refers to the capacity of the filesystem */ + used_label = gtk_label_new (g_strconcat (used, " ", _("used"), NULL)); + + free_canvas = gtk_drawing_area_new (); + gtk_widget_set_size_request (free_canvas,20,20); + /* Translators: "free" refers to the capacity of the filesystem */ + free_label = gtk_label_new (g_strconcat (free, " ", _("free"), NULL)); + + capacity_label = gtk_label_new (g_strconcat (_("Total capacity:"), " ", capacity, NULL)); + fstype_label = gtk_label_new (NULL); + + location = g_file_new_for_uri (uri); + info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, + NULL, NULL); + if (info) { + fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); + if (fs_type != NULL) { + gtk_label_set_text (GTK_LABEL (fstype_label), g_strconcat (_("Filesystem type:"), " ", fs_type, NULL)); + } + + g_object_unref (info); + } + g_object_unref (location); + + g_free (uri); + g_free (capacity); + g_free (used); + g_free (free); + + gtk_table_attach (table, pie_canvas , 0, 1, 0, 4, GTK_FILL, GTK_SHRINK, 5, 5); + + gtk_table_attach (table, used_canvas, 1, 2, 0, 1, 0, 0, 5, 5); + gtk_table_attach (table, used_label , 2, 3, 0, 1, GTK_FILL, 0, 5, 5); + + gtk_table_attach (table, free_canvas, 1, 2, 1, 2, 0, 0, 5, 5); + gtk_table_attach (table, free_label , 2, 3, 1, 2, GTK_FILL, 0, 5, 5); + + gtk_table_attach (table, capacity_label , 1, 3, 2, 3, GTK_FILL, 0, 5, 5); + gtk_table_attach (table, fstype_label , 1, 3, 3, 4, GTK_FILL, 0, 5, 5); + + g_signal_connect (G_OBJECT (pie_canvas), "expose-event", G_CALLBACK (paint_pie_chart), window); + g_signal_connect (G_OBJECT (used_canvas), "expose-event", G_CALLBACK (paint_used_legend), window); + g_signal_connect (G_OBJECT (free_canvas), "expose-event", G_CALLBACK (paint_free_legend), window); + + return GTK_WIDGET (table); +} + +static GtkWidget* +create_volume_usage_widget (FMPropertiesWindow *window) +{ + GtkWidget *piewidget; + gchar *uri; + CajaFile *file; + GFile *location; + GFileInfo *info; + + file = get_original_file (window); + + uri = caja_file_get_activation_uri (file); + + location = g_file_new_for_uri (uri); + info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL); + + if (info) { + window->details->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); + window->details->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + + g_object_unref (info); + } else { + window->details->volume_capacity = 0; + window->details->volume_free = 0; + } + + g_object_unref (location); + + piewidget = create_pie_widget (window); + + gtk_widget_show_all (piewidget); + + return piewidget; +} + +static void +create_basic_page (FMPropertiesWindow *window) +{ + GtkTable *table; + GtkWidget *icon_aligner; + GtkWidget *icon_pixmap_widget; + GtkWidget *volume_usage; + GtkWidget *hbox, *vbox; + + guint last_row, row; + + hbox = create_page_with_hbox (window->details->notebook, _("Basic")); + + /* Icon pixmap */ + + icon_pixmap_widget = create_image_widget ( + window, should_show_custom_icon_buttons (window)); + gtk_widget_show (icon_pixmap_widget); + + icon_aligner = gtk_alignment_new (1, 0, 0, 0); + gtk_widget_show (icon_aligner); + + gtk_container_add (GTK_CONTAINER (icon_aligner), icon_pixmap_widget); + gtk_box_pack_start (GTK_BOX (hbox), icon_aligner, FALSE, FALSE, 0); + + window->details->icon_chooser = NULL; + + /* Table */ + + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (hbox), vbox); + + table = GTK_TABLE (create_attribute_value_table (GTK_VBOX (vbox), 0)); + window->details->basic_table = table; + + /* Name label. The text will be determined in update_name_field */ + row = append_title_field (table, NULL, &window->details->name_label); + window->details->name_row = row; + + /* Name field */ + window->details->name_field = NULL; + update_name_field (window); + + /* Start with name field selected, if it's an entry. */ + if (CAJA_IS_ENTRY (window->details->name_field)) { + caja_entry_select_all (CAJA_ENTRY (window->details->name_field)); + gtk_widget_grab_focus (GTK_WIDGET (window->details->name_field)); + } + + if (fm_ditem_page_should_show (window->details->target_files)) { + GtkSizeGroup *label_size_group; + GtkWidget *box; + + row = append_row (table); + + label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget (label_size_group, + GTK_WIDGET (window->details->name_label)); + box = fm_ditem_page_make_box (label_size_group, + window->details->target_files); + + gtk_table_attach (window->details->basic_table, box, + TITLE_COLUMN, VALUE_COLUMN + 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); + } + + if (should_show_file_type (window)) { + append_title_value_pair (window, + table, _("Type:"), + "type", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_link_target (window)) { + append_title_and_ellipsizing_value (window, table, + _("Link target:"), + "link_target", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (is_multi_file_window (window) || + caja_file_is_directory (get_target_file (window))) { + append_directory_contents_fields (window, table); + } else { + append_title_value_pair (window, table, _("Size:"), + "size_detail", + INCONSISTENT_STATE_STRING, + FALSE); + } + + append_blank_row (table); + + if (should_show_location_info (window)) { + append_title_and_ellipsizing_value (window, table, _("Location:"), + "where", + INCONSISTENT_STATE_STRING, + TRUE); + + append_title_and_ellipsizing_value (window, table, + _("Volume:"), + "volume", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_accessed_date (window)) { + append_blank_row (table); + + append_title_value_pair (window, table, _("Accessed:"), + "date_accessed", + INCONSISTENT_STATE_STRING, + FALSE); + append_title_value_pair (window, table, _("Modified:"), + "date_modified", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_free_space (window)) { + append_blank_row (table); + + append_title_value_pair (window, table, _("Free space:"), + "free_space", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_volume_usage (window)) { + last_row = append_row (table); + volume_usage = create_volume_usage_widget (window); + gtk_table_attach_defaults (GTK_TABLE(table), volume_usage, 0, 2, last_row, last_row+1); + } +} + +static GHashTable * +get_initial_emblems (GList *files) +{ + GHashTable *ret; + GList *l; + + ret = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)eel_g_list_free_deep); + + for (l = files; l != NULL; l = l->next) { + CajaFile *file; + GList *keywords; + + file = CAJA_FILE (l->data); + + keywords = caja_file_get_keywords (file); + g_hash_table_insert (ret, file, keywords); + } + + return ret; +} + +static gboolean +files_has_directory (FMPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + file = CAJA_FILE (l->data); + if (caja_file_is_directory (file)) { + return TRUE; + } + + } + + return FALSE; +} + +static gboolean +files_has_changable_permissions_directory (FMPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + file = CAJA_FILE (l->data); + if (caja_file_is_directory (file) && + caja_file_can_get_permissions (file) && + caja_file_can_set_permissions (file)) { + return TRUE; + } + + } + + return FALSE; +} + + +static gboolean +files_has_file (FMPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + file = CAJA_FILE (l->data); + if (!caja_file_is_directory (file)) { + return TRUE; + } + + } + + return FALSE; +} + + +static void +create_emblems_page (FMPropertiesWindow *window) +{ + GtkWidget *emblems_table, *button, *scroller; + char *emblem_name; + GdkPixbuf *pixbuf; + char *label; + GList *icons, *l; + CajaIconInfo *info; + + /* The emblems wrapped table */ + scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_NONE, &emblems_table); + + gtk_container_set_border_width (GTK_CONTAINER (emblems_table), 12); + + gtk_widget_show (scroller); + + gtk_notebook_append_page (window->details->notebook, + scroller, gtk_label_new (_("Emblems"))); + + icons = caja_emblem_list_available (); + + window->details->initial_emblems = get_initial_emblems (window->details->original_files); + + l = icons; + while (l != NULL) { + emblem_name = l->data; + l = l->next; + + if (!caja_emblem_should_show_in_list (emblem_name)) { + continue; + } + + info = caja_icon_info_lookup_from_name (emblem_name, CAJA_ICON_SIZE_SMALL); + pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, CAJA_ICON_SIZE_SMALL); + + if (pixbuf == NULL) { + continue; + } + + label = g_strdup (caja_icon_info_get_display_name (info)); + g_object_unref (info); + + if (label == NULL) { + label = caja_emblem_get_keyword_from_icon_name (emblem_name); + } + + button = eel_labeled_image_check_button_new (label, pixbuf); + eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), STANDARD_EMBLEM_HEIGHT); + eel_labeled_image_set_spacing (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), EMBLEM_LABEL_SPACING); + + g_free (label); + g_object_unref (pixbuf); + + /* Attach parameters and signal handler. */ + g_object_set_data_full (G_OBJECT (button), "caja_emblem_name", + caja_emblem_get_keyword_from_icon_name (emblem_name), g_free); + + window->details->emblem_buttons = + g_list_append (window->details->emblem_buttons, + button); + + g_signal_connect_object (button, "toggled", + G_CALLBACK (emblem_button_toggled), + G_OBJECT (window), + 0); + + gtk_container_add (GTK_CONTAINER (emblems_table), button); + } + eel_g_list_free_deep (icons); + gtk_widget_show_all (emblems_table); +} + +static void +start_long_operation (FMPropertiesWindow *window) +{ + if (window->details->long_operation_underway == 0) { + /* start long operation */ + GdkCursor * cursor; + + cursor = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor); + gdk_cursor_unref (cursor); + } + window->details->long_operation_underway ++; +} + +static void +end_long_operation (FMPropertiesWindow *window) +{ + if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL && + window->details->long_operation_underway == 1) { + /* finished !! */ + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL); + } + window->details->long_operation_underway--; +} + +static void +permission_change_callback (CajaFile *file, + GFile *res_loc, + GError *error, + gpointer callback_data) +{ + FMPropertiesWindow *window; + g_assert (callback_data != NULL); + + window = FM_PROPERTIES_WINDOW (callback_data); + end_long_operation (window); + + /* Report the error if it's an error. */ + fm_report_error_setting_permissions (file, error, NULL); + + g_object_unref (window); +} + +static void +update_permissions (FMPropertiesWindow *window, + guint32 vfs_new_perm, + guint32 vfs_mask, + gboolean is_folder, + gboolean apply_to_both_folder_and_dir, + gboolean use_original) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + guint32 permissions; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_get_permissions (file)) { + continue; + } + + if (!apply_to_both_folder_and_dir && + ((caja_file_is_directory (file) && !is_folder) || + (!caja_file_is_directory (file) && is_folder))) { + continue; + } + + permissions = caja_file_get_permissions (file); + if (use_original) { + gpointer ptr; + if (g_hash_table_lookup_extended (window->details->initial_permissions, + file, NULL, &ptr)) { + permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask); + } + } else { + permissions = (permissions & ~vfs_mask) | vfs_new_perm; + } + + start_long_operation (window); + g_object_ref (window); + caja_file_set_permissions + (file, permissions, + permission_change_callback, + window); + } +} + +static gboolean +initial_permission_state_consistent (FMPropertiesWindow *window, + guint32 mask, + gboolean is_folder, + gboolean both_folder_and_dir) +{ + GList *l; + gboolean first; + guint32 first_permissions; + + first = TRUE; + first_permissions = 0; + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + guint32 permissions; + + file = l->data; + + if (!both_folder_and_dir && + ((caja_file_is_directory (file) && !is_folder) || + (!caja_file_is_directory (file) && is_folder))) { + continue; + } + + permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->details->initial_permissions, + file)); + + if (first) { + if ((permissions & mask) != mask && + (permissions & mask) != 0) { + /* Not fully on or off -> inconsistent */ + return FALSE; + } + + first_permissions = permissions; + first = FALSE; + + } else if ((permissions & mask) != first_permissions) { + /* Not same permissions as first -> inconsistent */ + return FALSE; + } + } + return TRUE; +} + +static void +permission_button_toggled (GtkToggleButton *button, + FMPropertiesWindow *window) +{ + gboolean is_folder, is_special; + guint32 permission_mask; + gboolean inconsistent; + gboolean on; + + permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "permission")); + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-folder")); + is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-special")); + + if (gtk_toggle_button_get_active (button) + && !gtk_toggle_button_get_inconsistent (button)) { + /* Go to the initial state unless the initial state was + consistent, or we support recursive apply */ + inconsistent = TRUE; + on = TRUE; + + if (!window->details->has_recursive_apply && + initial_permission_state_consistent (window, permission_mask, is_folder, is_special)) { + inconsistent = FALSE; + on = TRUE; + } + } else if (gtk_toggle_button_get_inconsistent (button) + && !gtk_toggle_button_get_active (button)) { + inconsistent = FALSE; + on = TRUE; + } else { + inconsistent = FALSE; + on = FALSE; + } + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); + + gtk_toggle_button_set_active (button, on); + gtk_toggle_button_set_inconsistent (button, inconsistent); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); + + update_permissions (window, + on?permission_mask:0, + permission_mask, + is_folder, + is_special, + inconsistent); +} + +static void +permission_button_update (FMPropertiesWindow *window, + GtkToggleButton *button) +{ + GList *l; + gboolean all_set; + gboolean all_unset; + gboolean all_cannot_set; + gboolean is_folder, is_special; + gboolean no_match; + gboolean sensitive; + guint32 button_permission; + + if (gtk_toggle_button_get_inconsistent (button) && + window->details->has_recursive_apply) { + /* Never change from an inconsistent state if we have dirs, even + * if the current state is now consistent, because its a useful + * state for recursive apply. + */ + return; + } + + button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "permission")); + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-folder")); + is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-special")); + + all_set = TRUE; + all_unset = TRUE; + all_cannot_set = TRUE; + no_match = TRUE; + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + guint32 file_permissions; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_get_permissions (file)) { + continue; + } + + if (!is_special && + ((caja_file_is_directory (file) && !is_folder) || + (!caja_file_is_directory (file) && is_folder))) { + continue; + } + + no_match = FALSE; + + file_permissions = caja_file_get_permissions (file); + + if ((file_permissions & button_permission) == button_permission) { + all_unset = FALSE; + } else if ((file_permissions & button_permission) == 0) { + all_set = FALSE; + } else { + all_unset = FALSE; + all_set = FALSE; + } + + if (caja_file_can_set_permissions (file)) { + all_cannot_set = FALSE; + } + } + + sensitive = !all_cannot_set; + if (!is_folder) { + /* Don't insitive files when we have recursive apply */ + sensitive |= window->details->has_recursive_apply; + } + + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); + + gtk_toggle_button_set_active (button, !all_unset); + /* if actually inconsistent, or default value for file buttons + if no files are selected. (useful for recursive apply) */ + gtk_toggle_button_set_inconsistent (button, + (!all_unset && !all_set) || + (!is_folder && no_match)); + gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); +} + +static void +set_up_permissions_checkbox (FMPropertiesWindow *window, + GtkWidget *check_button, + guint32 permission, + gboolean is_folder) +{ + /* Load up the check_button with data we'll need when updating its state. */ + g_object_set_data (G_OBJECT (check_button), "permission", + GINT_TO_POINTER (permission)); + g_object_set_data (G_OBJECT (check_button), "properties_window", + window); + g_object_set_data (G_OBJECT (check_button), "is-folder", + GINT_TO_POINTER (is_folder)); + + window->details->permission_buttons = + g_list_prepend (window->details->permission_buttons, + check_button); + + g_signal_connect_object (check_button, "toggled", + G_CALLBACK (permission_button_toggled), + window, + 0); +} + +static void +add_permissions_checkbox_with_label (FMPropertiesWindow *window, + GtkTable *table, + int row, int column, + const char *label, + guint32 permission_to_check, + GtkLabel *label_for, + gboolean is_folder) +{ + GtkWidget *check_button; + gboolean a11y_enabled; + + check_button = gtk_check_button_new_with_mnemonic (label); + gtk_widget_show (check_button); + gtk_table_attach (table, check_button, + column, column + 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); + + set_up_permissions_checkbox (window, + check_button, + permission_to_check, + is_folder); + + a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (check_button)); + if (a11y_enabled && label_for != NULL) { + eel_accessibility_set_up_label_widget_relation (GTK_WIDGET (label_for), + check_button); + } +} + +static void +add_permissions_checkbox (FMPropertiesWindow *window, + GtkTable *table, + int row, int column, + guint32 permission_to_check, + GtkLabel *label_for, + gboolean is_folder) +{ + gchar *label; + + if (column == PERMISSIONS_CHECKBOXES_READ_COLUMN) { + label = _("_Read"); + } else if (column == PERMISSIONS_CHECKBOXES_WRITE_COLUMN) { + label = _("_Write"); + } else { + label = _("E_xecute"); + } + + add_permissions_checkbox_with_label (window, table, + row, column, + label, + permission_to_check, + label_for, + is_folder); +} + +enum { + UNIX_PERM_SUID = S_ISUID, + UNIX_PERM_SGID = S_ISGID, + UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */ + UNIX_PERM_USER_READ = S_IRUSR, + UNIX_PERM_USER_WRITE = S_IWUSR, + UNIX_PERM_USER_EXEC = S_IXUSR, + UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR, + UNIX_PERM_GROUP_READ = S_IRGRP, + UNIX_PERM_GROUP_WRITE = S_IWGRP, + UNIX_PERM_GROUP_EXEC = S_IXGRP, + UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP, + UNIX_PERM_OTHER_READ = S_IROTH, + UNIX_PERM_OTHER_WRITE = S_IWOTH, + UNIX_PERM_OTHER_EXEC = S_IXOTH, + UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH +}; + +typedef enum { + PERMISSION_READ = (1<<0), + PERMISSION_WRITE = (1<<1), + PERMISSION_EXEC = (1<<2) +} PermissionValue; + +typedef enum { + PERMISSION_USER, + PERMISSION_GROUP, + PERMISSION_OTHER +} PermissionType; + +static guint32 vfs_perms[3][3] = { + {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC}, + {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC}, + {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC}, +}; + +static guint32 +permission_to_vfs (PermissionType type, PermissionValue perm) +{ + guint32 vfs_perm; + g_assert (type >= 0 && type < 3); + + vfs_perm = 0; + if (perm & PERMISSION_READ) { + vfs_perm |= vfs_perms[type][0]; + } + if (perm & PERMISSION_WRITE) { + vfs_perm |= vfs_perms[type][1]; + } + if (perm & PERMISSION_EXEC) { + vfs_perm |= vfs_perms[type][2]; + } + + return vfs_perm; +} + + +static PermissionValue +permission_from_vfs (PermissionType type, guint32 vfs_perm) +{ + PermissionValue perm; + g_assert (type >= 0 && type < 3); + + perm = 0; + if (vfs_perm & vfs_perms[type][0]) { + perm |= PERMISSION_READ; + } + if (vfs_perm & vfs_perms[type][1]) { + perm |= PERMISSION_WRITE; + } + if (vfs_perm & vfs_perms[type][2]) { + perm |= PERMISSION_EXEC; + } + + return perm; +} + +static void +permission_combo_changed (GtkWidget *combo, FMPropertiesWindow *window) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gboolean is_folder, use_original; + PermissionType type; + int new_perm, mask; + guint32 vfs_new_perm, vfs_mask; + + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + + if (is_folder) { + mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; + } else { + mask = PERMISSION_READ|PERMISSION_WRITE; + } + + vfs_mask = permission_to_vfs (type, mask); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { + return; + } + gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1); + vfs_new_perm = permission_to_vfs (type, new_perm); + + update_permissions (window, vfs_new_perm, vfs_mask, + is_folder, FALSE, use_original); +} + +static void +permission_combo_add_multiple_choice (GtkComboBox *combo, GtkTreeIter *iter) +{ + GtkTreeModel *model; + GtkListStore *store; + gboolean found; + + model = gtk_combo_box_get_model (combo); + store = GTK_LIST_STORE (model); + + found = FALSE; + gtk_tree_model_get_iter_first (model, iter); + do { + gboolean multi; + gtk_tree_model_get (model, iter, 2, &multi, -1); + + if (multi) { + found = TRUE; + break; + } + } while (gtk_tree_model_iter_next (model, iter)); + + if (!found) { + gtk_list_store_append (store, iter); + gtk_list_store_set (store, iter, 0, "---", 1, 0, 2, TRUE, -1); + } +} + +static void +permission_combo_update (FMPropertiesWindow *window, + GtkComboBox *combo) +{ + PermissionType type; + PermissionValue perm, all_dir_perm, all_file_perm, all_perm; + gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same; + gboolean all_dir_cannot_set, all_file_cannot_set, sensitive; + GtkTreeIter iter; + int mask; + GtkTreeModel *model; + GtkListStore *store; + GList *l; + gboolean is_multi; + + model = gtk_combo_box_get_model (combo); + + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + + is_multi = FALSE; + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { + gtk_tree_model_get (model, &iter, 2, &is_multi, -1); + } + + if (is_multi && window->details->has_recursive_apply) { + /* Never change from an inconsistent state if we have dirs, even + * if the current state is now consistent, because its a useful + * state for recursive apply. + */ + return; + } + + no_files = TRUE; + no_dirs = TRUE; + all_dir_same = TRUE; + all_file_same = TRUE; + all_dir_perm = 0; + all_file_perm = 0; + all_dir_cannot_set = TRUE; + all_file_cannot_set = TRUE; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + guint32 file_permissions; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_get_permissions (file)) { + continue; + } + + if (caja_file_is_directory (file)) { + mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; + } else { + mask = PERMISSION_READ|PERMISSION_WRITE; + } + + file_permissions = caja_file_get_permissions (file); + + perm = permission_from_vfs (type, file_permissions) & mask; + + if (caja_file_is_directory (file)) { + if (no_dirs) { + all_dir_perm = perm; + no_dirs = FALSE; + } else if (perm != all_dir_perm) { + all_dir_same = FALSE; + } + + if (caja_file_can_set_permissions (file)) { + all_dir_cannot_set = FALSE; + } + } else { + if (no_files) { + all_file_perm = perm; + no_files = FALSE; + } else if (perm != all_file_perm) { + all_file_same = FALSE; + } + + if (caja_file_can_set_permissions (file)) { + all_file_cannot_set = FALSE; + } + } + } + + if (is_folder) { + all_same = all_dir_same; + all_perm = all_dir_perm; + } else { + all_same = all_file_same && !no_files; + all_perm = all_file_perm; + } + + store = GTK_LIST_STORE (model); + if (all_same) { + gboolean found; + + found = FALSE; + gtk_tree_model_get_iter_first (model, &iter); + do { + int current_perm; + gtk_tree_model_get (model, &iter, 1, ¤t_perm, -1); + + if (current_perm == all_perm) { + found = TRUE; + break; + } + } while (gtk_tree_model_iter_next (model, &iter)); + + if (!found) { + GString *str; + str = g_string_new (""); + + if (!(all_perm & PERMISSION_READ)) { + /* translators: this gets concatenated to "no read", + * "no access", etc. (see following strings) + */ + g_string_append (str, _("no ")); + } + if (is_folder) { + g_string_append (str, _("list")); + } else { + g_string_append (str, _("read")); + } + + g_string_append (str, ", "); + + if (!(all_perm & PERMISSION_WRITE)) { + g_string_append (str, _("no ")); + } + if (is_folder) { + g_string_append (str, _("create/delete")); + } else { + g_string_append (str, _("write")); + } + + if (is_folder) { + g_string_append (str, ", "); + + if (!(all_perm & PERMISSION_EXEC)) { + g_string_append (str, _("no ")); + } + g_string_append (str, _("access")); + } + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, str->str, + 1, all_perm, -1); + + g_string_free (str, TRUE); + } + } else { + permission_combo_add_multiple_choice (combo, &iter); + } + + g_signal_handlers_block_by_func (G_OBJECT (combo), + G_CALLBACK (permission_combo_changed), + window); + + gtk_combo_box_set_active_iter (combo, &iter); + + /* Also enable if no files found (for recursive + file changes when only selecting folders) */ + if (is_folder) { + sensitive = !all_dir_cannot_set; + } else { + sensitive = !all_file_cannot_set || + window->details->has_recursive_apply; + } + gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive); + + g_signal_handlers_unblock_by_func (G_OBJECT (combo), + G_CALLBACK (permission_combo_changed), + window); + +} + +static void +add_permissions_combo_box (FMPropertiesWindow *window, GtkTable *table, + PermissionType type, gboolean is_folder, + gboolean short_label) +{ + GtkWidget *combo; + GtkLabel *label; + GtkListStore *store; + GtkCellRenderer *cell; + GtkTreeIter iter; + int row; + + if (short_label) { + row = append_title_field (table, _("Access:"), &label); + } else if (is_folder) { + row = append_title_field (table, _("Folder access:"), &label); + } else { + row = append_title_field (table, _("File access:"), &label); + } + + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN); + combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); + + g_object_set_data (G_OBJECT (combo), "is-folder", GINT_TO_POINTER (is_folder)); + g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type)); + + if (is_folder) { + if (type != PERMISSION_USER) { + gtk_list_store_append (store, &iter); + /* Translators: this is referred to the permissions + * the user has in a directory. + */ + gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1); + } + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("List files only"), 1, PERMISSION_READ, -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("Access files"), 1, PERMISSION_READ|PERMISSION_EXEC, -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("Create and delete files"), 1, PERMISSION_READ|PERMISSION_EXEC|PERMISSION_WRITE, -1); + } else { + if (type != PERMISSION_USER) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1); + } + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("Read-only"), 1, PERMISSION_READ, -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("Read and write"), 1, PERMISSION_READ|PERMISSION_WRITE, -1); + } + if (window->details->has_recursive_apply) { + permission_combo_add_multiple_choice (GTK_COMBO_BOX (combo), &iter); + } + + g_object_unref (store); + + window->details->permission_combos = + g_list_prepend (window->details->permission_combos, + combo); + + g_signal_connect (combo, "changed", G_CALLBACK (permission_combo_changed), window); + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, + "text", 0, + NULL); + + gtk_label_set_mnemonic_widget (label, combo); + gtk_widget_show (combo); + + gtk_table_attach (table, combo, + VALUE_COLUMN, VALUE_COLUMN + 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); +} + + +static GtkWidget * +append_special_execution_checkbox (FMPropertiesWindow *window, + GtkTable *table, + const char *label_text, + guint32 permission_to_check) +{ + GtkWidget *check_button; + guint last_row; + + last_row = append_row (table); + + check_button = gtk_check_button_new_with_mnemonic (label_text); + gtk_widget_show (check_button); + + gtk_table_attach (table, check_button, + VALUE_COLUMN, VALUE_COLUMN + 1, + last_row, last_row + 1, + GTK_FILL, 0, + 0, 0); + + set_up_permissions_checkbox (window, + check_button, + permission_to_check, + FALSE); + g_object_set_data (G_OBJECT (check_button), "is-special", + GINT_TO_POINTER (TRUE)); + + return check_button; +} + +static void +append_special_execution_flags (FMPropertiesWindow *window, GtkTable *table) +{ + gint nrows; + + append_special_execution_checkbox + (window, table, _("Set _user ID"), UNIX_PERM_SUID); + + g_object_get (table, "n-rows", &nrows, NULL); + attach_title_field (table, nrows - 1, _("Special flags:")); + + append_special_execution_checkbox (window, table, _("Set gro_up ID"), UNIX_PERM_SGID); + append_special_execution_checkbox (window, table, _("_Sticky"), UNIX_PERM_STICKY); + + g_object_get (table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (table, nrows - 1, 18); +} + +static gboolean +all_can_get_permissions (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_get_permissions (file)) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +all_can_set_permissions (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_set_permissions (file)) { + return FALSE; + } + } + + return TRUE; +} + +static GHashTable * +get_initial_permissions (GList *file_list) +{ + GHashTable *ret; + GList *l; + + ret = g_hash_table_new (g_direct_hash, + g_direct_equal); + + for (l = file_list; l != NULL; l = l->next) { + guint32 permissions; + CajaFile *file; + + file = CAJA_FILE (l->data); + + permissions = caja_file_get_permissions (file); + g_hash_table_insert (ret, file, + GINT_TO_POINTER (permissions)); + } + + return ret; +} + +static void +create_simple_permissions (FMPropertiesWindow *window, GtkTable *page_table) +{ + gboolean has_file, has_directory; + GtkLabel *group_label; + GtkLabel *owner_label; + GtkLabel *execute_label; + GtkWidget *value; + GtkComboBox *group_combo_box; + GtkComboBox *owner_combo_box; + guint last_row; + gint nrows; + + last_row = 0; + + has_file = files_has_file (window); + has_directory = files_has_directory (window); + + if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) { + owner_label = attach_title_field (page_table, last_row, _("_Owner:")); + /* Combo box in this case. */ + owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window)); + gtk_label_set_mnemonic_widget (owner_label, + GTK_WIDGET (owner_combo_box)); + } else { + owner_label = attach_title_field (page_table, last_row, _("Owner:")); + /* Static text in this case. */ + value = attach_value_field (window, + page_table, last_row, VALUE_COLUMN, + "owner", + INCONSISTENT_STATE_STRING, + FALSE); + gtk_label_set_mnemonic_widget (owner_label, value); + } + + if (has_directory) { + add_permissions_combo_box (window, page_table, + PERMISSION_USER, TRUE, FALSE); + } + if (has_file || window->details->has_recursive_apply) { + add_permissions_combo_box (window, page_table, + PERMISSION_USER, FALSE, !has_directory); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) { + last_row = append_title_field (page_table, + _("_Group:"), + &group_label); + /* Combo box in this case. */ + group_combo_box = attach_group_combo_box (page_table, last_row, + get_target_file (window)); + gtk_label_set_mnemonic_widget (group_label, + GTK_WIDGET (group_combo_box)); + } else { + last_row = append_title_field (page_table, + _("Group:"), + &group_label); + /* Static text in this case. */ + value = attach_value_field (window, page_table, last_row, + VALUE_COLUMN, + "group", + INCONSISTENT_STATE_STRING, + FALSE); + gtk_label_set_mnemonic_widget (group_label, value); + } + + if (has_directory) { + add_permissions_combo_box (window, page_table, + PERMISSION_GROUP, TRUE, + FALSE); + } + if (has_file || window->details->has_recursive_apply) { + add_permissions_combo_box (window, page_table, + PERMISSION_GROUP, FALSE, + !has_directory); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + append_title_field (page_table, + _("Others"), + &group_label); + + if (has_directory) { + add_permissions_combo_box (window, page_table, + PERMISSION_OTHER, TRUE, + FALSE); + } + if (has_file || window->details->has_recursive_apply) { + add_permissions_combo_box (window, page_table, + PERMISSION_OTHER, FALSE, + !has_directory); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + last_row = append_title_field (page_table, + _("Execute:"), + &execute_label); + add_permissions_checkbox_with_label (window, page_table, + last_row, 1, + _("Allow _executing file as program"), + UNIX_PERM_USER_EXEC|UNIX_PERM_GROUP_EXEC|UNIX_PERM_OTHER_EXEC, + execute_label, FALSE); + +} + +static void +create_permission_checkboxes (FMPropertiesWindow *window, + GtkTable *page_table, + gboolean is_folder) +{ + guint checkbox_titles_row; + GtkLabel *owner_perm_label; + GtkLabel *group_perm_label; + GtkLabel *other_perm_label; + GtkTable *check_button_table; + + checkbox_titles_row = append_title_field (page_table, _("Owner:"), &owner_perm_label); + append_title_field (page_table, _("Group:"), &group_perm_label); + append_title_field (page_table, _("Others:"), &other_perm_label); + + check_button_table = GTK_TABLE (gtk_table_new + (PERMISSIONS_CHECKBOXES_ROW_COUNT, + PERMISSIONS_CHECKBOXES_COLUMN_COUNT, + FALSE)); + apply_standard_table_padding (check_button_table); + gtk_widget_show (GTK_WIDGET (check_button_table)); + gtk_table_attach (page_table, GTK_WIDGET (check_button_table), + VALUE_COLUMN, VALUE_COLUMN + 1, + checkbox_titles_row, checkbox_titles_row + PERMISSIONS_CHECKBOXES_ROW_COUNT, + 0, 0, + 0, 0); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OWNER_ROW, + PERMISSIONS_CHECKBOXES_READ_COLUMN, + UNIX_PERM_USER_READ, + owner_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OWNER_ROW, + PERMISSIONS_CHECKBOXES_WRITE_COLUMN, + UNIX_PERM_USER_WRITE, + owner_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OWNER_ROW, + PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, + UNIX_PERM_USER_EXEC, + owner_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_GROUP_ROW, + PERMISSIONS_CHECKBOXES_READ_COLUMN, + UNIX_PERM_GROUP_READ, + group_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_GROUP_ROW, + PERMISSIONS_CHECKBOXES_WRITE_COLUMN, + UNIX_PERM_GROUP_WRITE, + group_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_GROUP_ROW, + PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, + UNIX_PERM_GROUP_EXEC, + group_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OTHERS_ROW, + PERMISSIONS_CHECKBOXES_READ_COLUMN, + UNIX_PERM_OTHER_READ, + other_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OTHERS_ROW, + PERMISSIONS_CHECKBOXES_WRITE_COLUMN, + UNIX_PERM_OTHER_WRITE, + other_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OTHERS_ROW, + PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, + UNIX_PERM_OTHER_EXEC, + other_perm_label, + is_folder); +} + +static void +create_advanced_permissions (FMPropertiesWindow *window, GtkTable *page_table) +{ + guint last_row; + GtkLabel *group_label; + GtkLabel *owner_label; + GtkComboBox *group_combo_box; + GtkComboBox *owner_combo_box; + gboolean has_directory, has_file; + gint nrows; + + last_row = 0; + + if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) { + + owner_label = attach_title_field (page_table, last_row, _("_Owner:")); + /* Combo box in this case. */ + owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window)); + gtk_label_set_mnemonic_widget (owner_label, + GTK_WIDGET (owner_combo_box)); + } else { + GtkWidget *value; + + owner_label = attach_title_field (page_table, last_row, _("Owner:")); + /* Static text in this case. */ + value = attach_value_field (window, + page_table, last_row, VALUE_COLUMN, + "owner", + INCONSISTENT_STATE_STRING, + FALSE); + gtk_label_set_mnemonic_widget (owner_label, value); + } + + if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) { + last_row = append_title_field (page_table, + _("_Group:"), + &group_label); + /* Combo box in this case. */ + group_combo_box = attach_group_combo_box (page_table, last_row, + get_target_file (window)); + gtk_label_set_mnemonic_widget (group_label, + GTK_WIDGET (group_combo_box)); + } else { + last_row = append_title_field (page_table, + _("Group:"), + NULL); + /* Static text in this case. */ + attach_value_field (window, page_table, last_row, + VALUE_COLUMN, + "group", + INCONSISTENT_STATE_STRING, + FALSE); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + has_directory = files_has_directory (window); + has_file = files_has_file (window); + + if (has_directory) { + if (has_file || window->details->has_recursive_apply) { + append_title_field (page_table, + _("Folder Permissions:"), + NULL); + } + create_permission_checkboxes (window, page_table, TRUE); + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + } + + + if (has_file || window->details->has_recursive_apply) { + if (has_directory) { + append_title_field (page_table, + _("File Permissions:"), + NULL); + } + create_permission_checkboxes (window, page_table, FALSE); + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + } + + append_special_execution_flags (window, page_table); + + append_title_value_pair + (window, page_table, _("Text view:"), + "permissions", INCONSISTENT_STATE_STRING, + FALSE); +} + +static void +set_recursive_permissions_done (gpointer callback_data) +{ + FMPropertiesWindow *window; + + window = FM_PROPERTIES_WINDOW (callback_data); + end_long_operation (window); + + g_object_unref (window); +} + + +static void +apply_recursive_clicked (GtkWidget *recursive_button, + FMPropertiesWindow *window) +{ + guint32 file_permission, file_permission_mask; + guint32 dir_permission, dir_permission_mask; + guint32 vfs_mask, vfs_new_perm, p; + GtkWidget *button, *combo; + gboolean active, is_folder, is_special, use_original; + GList *l; + GtkTreeModel *model; + GtkTreeIter iter; + PermissionType type; + int new_perm, mask; + + file_permission = 0; + file_permission_mask = 0; + dir_permission = 0; + dir_permission_mask = 0; + + /* Advanced mode and execute checkbox: */ + for (l = window->details->permission_buttons; l != NULL; l = l->next) { + button = l->data; + + if (gtk_toggle_button_get_inconsistent (GTK_TOGGLE_BUTTON (button))) { + continue; + } + + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); + p = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "permission")); + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-folder")); + is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-special")); + + if (is_folder || is_special) { + dir_permission_mask |= p; + if (active) { + dir_permission |= p; + } + } + if (!is_folder || is_special) { + file_permission_mask |= p; + if (active) { + file_permission |= p; + } + } + } + /* Simple mode, minus exec checkbox */ + for (l = window->details->permission_combos; l != NULL; l = l->next) { + combo = l->data; + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { + continue; + } + + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), + "is-folder")); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1); + if (use_original) { + continue; + } + vfs_new_perm = permission_to_vfs (type, new_perm); + + if (is_folder) { + mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; + } else { + mask = PERMISSION_READ|PERMISSION_WRITE; + } + vfs_mask = permission_to_vfs (type, mask); + + if (is_folder) { + dir_permission_mask |= vfs_mask; + dir_permission |= vfs_new_perm; + } else { + file_permission_mask |= vfs_mask; + file_permission |= vfs_new_perm; + } + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + char *uri; + + file = CAJA_FILE (l->data); + + if (caja_file_is_directory (file) && + caja_file_can_set_permissions (file)) { + uri = caja_file_get_uri (file); + start_long_operation (window); + g_object_ref (window); + caja_file_set_permissions_recursive (uri, + file_permission, + file_permission_mask, + dir_permission, + dir_permission_mask, + set_recursive_permissions_done, + window); + g_free (uri); + } + } +} + +static void +create_permissions_page (FMPropertiesWindow *window) +{ + GtkWidget *vbox, *button, *hbox; + GtkTable *page_table; + char *file_name, *prompt_text; + GList *file_list; + guint last_row; + gint nrows; + + vbox = create_page_with_vbox (window->details->notebook, + _("Permissions")); + + file_list = window->details->original_files; + + window->details->initial_permissions = NULL; + + if (all_can_get_permissions (file_list) && all_can_get_permissions (window->details->target_files)) { + window->details->initial_permissions = get_initial_permissions (window->details->target_files); + window->details->has_recursive_apply = files_has_changable_permissions_directory (window); + + if (!all_can_set_permissions (file_list)) { + add_prompt_and_separator ( + GTK_VBOX (vbox), + _("You are not the owner, so you cannot change these permissions.")); + } + + page_table = GTK_TABLE (gtk_table_new (1, COLUMN_COUNT, FALSE)); + window->details->permissions_table = page_table; + + apply_standard_table_padding (page_table); + gtk_widget_show (GTK_WIDGET (page_table)); + gtk_box_pack_start (GTK_BOX (vbox), + GTK_WIDGET (page_table), + TRUE, TRUE, 0); + + if (eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS)) { + window->details->advanced_permissions = TRUE; + create_advanced_permissions (window, page_table); + } else { + window->details->advanced_permissions = FALSE; + create_simple_permissions (window, page_table); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + +#ifdef HAVE_SELINUX + append_title_value_pair + (window, page_table, _("SELinux context:"), + "selinux_context", INCONSISTENT_STATE_STRING, + FALSE); +#endif + append_title_value_pair + (window, page_table, _("Last changed:"), + "date_permissions", INCONSISTENT_STATE_STRING, + FALSE); + + if (window->details->has_recursive_apply) { + last_row = append_row (page_table); + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_table_attach (page_table, hbox, + 0, 2, + last_row, last_row+1, + GTK_FILL, 0, + 0, 0); + + button = gtk_button_new_with_mnemonic (_("Apply Permissions to Enclosed Files")); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + g_signal_connect (button, "clicked", + G_CALLBACK (apply_recursive_clicked), + window); + } + } else { + if (!is_multi_file_window (window)) { + file_name = caja_file_get_display_name (get_target_file (window)); + prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name); + g_free (file_name); + } else { + prompt_text = g_strdup (_("The permissions of the selected file could not be determined.")); + } + + add_prompt (GTK_VBOX (vbox), prompt_text, TRUE); + g_free (prompt_text); + } +} + +static void +append_extension_pages (FMPropertiesWindow *window) +{ + GList *providers; + GList *p; + + providers = caja_module_get_extensions_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER); + + for (p = providers; p != NULL; p = p->next) { + CajaPropertyPageProvider *provider; + GList *pages; + GList *l; + + provider = CAJA_PROPERTY_PAGE_PROVIDER (p->data); + + pages = caja_property_page_provider_get_pages + (provider, window->details->original_files); + + for (l = pages; l != NULL; l = l->next) { + CajaPropertyPage *page; + GtkWidget *page_widget; + GtkWidget *label; + + page = CAJA_PROPERTY_PAGE (l->data); + + g_object_get (G_OBJECT (page), + "page", &page_widget, "label", &label, + NULL); + + gtk_notebook_append_page (window->details->notebook, + page_widget, label); + + g_object_set_data (G_OBJECT (page_widget), + "is-extension-page", + page); + + g_object_unref (page_widget); + g_object_unref (label); + + g_object_unref (page); + } + + g_list_free (pages); + } + + caja_module_extension_list_free (providers); +} + +static gboolean +should_show_emblems (FMPropertiesWindow *window) +{ + /* FIXME bugzilla.gnome.org 45643: + * Emblems aren't displayed on the the desktop Trash icon, so + * we shouldn't pretend that they work by showing them here. + * When bug 5643 is fixed we can remove this case. + */ + if (!is_multi_file_window (window) + && is_merged_trash_directory (get_target_file (window))) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_permissions (FMPropertiesWindow *window) +{ + CajaFile *file; + + file = get_target_file (window); + + /* Don't show permissions for Trash and Computer since they're not + * really file system objects. + */ + if (!is_multi_file_window (window) + && (is_merged_trash_directory (file) || + is_computer_directory (file))) { + return FALSE; + } + + return TRUE; +} + +static char * +get_pending_key (GList *file_list) +{ + GList *l; + GList *uris; + GString *key; + char *ret; + + uris = NULL; + for (l = file_list; l != NULL; l = l->next) { + uris = g_list_prepend (uris, caja_file_get_uri (CAJA_FILE (l->data))); + } + uris = g_list_sort (uris, (GCompareFunc)strcmp); + + key = g_string_new (""); + for (l = uris; l != NULL; l = l->next) { + g_string_append (key, l->data); + g_string_append (key, ";"); + } + + eel_g_list_free_deep (uris); + + ret = key->str; + g_string_free (key, FALSE); + + return ret; +} + +static StartupData * +startup_data_new (GList *original_files, + GList *target_files, + const char *pending_key, + GtkWidget *parent_widget) +{ + StartupData *data; + GList *l; + + data = g_new0 (StartupData, 1); + data->original_files = caja_file_list_copy (original_files); + data->target_files = caja_file_list_copy (target_files); + data->parent_widget = parent_widget; + data->pending_key = g_strdup (pending_key); + data->pending_files = g_hash_table_new (g_direct_hash, + g_direct_equal); + + for (l = data->target_files; l != NULL; l = l->next) { + g_hash_table_insert (data->pending_files, l->data, l->data); + } + + return data; +} + +static void +startup_data_free (StartupData *data) +{ + caja_file_list_free (data->original_files); + caja_file_list_free (data->target_files); + g_hash_table_destroy (data->pending_files); + g_free (data->pending_key); + g_free (data); +} + +static void +file_changed_callback (CajaFile *file, gpointer user_data) +{ + FMPropertiesWindow *window = FM_PROPERTIES_WINDOW (user_data); + + if (!g_list_find (window->details->changed_files, file)) { + caja_file_ref (file); + window->details->changed_files = g_list_prepend (window->details->changed_files, file); + + schedule_files_update (window); + } +} + +static gboolean +is_a_special_file (CajaFile *file) +{ + if (file == NULL || + CAJA_IS_DESKTOP_ICON_FILE (file) || + caja_file_is_caja_link (file) || + is_merged_trash_directory (file) || + is_computer_directory (file)) { + return TRUE; + } + return FALSE; +} + +static gboolean +should_show_open_with (FMPropertiesWindow *window) +{ + CajaFile *file; + + /* Don't show open with tab for desktop special icons (trash, etc) + * or desktop files. We don't get the open-with menu for these anyway. + * + * Also don't show it for folders. Changing the default app for folders + * leads to all sort of hard to understand errors. + */ + + if (is_multi_file_window (window)) { + if (!file_list_attributes_identical (window->details->original_files, + "mime_type")) { + return FALSE; + } else { + + GList *l; + + for (l = window->details->original_files; l; l = l->next) { + file = CAJA_FILE (l->data); + if (caja_file_is_directory (file) || + is_a_special_file (file)) { + return FALSE; + } + } + } + } else { + file = get_original_file (window); + if (caja_file_is_directory (file) || + is_a_special_file (file)) { + return FALSE; + } + } + return TRUE; +} + +static void +create_open_with_page (FMPropertiesWindow *window) +{ + GtkWidget *vbox; + char *mime_type; + char *uri; + + mime_type = caja_file_get_mime_type (get_target_file (window)); + + if (!is_multi_file_window (window)) { + uri = caja_file_get_uri (get_target_file (window)); + if (uri == NULL) { + return; + } + vbox = caja_mime_application_chooser_new (uri, mime_type); + + g_free (uri); + } else { + GList *uris; + + uris = window->details->original_files; + if (uris == NULL) { + return; + } + vbox = caja_mime_application_chooser_new_for_multiple_files (uris, mime_type); + } + + gtk_widget_show (vbox); + g_free (mime_type); + + gtk_notebook_append_page (window->details->notebook, + vbox, gtk_label_new (_("Open With"))); +} + + +static FMPropertiesWindow * +create_properties_window (StartupData *startup_data) +{ + FMPropertiesWindow *window; + GList *l; + + window = FM_PROPERTIES_WINDOW (gtk_widget_new (fm_properties_window_get_type (), NULL)); + + window->details->original_files = caja_file_list_copy (startup_data->original_files); + + window->details->target_files = caja_file_list_copy (startup_data->target_files); + + gtk_window_set_wmclass (GTK_WINDOW (window), "file_properties", "Caja"); + gtk_window_set_screen (GTK_WINDOW (window), + gtk_widget_get_screen (startup_data->parent_widget)); + + gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG); + + /* Set initial window title */ + update_properties_window_title (window); + + /* Start monitoring the file attributes we display. Note that some + * of the attributes are for the original file, and some for the + * target files. + */ + + for (l = window->details->original_files; l != NULL; l = l->next) { + CajaFile *file; + CajaFileAttributes attributes; + + file = CAJA_FILE (l->data); + + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO; + + caja_file_monitor_add (CAJA_FILE (l->data), + &window->details->original_files, + attributes); + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + CajaFileAttributes attributes; + + file = CAJA_FILE (l->data); + + attributes = 0; + if (caja_file_is_directory (file)) { + attributes |= CAJA_FILE_ATTRIBUTE_DEEP_COUNTS; + } + + attributes |= CAJA_FILE_ATTRIBUTE_INFO; + caja_file_monitor_add (file, &window->details->target_files, attributes); + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + g_signal_connect_object (CAJA_FILE (l->data), + "changed", + G_CALLBACK (file_changed_callback), + G_OBJECT (window), + 0); + } + + for (l = window->details->original_files; l != NULL; l = l->next) { + g_signal_connect_object (CAJA_FILE (l->data), + "changed", + G_CALLBACK (file_changed_callback), + G_OBJECT (window), + 0); + } + + /* Create the notebook tabs. */ + window->details->notebook = GTK_NOTEBOOK (gtk_notebook_new ()); + gtk_widget_show (GTK_WIDGET (window->details->notebook)); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), + GTK_WIDGET (window->details->notebook), + TRUE, TRUE, 0); + + /* Create the pages. */ + create_basic_page (window); + + if (should_show_emblems (window)) { + create_emblems_page (window); + } + + if (should_show_permissions (window)) { + create_permissions_page (window); + } + + if (should_show_open_with (window)) { + create_open_with_page (window); + } + + /* append pages from available views */ + append_extension_pages (window); + + gtk_dialog_add_buttons (GTK_DIALOG (window), + GTK_STOCK_HELP, GTK_RESPONSE_HELP, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + + /* FIXME - HIGificiation, should be done inside GTK+ */ + gtk_widget_ensure_style (GTK_WIDGET (window)); + gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12); + gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (window))), 0); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12); + gtk_dialog_set_has_separator (GTK_DIALOG (window), FALSE); + + /* Update from initial state */ + properties_window_update (window, NULL); + + return window; +} + +static GList * +get_target_file_list (GList *original_files) +{ + GList *ret; + GList *l; + + ret = NULL; + + for (l = original_files; l != NULL; l = l->next) { + CajaFile *target; + + target = get_target_file_for_original_file (CAJA_FILE (l->data)); + + ret = g_list_prepend (ret, target); + } + + ret = g_list_reverse (ret); + + return ret; +} + +static void +add_window (FMPropertiesWindow *window) +{ + if (!is_multi_file_window (window)) { + g_hash_table_insert (windows, + get_original_file (window), + window); + g_object_set_data (G_OBJECT (window), "window_key", + get_original_file (window)); + } +} + +static void +remove_window (FMPropertiesWindow *window) +{ + gpointer key; + + key = g_object_get_data (G_OBJECT (window), "window_key"); + if (key) { + g_hash_table_remove (windows, key); + } +} + +static GtkWindow * +get_existing_window (GList *file_list) +{ + if (!file_list->next) { + return g_hash_table_lookup (windows, file_list->data); + } + + return NULL; +} + +static void +cancel_create_properties_window_callback (gpointer callback_data) +{ + remove_pending ((StartupData *)callback_data, TRUE, FALSE, TRUE); +} + +static void +parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data) +{ + g_assert (widget == ((StartupData *)callback_data)->parent_widget); + + remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE); +} + +static void +cancel_call_when_ready_callback (gpointer key, + gpointer value, + gpointer user_data) +{ + caja_file_cancel_call_when_ready + (CAJA_FILE (key), + is_directory_ready_callback, + user_data); +} + +static void +remove_pending (StartupData *startup_data, + gboolean cancel_call_when_ready, + gboolean cancel_timed_wait, + gboolean cancel_destroy_handler) +{ + if (cancel_call_when_ready) { + g_hash_table_foreach (startup_data->pending_files, + cancel_call_when_ready_callback, + startup_data); + + } + if (cancel_timed_wait) { + eel_timed_wait_stop + (cancel_create_properties_window_callback, startup_data); + } + if (cancel_destroy_handler) { + g_signal_handlers_disconnect_by_func (startup_data->parent_widget, + G_CALLBACK (parent_widget_destroyed_callback), + startup_data); + } + + g_hash_table_remove (pending_lists, startup_data->pending_key); + + startup_data_free (startup_data); +} + +static void +is_directory_ready_callback (CajaFile *file, + gpointer data) +{ + StartupData *startup_data; + + startup_data = data; + + g_hash_table_remove (startup_data->pending_files, file); + + if (g_hash_table_size (startup_data->pending_files) == 0) { + FMPropertiesWindow *new_window; + + new_window = create_properties_window (startup_data); + + add_window (new_window); + + remove_pending (startup_data, FALSE, TRUE, TRUE); + +/* FIXME bugzilla.gnome.org 42151: + * See comment elsewhere in this file about bug 2151. + */ +#ifdef UNDO_ENABLED + caja_undo_share_undo_manager (GTK_OBJECT (new_window), + GTK_OBJECT (callback_data)); +#endif + gtk_window_present (GTK_WINDOW (new_window)); + } +} + + +void +fm_properties_window_present (GList *original_files, + GtkWidget *parent_widget) +{ + GList *l, *next; + GtkWidget *parent_window; + StartupData *startup_data; + GList *target_files; + GtkWindow *existing_window; + char *pending_key; + + g_return_if_fail (original_files != NULL); + g_return_if_fail (GTK_IS_WIDGET (parent_widget)); + + /* Create the hash tables first time through. */ + if (windows == NULL) { + windows = eel_g_hash_table_new_free_at_exit + (NULL, NULL, "property windows"); + } + + if (pending_lists == NULL) { + pending_lists = eel_g_hash_table_new_free_at_exit + (g_str_hash, g_str_equal, "pending property window files"); + } + + /* Look to see if there's already a window for this file. */ + existing_window = get_existing_window (original_files); + if (existing_window != NULL) { + gtk_window_set_screen (existing_window, + gtk_widget_get_screen (parent_widget)); + gtk_window_present (existing_window); + return; + } + + + pending_key = get_pending_key (original_files); + + /* Look to see if we're already waiting for a window for this file. */ + if (g_hash_table_lookup (pending_lists, pending_key) != NULL) { + return; + } + + target_files = get_target_file_list (original_files); + + startup_data = startup_data_new (original_files, + target_files, + pending_key, + parent_widget); + + caja_file_list_free (target_files); + g_free(pending_key); + + /* Wait until we can tell whether it's a directory before showing, since + * some one-time layout decisions depend on that info. + */ + + g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key); + g_signal_connect (parent_widget, "destroy", + G_CALLBACK (parent_widget_destroyed_callback), startup_data); + + parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW); + + eel_timed_wait_start + (cancel_create_properties_window_callback, + startup_data, + _("Creating Properties window."), + parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); + + + for (l = startup_data->target_files; l != NULL; l = next) { + next = l->next; + caja_file_call_when_ready + (CAJA_FILE (l->data), + CAJA_FILE_ATTRIBUTE_INFO, + is_directory_ready_callback, + startup_data); + } +} + +static void +real_response (GtkDialog *dialog, + int response) +{ + GError *error = NULL; + + switch (response) { + case GTK_RESPONSE_HELP: + gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)), + "ghelp:user-guide#goscaja-51", + gtk_get_current_event_time (), + &error); + if (error != NULL) { + eel_show_error_dialog (_("There was an error displaying help."), error->message, + GTK_WINDOW (dialog)); + g_error_free (error); + } + break; + + case GTK_RESPONSE_NONE: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + + default: + g_assert_not_reached (); + break; + } +} + +static void +real_destroy (GtkObject *object) +{ + FMPropertiesWindow *window; + GList *l; + + window = FM_PROPERTIES_WINDOW (object); + + remove_window (window); + + for (l = window->details->original_files; l != NULL; l = l->next) { + caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->original_files); + } + caja_file_list_free (window->details->original_files); + window->details->original_files = NULL; + + for (l = window->details->target_files; l != NULL; l = l->next) { + caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->target_files); + } + caja_file_list_free (window->details->target_files); + window->details->target_files = NULL; + + caja_file_list_free (window->details->changed_files); + window->details->changed_files = NULL; + + window->details->name_field = NULL; + + g_list_free (window->details->emblem_buttons); + window->details->emblem_buttons = NULL; + + if (window->details->initial_emblems) { + g_hash_table_destroy (window->details->initial_emblems); + window->details->initial_emblems = NULL; + } + + g_list_free (window->details->permission_buttons); + window->details->permission_buttons = NULL; + + g_list_free (window->details->permission_combos); + window->details->permission_combos = NULL; + + if (window->details->initial_permissions) { + g_hash_table_destroy (window->details->initial_permissions); + window->details->initial_permissions = NULL; + } + + g_list_free (window->details->value_fields); + window->details->value_fields = NULL; + + if (window->details->update_directory_contents_timeout_id != 0) { + g_source_remove (window->details->update_directory_contents_timeout_id); + window->details->update_directory_contents_timeout_id = 0; + } + + if (window->details->update_files_timeout_id != 0) { + g_source_remove (window->details->update_files_timeout_id); + window->details->update_files_timeout_id = 0; + } + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +real_finalize (GObject *object) +{ + FMPropertiesWindow *window; + + window = FM_PROPERTIES_WINDOW (object); + + eel_g_list_free_deep (window->details->mime_list); + + g_free (window->details->pending_name); + g_free (window->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* converts + * file://foo/foobar/foofoo/bar + * to + * foofoo/bar + * if + * file://foo/foobar + * is the parent + * + * It does not resolve any symlinks. + * */ +static char * +make_relative_uri_from_full (const char *uri, + const char *base_uri) +{ + g_assert (uri != NULL); + g_assert (base_uri != NULL); + + if (g_str_has_prefix (uri, base_uri)) { + uri += strlen (base_uri); + if (*uri != '/') { + return NULL; + } + + while (*uri == '/') { + uri++; + } + + if (*uri != '\0') { + return g_strdup (uri); + } + } + + return NULL; +} + +/* icon selection callback to set the image of the file object to the selected file */ +static void +set_icon (const char* icon_uri, FMPropertiesWindow *properties_window) +{ + CajaFile *file; + char *file_uri; + char *icon_path; + char *real_icon_uri; + + g_assert (icon_uri != NULL); + g_assert (FM_IS_PROPERTIES_WINDOW (properties_window)); + + icon_path = g_filename_from_uri (icon_uri, NULL, NULL); + /* we don't allow remote URIs */ + if (icon_path != NULL) { + GList *l; + + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + file_uri = caja_file_get_uri (file); + + if (caja_file_is_mime_type (file, "application/x-desktop")) { + if (caja_link_local_set_icon (file_uri, icon_path)) { + caja_file_invalidate_attributes (file, + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO); + } + } else { + real_icon_uri = make_relative_uri_from_full (icon_uri, file_uri); + if (real_icon_uri == NULL) { + real_icon_uri = g_strdup (icon_uri); + } + + caja_file_set_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri); + caja_file_set_metadata (file, CAJA_METADATA_KEY_ICON_SCALE, NULL, NULL); + + g_free (real_icon_uri); + } + + g_free (file_uri); + } + + g_free (icon_path); + } +} + +static void +update_preview_callback (GtkFileChooser *icon_chooser, + FMPropertiesWindow *window) +{ + GtkWidget *preview_widget; + GdkPixbuf *pixbuf, *scaled_pixbuf; + char *filename; + double scale; + + pixbuf = NULL; + + filename = gtk_file_chooser_get_filename (icon_chooser); + if (filename != NULL) { + pixbuf = gdk_pixbuf_new_from_file (filename, NULL); + } + + if (pixbuf != NULL) { + preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser); + gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE); + + if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH) { + scale = (double)gdk_pixbuf_get_height (pixbuf) / + gdk_pixbuf_get_width (pixbuf); + + scaled_pixbuf = mate_desktop_thumbnail_scale_down_pixbuf + (pixbuf, + PREVIEW_IMAGE_WIDTH, + scale * PREVIEW_IMAGE_WIDTH); + g_object_unref (pixbuf); + pixbuf = scaled_pixbuf; + } + + gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf); + } else { + gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE); + } + + g_free (filename); + + if (pixbuf != NULL) { + g_object_unref (pixbuf); + } +} + +static void +custom_icon_file_chooser_response_cb (GtkDialog *dialog, + gint response, + FMPropertiesWindow *window) +{ + char *uri; + + switch (response) { + case GTK_RESPONSE_NO: + reset_icon (window); + break; + + case GTK_RESPONSE_OK: + uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); + set_icon (uri, window); + g_free (uri); + break; + + default: + break; + } + + gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static void +select_image_button_callback (GtkWidget *widget, + FMPropertiesWindow *window) +{ + GtkWidget *dialog, *preview; + GtkFileFilter *filter; + GList *l; + CajaFile *file; + char *uri; + char *image_path; + gboolean revert_is_sensitive; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + dialog = window->details->icon_chooser; + + if (dialog == NULL) { + dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_REVERT_TO_SAVED, GTK_RESPONSE_NO, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_OK, + NULL); + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), "/usr/share/pixmaps", NULL); + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + + filter = gtk_file_filter_new (); + gtk_file_filter_add_pixbuf_formats (filter); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + + preview = gtk_image_new (); + gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1); + gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview); + gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE); + gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE); + + g_signal_connect (dialog, "update-preview", + G_CALLBACK (update_preview_callback), window); + + window->details->icon_chooser = dialog; + + g_object_add_weak_pointer (G_OBJECT (dialog), + (gpointer *) &window->details->icon_chooser); + } + + /* it's likely that the user wants to pick an icon that is inside a local directory */ + if (g_list_length (window->details->original_files) == 1) { + file = CAJA_FILE (window->details->original_files->data); + + if (caja_file_is_directory (file)) { + uri = caja_file_get_uri (file); + + image_path = g_filename_from_uri (uri, NULL, NULL); + if (image_path != NULL) { + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path); + g_free (image_path); + } + + g_free (uri); + } + } + + revert_is_sensitive = FALSE; + for (l = window->details->original_files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + image_path = caja_file_get_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL); + revert_is_sensitive = (image_path != NULL); + g_free (image_path); + + if (revert_is_sensitive) { + break; + } + } + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive); + + g_signal_connect (dialog, "response", + G_CALLBACK (custom_icon_file_chooser_response_cb), window); + gtk_widget_show (dialog); +} + +static void +fm_properties_window_class_init (FMPropertiesWindowClass *class) +{ + GtkBindingSet *binding_set; + + G_OBJECT_CLASS (class)->finalize = real_finalize; + GTK_OBJECT_CLASS (class)->destroy = real_destroy; + GTK_DIALOG_CLASS (class)->response = real_response; + + binding_set = gtk_binding_set_by_class (class); + gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, + "close", 0); +} + +static void +fm_properties_window_init (FMPropertiesWindow *window) +{ + window->details = g_new0 (FMPropertiesWindowDetails, 1); +} diff --git a/src/file-manager/fm-properties-window.h b/src/file-manager/fm-properties-window.h new file mode 100644 index 00000000..6044e8b9 --- /dev/null +++ b/src/file-manager/fm-properties-window.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-properties-window.h - interface for window that lets user modify + icon properties + + Copyright (C) 2000 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Darin Adler <[email protected]> +*/ + +#ifndef FM_PROPERTIES_WINDOW_H +#define FM_PROPERTIES_WINDOW_H + +#include <gtk/gtk.h> +#include <libcaja-private/caja-file.h> + +typedef struct FMPropertiesWindow FMPropertiesWindow; + +#define FM_TYPE_PROPERTIES_WINDOW fm_properties_window_get_type() +#define FM_PROPERTIES_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindow)) +#define FM_PROPERTIES_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindowClass)) +#define FM_IS_PROPERTIES_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_PROPERTIES_WINDOW)) +#define FM_IS_PROPERTIES_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_PROPERTIES_WINDOW)) +#define FM_PROPERTIES_WINDOW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindowClass)) + +typedef struct FMPropertiesWindowDetails FMPropertiesWindowDetails; + +struct FMPropertiesWindow +{ + GtkDialog window; + FMPropertiesWindowDetails *details; +}; + +struct FMPropertiesWindowClass +{ + GtkDialogClass parent_class; + + /* Keybinding signals */ + void (* close) (FMPropertiesWindow *window); +}; + +typedef struct FMPropertiesWindowClass FMPropertiesWindowClass; + +GType fm_properties_window_get_type (void); + +void fm_properties_window_present (GList *files, + GtkWidget *parent_widget); + +#endif /* FM_PROPERTIES_WINDOW_H */ diff --git a/src/file-manager/fm-tree-model.c b/src/file-manager/fm-tree-model.c new file mode 100644 index 00000000..6356db90 --- /dev/null +++ b/src/file-manager/fm-tree-model.c @@ -0,0 +1,2152 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Bent Spoon Software + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Anders Carlsson <[email protected]> + * Darin Adler <[email protected]> + */ + +/* fm-tree-model.c - model for the tree view */ + +#include <config.h> +#include "fm-tree-model.h" + +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file.h> +#include <gtk/gtk.h> +#include <string.h> + +enum +{ + ROW_LOADED, + LAST_SIGNAL +}; + +static guint tree_model_signals[LAST_SIGNAL] = { 0 }; + +typedef gboolean (* FilePredicate) (CajaFile *); + +/* The user_data of the GtkTreeIter is the TreeNode pointer. + * It's NULL for the dummy node. If it's NULL, then user_data2 + * is the TreeNode pointer to the parent. + */ + +typedef struct TreeNode TreeNode; +typedef struct FMTreeModelRoot FMTreeModelRoot; + +struct TreeNode +{ + /* part of this node for the file itself */ + int ref_count; + + CajaFile *file; + char *display_name; + GIcon *icon; + GMount *mount; + GdkPixbuf *closed_pixbuf; + GdkPixbuf *open_pixbuf; + GdkPixbuf *emblem_pixbuf; + + FMTreeModelRoot *root; + + TreeNode *parent; + TreeNode *next; + TreeNode *prev; + + /* part of the node used only for directories */ + int dummy_child_ref_count; + int all_children_ref_count; + + CajaDirectory *directory; + guint done_loading_id; + guint files_added_id; + guint files_changed_id; + + TreeNode *first_child; + + /* misc. flags */ + guint done_loading : 1; + guint force_has_dummy : 1; + guint inserted : 1; +}; + +struct FMTreeModelDetails +{ + int stamp; + + TreeNode *root_node; + + guint monitoring_update_idle_id; + + gboolean show_hidden_files; + gboolean show_backup_files; + gboolean show_only_directories; + + GList *highlighted_files; +}; + +struct FMTreeModelRoot +{ + FMTreeModel *model; + + /* separate hash table for each root node needed */ + GHashTable *file_to_node_map; + + TreeNode *root_node; +}; + +typedef struct +{ + CajaDirectory *directory; + FMTreeModel *model; +} DoneLoadingParameters; + +static void fm_tree_model_tree_model_init (GtkTreeModelIface *iface); +static void schedule_monitoring_update (FMTreeModel *model); +static void destroy_node_without_reporting (FMTreeModel *model, + TreeNode *node); +static void report_node_contents_changed (FMTreeModel *model, + TreeNode *node); + +G_DEFINE_TYPE_WITH_CODE (FMTreeModel, fm_tree_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, + fm_tree_model_tree_model_init)); + +static GtkTreeModelFlags +fm_tree_model_get_flags (GtkTreeModel *tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static void +object_unref_if_not_NULL (gpointer object) +{ + if (object == NULL) + { + return; + } + g_object_unref (object); +} + +static FMTreeModelRoot * +tree_model_root_new (FMTreeModel *model) +{ + FMTreeModelRoot *root; + + root = g_new0 (FMTreeModelRoot, 1); + root->model = model; + root->file_to_node_map = g_hash_table_new (NULL, NULL); + + return root; +} + +static TreeNode * +tree_node_new (CajaFile *file, FMTreeModelRoot *root) +{ + TreeNode *node; + + node = g_new0 (TreeNode, 1); + node->file = caja_file_ref (file); + node->root = root; + return node; +} + +static void +tree_node_unparent (FMTreeModel *model, TreeNode *node) +{ + TreeNode *parent, *next, *prev; + + parent = node->parent; + next = node->next; + prev = node->prev; + + if (parent == NULL && + node == model->details->root_node) + { + /* it's the first root node -> if there is a next then let it be the first root node */ + model->details->root_node = next; + } + + if (next != NULL) + { + next->prev = prev; + } + if (prev == NULL && parent != NULL) + { + g_assert (parent->first_child == node); + parent->first_child = next; + } + else if (prev != NULL) + { + prev->next = next; + } + + node->parent = NULL; + node->next = NULL; + node->prev = NULL; + node->root = NULL; +} + +static void +tree_node_destroy (FMTreeModel *model, TreeNode *node) +{ + g_assert (node->first_child == NULL); + g_assert (node->ref_count == 0); + + tree_node_unparent (model, node); + + g_object_unref (node->file); + g_free (node->display_name); + object_unref_if_not_NULL (node->icon); + object_unref_if_not_NULL (node->closed_pixbuf); + object_unref_if_not_NULL (node->open_pixbuf); + object_unref_if_not_NULL (node->emblem_pixbuf); + + g_assert (node->done_loading_id == 0); + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + caja_directory_unref (node->directory); + + g_free (node); +} + +static void +tree_node_parent (TreeNode *node, TreeNode *parent) +{ + TreeNode *first_child; + + g_assert (parent != NULL); + g_assert (node->parent == NULL); + g_assert (node->prev == NULL); + g_assert (node->next == NULL); + + first_child = parent->first_child; + + node->parent = parent; + node->root = parent->root; + node->next = first_child; + + if (first_child != NULL) + { + g_assert (first_child->prev == NULL); + first_child->prev = node; + } + + parent->first_child = node; +} + +static GdkPixbuf * +get_menu_icon (GIcon *icon) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf; + int size; + + size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + info = caja_icon_info_lookup (icon, size); + pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); + g_object_unref (info); + + return pixbuf; +} + +static GdkPixbuf * +get_menu_icon_for_file (TreeNode *node, + CajaFile *file, + CajaFileIconFlags flags) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf, *retval; + gboolean highlight; + int size; + FMTreeModel *model; + + size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + info = caja_file_get_icon (file, size, flags); + retval = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); + model = node->root->model; + + highlight = (g_list_find_custom (model->details->highlighted_files, + file, (GCompareFunc) caja_file_compare_location) != NULL); + + if (highlight) + { + pixbuf = eel_gdk_pixbuf_render (retval, 1, 255, 255, 0, 0); + + if (pixbuf != NULL) + { + g_object_unref (retval); + retval = pixbuf; + } + } + + g_object_unref (info); + + return retval; +} + +static GdkPixbuf * +tree_node_get_pixbuf (TreeNode *node, + CajaFileIconFlags flags) +{ + if (node->parent == NULL) + { + return get_menu_icon (node->icon); + } + return get_menu_icon_for_file (node, node->file, flags); +} + +static gboolean +tree_node_update_pixbuf (TreeNode *node, + GdkPixbuf **pixbuf_storage, + CajaFileIconFlags flags) +{ + GdkPixbuf *pixbuf; + + if (*pixbuf_storage == NULL) + { + return FALSE; + } + pixbuf = tree_node_get_pixbuf (node, flags); + if (pixbuf == *pixbuf_storage) + { + g_object_unref (pixbuf); + return FALSE; + } + g_object_unref (*pixbuf_storage); + *pixbuf_storage = pixbuf; + return TRUE; +} + +static gboolean +tree_node_update_closed_pixbuf (TreeNode *node) +{ + return tree_node_update_pixbuf (node, &node->closed_pixbuf, 0); +} + +static gboolean +tree_node_update_open_pixbuf (TreeNode *node) +{ + return tree_node_update_pixbuf (node, &node->open_pixbuf, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER); +} + +static GdkPixbuf * +tree_node_get_emblem_pixbuf_internal (TreeNode *node) +{ + GdkPixbuf *pixbuf; + GList *emblem_pixbufs; + char *emblems_to_ignore[3]; + int i; + + i = 0; + emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH; + + if (node->parent && node->parent->file) + { + if (!caja_file_can_write (node->parent->file)) + { + emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE; + } + } + + emblems_to_ignore[i++] = NULL; + + emblem_pixbufs = caja_file_get_emblem_pixbufs (node->file, + caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU), + TRUE, + emblems_to_ignore); + + + if (emblem_pixbufs != NULL) + { + pixbuf = g_object_ref (emblem_pixbufs->data); + } + else + { + pixbuf = NULL; + } + + eel_gdk_pixbuf_list_free (emblem_pixbufs); + + return pixbuf; +} + +static gboolean +tree_node_update_emblem_pixbuf (TreeNode *node) +{ + GdkPixbuf *pixbuf; + + if (node->emblem_pixbuf == NULL) + { + return FALSE; + } + pixbuf = tree_node_get_emblem_pixbuf_internal (node); + if (pixbuf == node->emblem_pixbuf) + { + g_object_unref (pixbuf); + return FALSE; + } + g_object_unref (node->emblem_pixbuf); + node->emblem_pixbuf = pixbuf; + return TRUE; +} + +static gboolean +tree_node_update_display_name (TreeNode *node) +{ + char *display_name; + + if (node->display_name == NULL) + { + return FALSE; + } + /* don't update root node display names */ + if (node->parent == NULL) + { + return FALSE; + } + display_name = caja_file_get_display_name (node->file); + if (strcmp (display_name, node->display_name) == 0) + { + g_free (display_name); + return FALSE; + } + g_free (node->display_name); + node->display_name = NULL; + return TRUE; +} + +static GdkPixbuf * +tree_node_get_closed_pixbuf (TreeNode *node) +{ + if (node->closed_pixbuf == NULL) + { + node->closed_pixbuf = tree_node_get_pixbuf (node, 0); + } + return node->closed_pixbuf; +} + +static GdkPixbuf * +tree_node_get_open_pixbuf (TreeNode *node) +{ + if (node->open_pixbuf == NULL) + { + node->open_pixbuf = tree_node_get_pixbuf (node, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER); + } + return node->open_pixbuf; +} + +static GdkPixbuf * +tree_node_get_emblem_pixbuf (TreeNode *node) +{ + if (node->emblem_pixbuf == NULL) + { + node->emblem_pixbuf = tree_node_get_emblem_pixbuf_internal (node); + } + return node->emblem_pixbuf; +} + +static const char * +tree_node_get_display_name (TreeNode *node) +{ + if (node->display_name == NULL) + { + node->display_name = caja_file_get_display_name (node->file); + } + return node->display_name; +} + +static gboolean +tree_node_has_dummy_child (TreeNode *node) +{ + return (node->directory != NULL + && (!node->done_loading + || node->first_child == NULL + || node->force_has_dummy)) || + /* Roots always have dummy nodes if directory isn't loaded yet */ + (node->directory == NULL && node->parent == NULL); +} + +static int +tree_node_get_child_index (TreeNode *parent, TreeNode *child) +{ + int i; + TreeNode *node; + + if (child == NULL) + { + g_assert (tree_node_has_dummy_child (parent)); + return 0; + } + + i = tree_node_has_dummy_child (parent) ? 1 : 0; + for (node = parent->first_child; node != NULL; node = node->next, i++) + { + if (child == node) + { + return i; + } + } + + g_assert_not_reached (); + return 0; +} + +static gboolean +make_iter_invalid (GtkTreeIter *iter) +{ + iter->stamp = 0; + iter->user_data = NULL; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + return FALSE; +} + +static gboolean +make_iter_for_node (TreeNode *node, GtkTreeIter *iter, int stamp) +{ + if (node == NULL) + { + return make_iter_invalid (iter); + } + iter->stamp = stamp; + iter->user_data = node; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + return TRUE; +} + +static gboolean +make_iter_for_dummy_row (TreeNode *parent, GtkTreeIter *iter, int stamp) +{ + g_assert (tree_node_has_dummy_child (parent)); + g_assert (parent != NULL); + iter->stamp = stamp; + iter->user_data = NULL; + iter->user_data2 = parent; + iter->user_data3 = NULL; + return TRUE; +} + +static TreeNode * +get_node_from_file (FMTreeModelRoot *root, CajaFile *file) +{ + return g_hash_table_lookup (root->file_to_node_map, file); +} + +static TreeNode * +get_parent_node_from_file (FMTreeModelRoot *root, CajaFile *file) +{ + CajaFile *parent_file; + TreeNode *parent_node; + + parent_file = caja_file_get_parent (file); + parent_node = get_node_from_file (root, parent_file); + caja_file_unref (parent_file); + return parent_node; +} + +static TreeNode * +create_node_for_file (FMTreeModelRoot *root, CajaFile *file) +{ + TreeNode *node; + + g_assert (get_node_from_file (root, file) == NULL); + node = tree_node_new (file, root); + g_hash_table_insert (root->file_to_node_map, node->file, node); + return node; +} + +#ifdef LOG_REF_COUNTS + +static char * +get_node_uri (GtkTreeIter *iter) +{ + TreeNode *node, *parent; + char *parent_uri, *node_uri; + + node = iter->user_data; + if (node != NULL) + { + return caja_file_get_uri (node->file); + } + + parent = iter->user_data2; + parent_uri = caja_file_get_uri (parent->file); + node_uri = g_strconcat (parent_uri, " -- DUMMY", NULL); + g_free (parent_uri); + return node_uri; +} + +#endif + +static void +decrement_ref_count (FMTreeModel *model, TreeNode *node, int count) +{ + node->all_children_ref_count -= count; + if (node->all_children_ref_count == 0) + { + schedule_monitoring_update (model); + } +} + +static void +abandon_node_ref_count (FMTreeModel *model, TreeNode *node) +{ + if (node->parent != NULL) + { + decrement_ref_count (model, node->parent, node->ref_count); +#ifdef LOG_REF_COUNTS + if (node->ref_count != 0) + { + char *uri; + + uri = caja_file_get_uri (node->file); + g_message ("abandoning %d ref of %s, count is now %d", + node->ref_count, uri, node->parent->all_children_ref_count); + g_free (uri); + } +#endif + } + node->ref_count = 0; +} + +static void +abandon_dummy_row_ref_count (FMTreeModel *model, TreeNode *node) +{ + decrement_ref_count (model, node, node->dummy_child_ref_count); + if (node->dummy_child_ref_count != 0) + { +#ifdef LOG_REF_COUNTS + char *uri; + + uri = caja_file_get_uri (node->file); + g_message ("abandoning %d ref of %s -- DUMMY, count is now %d", + node->dummy_child_ref_count, uri, node->all_children_ref_count); + g_free (uri); +#endif + } + node->dummy_child_ref_count = 0; +} + +static void +report_row_inserted (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static void +report_row_contents_changed (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static void +report_row_has_child_toggled (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static GtkTreePath * +get_node_path (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + make_iter_for_node (node, &iter, model->details->stamp); + return gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +} + +static void +report_dummy_row_inserted (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + + if (!parent->inserted) + { + return; + } + make_iter_for_dummy_row (parent, &iter, model->details->stamp); + report_row_inserted (model, &iter); +} + +static void +report_dummy_row_deleted (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + GtkTreePath *path; + + if (parent->inserted) + { + make_iter_for_node (parent, &iter, model->details->stamp); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_path_append_index (path, 0); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + } + abandon_dummy_row_ref_count (model, parent); +} + +static void +report_node_inserted (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + make_iter_for_node (node, &iter, model->details->stamp); + report_row_inserted (model, &iter); + node->inserted = TRUE; + + if (tree_node_has_dummy_child (node)) + { + report_dummy_row_inserted (model, node); + } + + if (node->directory != NULL || + node->parent == NULL) + { + report_row_has_child_toggled (model, &iter); + } +} + +static void +report_node_contents_changed (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + if (!node->inserted) + { + return; + } + make_iter_for_node (node, &iter, model->details->stamp); + report_row_contents_changed (model, &iter); +} + +static void +report_node_has_child_toggled (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + if (!node->inserted) + { + return; + } + make_iter_for_node (node, &iter, model->details->stamp); + report_row_has_child_toggled (model, &iter); +} + +static void +report_dummy_row_contents_changed (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + + if (!parent->inserted) + { + return; + } + make_iter_for_dummy_row (parent, &iter, model->details->stamp); + report_row_contents_changed (model, &iter); +} + +static void +stop_monitoring_directory (FMTreeModel *model, TreeNode *node) +{ + CajaDirectory *directory; + + if (node->done_loading_id == 0) + { + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + return; + } + + directory = node->directory; + + g_signal_handler_disconnect (node->directory, node->done_loading_id); + g_signal_handler_disconnect (node->directory, node->files_added_id); + g_signal_handler_disconnect (node->directory, node->files_changed_id); + + node->done_loading_id = 0; + node->files_added_id = 0; + node->files_changed_id = 0; + + caja_directory_file_monitor_remove (node->directory, model); +} + +static void +destroy_children_without_reporting (FMTreeModel *model, TreeNode *parent) +{ + while (parent->first_child != NULL) + { + destroy_node_without_reporting (model, parent->first_child); + } +} + +static void +destroy_node_without_reporting (FMTreeModel *model, TreeNode *node) +{ + abandon_node_ref_count (model, node); + stop_monitoring_directory (model, node); + node->inserted = FALSE; + destroy_children_without_reporting (model, node); + g_hash_table_remove (node->root->file_to_node_map, node->file); + tree_node_destroy (model, node); +} + +static void +destroy_node (FMTreeModel *model, TreeNode *node) +{ + TreeNode *parent; + gboolean parent_had_dummy_child; + GtkTreePath *path; + + parent = node->parent; + parent_had_dummy_child = tree_node_has_dummy_child (parent); + + path = get_node_path (model, node); + + /* Report row_deleted before actually deleting */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + destroy_node_without_reporting (model, node); + + if (tree_node_has_dummy_child (parent)) + { + if (!parent_had_dummy_child) + { + report_dummy_row_inserted (model, parent); + } + } + else + { + g_assert (!parent_had_dummy_child); + } +} + +static void +destroy_children (FMTreeModel *model, TreeNode *parent) +{ + while (parent->first_child != NULL) + { + destroy_node (model, parent->first_child); + } +} + +static void +destroy_children_by_function (FMTreeModel *model, TreeNode *parent, FilePredicate f) +{ + TreeNode *child, *next; + + for (child = parent->first_child; child != NULL; child = next) + { + next = child->next; + if (f (child->file)) + { + destroy_node (model, child); + } + else + { + destroy_children_by_function (model, child, f); + } + } +} + +static void +destroy_by_function (FMTreeModel *model, FilePredicate f) +{ + TreeNode *node; + for (node = model->details->root_node; node != NULL; node = node->next) + { + destroy_children_by_function (model, node, f); + } +} + +static gboolean +update_node_without_reporting (FMTreeModel *model, TreeNode *node) +{ + gboolean changed; + + changed = FALSE; + + if (node->directory == NULL && + (caja_file_is_directory (node->file) || node->parent == NULL)) + { + node->directory = caja_directory_get_for_file (node->file); + } + else if (node->directory != NULL && + !(caja_file_is_directory (node->file) || node->parent == NULL)) + { + stop_monitoring_directory (model, node); + destroy_children (model, node); + caja_directory_unref (node->directory); + node->directory = NULL; + } + + changed |= tree_node_update_display_name (node); + changed |= tree_node_update_closed_pixbuf (node); + changed |= tree_node_update_open_pixbuf (node); + changed |= tree_node_update_emblem_pixbuf (node); + + return changed; +} + +static void +insert_node (FMTreeModel *model, TreeNode *parent, TreeNode *node) +{ + gboolean parent_empty; + + parent_empty = parent->first_child == NULL; + if (parent_empty) + { + /* Make sure the dummy lives as we insert the new row */ + parent->force_has_dummy = TRUE; + } + + tree_node_parent (node, parent); + + update_node_without_reporting (model, node); + report_node_inserted (model, node); + + if (parent_empty) + { + parent->force_has_dummy = FALSE; + if (!tree_node_has_dummy_child (parent)) + { + /* Temporarily set this back so that row_deleted is + * sent before actually removing the dummy child */ + parent->force_has_dummy = TRUE; + report_dummy_row_deleted (model, parent); + parent->force_has_dummy = FALSE; + } + } +} + +static void +reparent_node (FMTreeModel *model, TreeNode *node) +{ + GtkTreePath *path; + TreeNode *new_parent; + + new_parent = get_parent_node_from_file (node->root, node->file); + if (new_parent == NULL || new_parent->directory == NULL) + { + destroy_node (model, node); + return; + } + + path = get_node_path (model, node); + + /* Report row_deleted before actually deleting */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + abandon_node_ref_count (model, node); + tree_node_unparent (model, node); + + insert_node (model, new_parent, node); +} + +static gboolean +should_show_file (FMTreeModel *model, CajaFile *file) +{ + gboolean should; + TreeNode *node; + + should = caja_file_should_show (file, + model->details->show_hidden_files, + model->details->show_backup_files, + TRUE); + + if (should + && model->details->show_only_directories + &&! caja_file_is_directory (file)) + { + should = FALSE; + } + + if (should && caja_file_is_gone (file)) + { + should = FALSE; + } + + for (node = model->details->root_node; node != NULL; node = node->next) + { + if (!should && node != NULL && file == node->file) + { + should = TRUE; + } + } + + return should; +} + +static void +update_node (FMTreeModel *model, TreeNode *node) +{ + gboolean had_dummy_child, has_dummy_child; + gboolean had_directory, has_directory; + gboolean changed; + + if (!should_show_file (model, node->file)) + { + destroy_node (model, node); + return; + } + + if (node->parent != NULL && node->parent->directory != NULL + && !caja_directory_contains_file (node->parent->directory, node->file)) + { + reparent_node (model, node); + return; + } + + had_dummy_child = tree_node_has_dummy_child (node); + had_directory = node->directory != NULL; + + changed = update_node_without_reporting (model, node); + + has_dummy_child = tree_node_has_dummy_child (node); + has_directory = node->directory != NULL; + + if (had_dummy_child != has_dummy_child) + { + if (has_dummy_child) + { + report_dummy_row_inserted (model, node); + } + else + { + /* Temporarily set this back so that row_deleted is + * sent before actually removing the dummy child */ + node->force_has_dummy = TRUE; + report_dummy_row_deleted (model, node); + node->force_has_dummy = FALSE; + } + } + if (had_directory != has_directory) + { + report_node_has_child_toggled (model, node); + } + + if (changed) + { + report_node_contents_changed (model, node); + } +} + +static void +process_file_change (FMTreeModelRoot *root, + CajaFile *file) +{ + TreeNode *node, *parent; + + node = get_node_from_file (root, file); + if (node != NULL) + { + update_node (root->model, node); + return; + } + + if (!should_show_file (root->model, file)) + { + return; + } + + parent = get_parent_node_from_file (root, file); + if (parent == NULL) + { + return; + } + + insert_node (root->model, parent, create_node_for_file (root, file)); +} + +static void +files_changed_callback (CajaDirectory *directory, + GList *changed_files, + gpointer callback_data) +{ + FMTreeModelRoot *root; + GList *node; + + root = (FMTreeModelRoot *) (callback_data); + + for (node = changed_files; node != NULL; node = node->next) + { + process_file_change (root, CAJA_FILE (node->data)); + } +} + +static void +set_done_loading (FMTreeModel *model, TreeNode *node, gboolean done_loading) +{ + gboolean had_dummy; + + if (node == NULL || node->done_loading == done_loading) + { + return; + } + + had_dummy = tree_node_has_dummy_child (node); + + node->done_loading = done_loading; + + if (tree_node_has_dummy_child (node)) + { + if (had_dummy) + { + report_dummy_row_contents_changed (model, node); + } + else + { + report_dummy_row_inserted (model, node); + } + } + else + { + if (had_dummy) + { + /* Temporarily set this back so that row_deleted is + * sent before actually removing the dummy child */ + node->force_has_dummy = TRUE; + report_dummy_row_deleted (model, node); + node->force_has_dummy = FALSE; + } + else + { + g_assert_not_reached (); + } + } +} + +static void +done_loading_callback (CajaDirectory *directory, + FMTreeModelRoot *root) +{ + CajaFile *file; + TreeNode *node; + GtkTreeIter iter; + + file = caja_directory_get_corresponding_file (directory); + node = get_node_from_file (root, file); + if (node == NULL) + { + /* This can happen for non-existing files as tree roots, + * since the directory <-> file object relation gets + * broken due to caja_directory_remove_file() + * getting called when i/o fails. + */ + return; + } + set_done_loading (root->model, node, TRUE); + caja_file_unref (file); + + make_iter_for_node (node, &iter, root->model->details->stamp); + g_signal_emit (root->model, + tree_model_signals[ROW_LOADED], 0, + &iter); +} + +static CajaFileAttributes +get_tree_monitor_attributes (void) +{ + CajaFileAttributes attributes; + + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO; + + return attributes; +} + +static void +start_monitoring_directory (FMTreeModel *model, TreeNode *node) +{ + CajaDirectory *directory; + CajaFileAttributes attributes; + + if (node->done_loading_id != 0) + { + return; + } + + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + + directory = node->directory; + + node->done_loading_id = g_signal_connect + (directory, "done_loading", + G_CALLBACK (done_loading_callback), node->root); + node->files_added_id = g_signal_connect + (directory, "files_added", + G_CALLBACK (files_changed_callback), node->root); + node->files_changed_id = g_signal_connect + (directory, "files_changed", + G_CALLBACK (files_changed_callback), node->root); + + set_done_loading (model, node, caja_directory_are_all_files_seen (directory)); + + attributes = get_tree_monitor_attributes (); + caja_directory_file_monitor_add (directory, model, + model->details->show_hidden_files, + model->details->show_backup_files, + attributes, files_changed_callback, node->root); +} + +static int +fm_tree_model_get_n_columns (GtkTreeModel *model) +{ + return FM_TREE_MODEL_NUM_COLUMNS; +} + +static GType +fm_tree_model_get_column_type (GtkTreeModel *model, int index) +{ + switch (index) + { + case FM_TREE_MODEL_DISPLAY_NAME_COLUMN: + return G_TYPE_STRING; + case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_TREE_MODEL_FONT_STYLE_COLUMN: + return PANGO_TYPE_STYLE; + default: + g_assert_not_reached (); + } + + return G_TYPE_INVALID; +} + +static gboolean +iter_is_valid (FMTreeModel *model, const GtkTreeIter *iter) +{ + TreeNode *node, *parent; + + if (iter->stamp != model->details->stamp) + { + return FALSE; + } + + node = iter->user_data; + parent = iter->user_data2; + if (node == NULL) + { + if (parent != NULL) + { + if (!CAJA_IS_FILE (parent->file)) + { + return FALSE; + } + if (!tree_node_has_dummy_child (parent)) + { + return FALSE; + } + } + } + else + { + if (!CAJA_IS_FILE (node->file)) + { + return FALSE; + } + if (parent != NULL) + { + return FALSE; + } + } + if (iter->user_data3 != NULL) + { + return FALSE; + } + return TRUE; +} + +static gboolean +fm_tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path) +{ + int *indices; + GtkTreeIter parent; + int depth, i; + + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + + if (! gtk_tree_model_iter_nth_child (model, iter, NULL, indices[0])) + { + return FALSE; + } + + for (i = 1; i < depth; i++) + { + parent = *iter; + + if (! gtk_tree_model_iter_nth_child (model, iter, &parent, indices[i])) + { + return FALSE; + } + } + + return TRUE; +} + +static GtkTreePath * +fm_tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter) +{ + FMTreeModel *tree_model; + TreeNode *node, *parent, *cnode; + GtkTreePath *path; + GtkTreeIter parent_iter; + int i; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL); + tree_model = FM_TREE_MODEL (model); + g_return_val_if_fail (iter_is_valid (tree_model, iter), NULL); + + node = iter->user_data; + if (node == NULL) + { + parent = iter->user_data2; + if (parent == NULL) + { + return gtk_tree_path_new (); + } + } + else + { + parent = node->parent; + if (parent == NULL) + { + i = 0; + for (cnode = tree_model->details->root_node; cnode != node; cnode = cnode->next) + { + i++; + } + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, i); + return path; + } + } + + parent_iter.stamp = iter->stamp; + parent_iter.user_data = parent; + parent_iter.user_data2 = NULL; + parent_iter.user_data3 = NULL; + + path = fm_tree_model_get_path (model, &parent_iter); + + gtk_tree_path_append_index (path, tree_node_get_child_index (parent, node)); + + return path; +} + +static void +fm_tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter, int column, GValue *value) +{ + TreeNode *node, *parent; + FMTreeModel *fm_model; + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + fm_model = FM_TREE_MODEL (model); + node = iter->user_data; + + switch (column) + { + case FM_TREE_MODEL_DISPLAY_NAME_COLUMN: + g_value_init (value, G_TYPE_STRING); + if (node == NULL) + { + parent = iter->user_data2; + g_value_set_static_string (value, parent->done_loading + ? _("(Empty)") : _("Loading...")); + } + else + { + g_value_set_string (value, tree_node_get_display_name (node)); + } + break; + case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + g_value_set_object (value, node == NULL ? NULL : tree_node_get_closed_pixbuf (node)); + break; + case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + g_value_set_object (value, node == NULL ? NULL : tree_node_get_open_pixbuf (node)); + break; + case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + g_value_set_object (value, node == NULL ? NULL : tree_node_get_emblem_pixbuf (node)); + break; + case FM_TREE_MODEL_FONT_STYLE_COLUMN: + g_value_init (value, PANGO_TYPE_STYLE); + if (node == NULL) + { + g_value_set_enum (value, PANGO_STYLE_ITALIC); + } + else + { + g_value_set_enum (value, PANGO_STYLE_NORMAL); + } + break; + default: + g_assert_not_reached (); + } +} + +static gboolean +fm_tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent, *next; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + node = iter->user_data; + + if (node == NULL) + { + parent = iter->user_data2; + next = parent->first_child; + } + else + { + next = node->next; + } + + return make_iter_for_node (next, iter, iter->stamp); +} + +static gboolean +fm_tree_model_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent_iter) +{ + TreeNode *parent; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE); + + parent = parent_iter->user_data; + if (parent == NULL) + { + return make_iter_invalid (iter); + } + + if (tree_node_has_dummy_child (parent)) + { + return make_iter_for_dummy_row (parent, iter, parent_iter->stamp); + } + return make_iter_for_node (parent->first_child, iter, parent_iter->stamp); +} + +static gboolean +fm_tree_model_iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child_iter) +{ + TreeNode *child, *parent; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), child_iter), FALSE); + + child = child_iter->user_data; + + if (child == NULL) + { + parent = child_iter->user_data2; + } + else + { + parent = child->parent; + } + + return make_iter_for_node (parent, iter, child_iter->stamp); +} + +static gboolean +fm_tree_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter) +{ + gboolean has_child; + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + node = iter->user_data; + + has_child = node != NULL && (node->directory != NULL || node->parent == NULL); + +#if 0 + g_warning ("Node '%s' %s", + node && node->file ? caja_file_get_uri (node->file) : "no name", + has_child ? "has child" : "no child"); +#endif + + return has_child; +} + +static int +fm_tree_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter) +{ + FMTreeModel *tree_model; + TreeNode *parent, *node; + int n; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter == NULL || iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + tree_model = FM_TREE_MODEL (model); + + if (iter == NULL) + { + return 1; + } + + parent = iter->user_data; + if (parent == NULL) + { + return 0; + } + + n = tree_node_has_dummy_child (parent) ? 1 : 0; + for (node = parent->first_child; node != NULL; node = node->next) + { + n++; + } + + return n; +} + +static gboolean +fm_tree_model_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter, + GtkTreeIter *parent_iter, int n) +{ + FMTreeModel *tree_model; + TreeNode *parent, *node; + int i; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (parent_iter == NULL + || iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE); + + tree_model = FM_TREE_MODEL (model); + + if (parent_iter == NULL) + { + node = tree_model->details->root_node; + for (i = 0; i < n && node != NULL; i++, node = node->next); + return make_iter_for_node (node, iter, + tree_model->details->stamp); + } + + parent = parent_iter->user_data; + if (parent == NULL) + { + return make_iter_invalid (iter); + } + + i = tree_node_has_dummy_child (parent) ? 1 : 0; + if (n == 0 && i == 1) + { + return make_iter_for_dummy_row (parent, iter, parent_iter->stamp); + } + for (node = parent->first_child; i != n; i++, node = node->next) + { + if (node == NULL) + { + return make_iter_invalid (iter); + } + } + + return make_iter_for_node (node, iter, parent_iter->stamp); +} + +static void +update_monitoring (FMTreeModel *model, TreeNode *node) +{ + TreeNode *child; + + if (node->all_children_ref_count == 0) + { + stop_monitoring_directory (model, node); + destroy_children (model, node); + } + else + { + for (child = node->first_child; child != NULL; child = child->next) + { + update_monitoring (model, child); + } + start_monitoring_directory (model, node); + } +} + +static gboolean +update_monitoring_idle_callback (gpointer callback_data) +{ + FMTreeModel *model; + TreeNode *node; + + model = FM_TREE_MODEL (callback_data); + model->details->monitoring_update_idle_id = 0; + for (node = model->details->root_node; node != NULL; node = node->next) + { + update_monitoring (model, node); + } + return FALSE; +} + +static void +schedule_monitoring_update (FMTreeModel *model) +{ + if (model->details->monitoring_update_idle_id == 0) + { + model->details->monitoring_update_idle_id = + g_idle_add (update_monitoring_idle_callback, model); + } +} + +static void +stop_monitoring_directory_and_children (FMTreeModel *model, TreeNode *node) +{ + TreeNode *child; + + stop_monitoring_directory (model, node); + for (child = node->first_child; child != NULL; child = child->next) + { + stop_monitoring_directory_and_children (model, child); + } +} + +static void +stop_monitoring (FMTreeModel *model) +{ + TreeNode *node; + + for (node = model->details->root_node; node != NULL; node = node->next) + { + stop_monitoring_directory_and_children (model, node); + } +} + +static void +fm_tree_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent; +#ifdef LOG_REF_COUNTS + char *uri; +#endif + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + node = iter->user_data; + if (node == NULL) + { + parent = iter->user_data2; + g_assert (parent->dummy_child_ref_count >= 0); + ++parent->dummy_child_ref_count; + } + else + { + parent = node->parent; + g_assert (node->ref_count >= 0); + ++node->ref_count; + } + + if (parent != NULL) + { + g_assert (parent->all_children_ref_count >= 0); + if (++parent->all_children_ref_count == 1) + { + if (parent->first_child == NULL) + { + parent->done_loading = FALSE; + } + schedule_monitoring_update (FM_TREE_MODEL (model)); + } +#ifdef LOG_REF_COUNTS + uri = get_node_uri (iter); + g_message ("ref of %s, count is now %d", + uri, parent->all_children_ref_count); + g_free (uri); +#endif + } +} + +static void +fm_tree_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent; +#ifdef LOG_REF_COUNTS + char *uri; +#endif + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + node = iter->user_data; + if (node == NULL) + { + parent = iter->user_data2; + g_assert (parent->dummy_child_ref_count > 0); + --parent->dummy_child_ref_count; + } + else + { + parent = node->parent; + g_assert (node->ref_count > 0); + --node->ref_count; + } + + if (parent != NULL) + { + g_assert (parent->all_children_ref_count > 0); +#ifdef LOG_REF_COUNTS + uri = get_node_uri (iter); + g_message ("unref of %s, count is now %d", + uri, parent->all_children_ref_count - 1); + g_free (uri); +#endif + if (--parent->all_children_ref_count == 0) + { + schedule_monitoring_update (FM_TREE_MODEL (model)); + } + } +} + +void +fm_tree_model_add_root_uri (FMTreeModel *model, const char *root_uri, const char *display_name, GIcon *icon, GMount *mount) +{ + CajaFile *file; + TreeNode *node, *cnode; + FMTreeModelRoot *newroot; + + file = caja_file_get_by_uri (root_uri); + + newroot = tree_model_root_new (model); + node = create_node_for_file (newroot, file); + node->display_name = g_strdup (display_name); + node->icon = g_object_ref (icon); + if (mount) + { + node->mount = g_object_ref (mount); + } + newroot->root_node = node; + node->parent = NULL; + if (model->details->root_node == NULL) + { + model->details->root_node = node; + } + else + { + /* append it */ + for (cnode = model->details->root_node; cnode->next != NULL; cnode = cnode->next); + cnode->next = node; + node->prev = cnode; + } + + caja_file_unref (file); + + update_node_without_reporting (model, node); + report_node_inserted (model, node); +} + +GMount * +fm_tree_model_get_mount_for_root_node_file (FMTreeModel *model, CajaFile *file) +{ + TreeNode *node; + + for (node = model->details->root_node; node != NULL; node = node->next) + { + if (file == node->file) + { + break; + } + } + + if (node) + { + return node->mount; + } + + return NULL; +} + +void +fm_tree_model_remove_root_uri (FMTreeModel *model, const char *uri) +{ + TreeNode *node; + GtkTreePath *path; + FMTreeModelRoot *root; + CajaFile *file; + + file = caja_file_get_by_uri (uri); + for (node = model->details->root_node; node != NULL; node = node->next) + { + if (file == node->file) + { + break; + } + } + caja_file_unref (file); + + if (node) + { + /* remove the node */ + + if (node->mount) + { + g_object_unref (node->mount); + node->mount = NULL; + } + + caja_file_monitor_remove (node->file, model); + path = get_node_path (model, node); + + /* Report row_deleted before actually deleting */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + if (node->prev) + { + node->prev->next = node->next; + } + if (node->next) + { + node->next->prev = node->prev; + } + if (node == model->details->root_node) + { + model->details->root_node = node->next; + } + + /* destroy the root identifier */ + root = node->root; + destroy_node_without_reporting (model, node); + g_hash_table_destroy (root->file_to_node_map); + g_free (root); + } +} + +FMTreeModel * +fm_tree_model_new (void) +{ + FMTreeModel *model; + + model = g_object_new (FM_TYPE_TREE_MODEL, NULL); + + return model; +} + +void +fm_tree_model_set_show_hidden_files (FMTreeModel *model, + gboolean show_hidden_files) +{ + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (show_hidden_files == FALSE || show_hidden_files == TRUE); + + show_hidden_files = show_hidden_files != FALSE; + if (model->details->show_hidden_files == show_hidden_files) + { + return; + } + model->details->show_hidden_files = show_hidden_files; + model->details->show_backup_files = show_hidden_files; + stop_monitoring (model); + if (!show_hidden_files) + { + destroy_by_function (model, caja_file_is_hidden_file); + } + schedule_monitoring_update (model); +} + +static gboolean +file_is_not_directory (CajaFile *file) +{ + return !caja_file_is_directory (file); +} + +void +fm_tree_model_set_show_only_directories (FMTreeModel *model, + gboolean show_only_directories) +{ + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (show_only_directories == FALSE || show_only_directories == TRUE); + + show_only_directories = show_only_directories != FALSE; + if (model->details->show_only_directories == show_only_directories) + { + return; + } + model->details->show_only_directories = show_only_directories; + stop_monitoring (model); + if (show_only_directories) + { + destroy_by_function (model, file_is_not_directory); + } + schedule_monitoring_update (model); +} + +CajaFile * +fm_tree_model_iter_get_file (FMTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), NULL); + + node = iter->user_data; + return node == NULL ? NULL : caja_file_ref (node->file); +} + +/* This is used to work around some sort order stability problems + with gtktreemodelsort */ +int +fm_tree_model_iter_compare_roots (FMTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b) +{ + TreeNode *a, *b, *n; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0); + g_return_val_if_fail (iter_is_valid (model, iter_a), 0); + g_return_val_if_fail (iter_is_valid (model, iter_b), 0); + + a = iter_a->user_data; + b = iter_b->user_data; + + g_assert (a != NULL && a->parent == NULL); + g_assert (b != NULL && b->parent == NULL); + + if (a == b) + { + return 0; + } + + for (n = model->details->root_node; n != NULL; n = n->next) + { + if (n == a) + { + return -1; + } + if (n == b) + { + return 1; + } + } + g_assert_not_reached (); +} + +gboolean +fm_tree_model_iter_is_root (FMTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0); + g_return_val_if_fail (iter_is_valid (model, iter), 0); + node = iter->user_data; + if (node == NULL) + { + return FALSE; + } + else + { + return (node->parent == NULL); + } +} + +gboolean +fm_tree_model_file_get_iter (FMTreeModel *model, + GtkTreeIter *iter, + CajaFile *file, + GtkTreeIter *current_iter) +{ + TreeNode *node, *root_node; + + if (current_iter != NULL && current_iter->user_data != NULL) + { + node = get_node_from_file (((TreeNode *) current_iter->user_data)->root, file); + return make_iter_for_node (node, iter, model->details->stamp); + } + + for (root_node = model->details->root_node; root_node != NULL; root_node = root_node->next) + { + node = get_node_from_file (root_node->root, file); + if (node != NULL) + { + return make_iter_for_node (node, iter, model->details->stamp); + } + } + return FALSE; +} + +static void +do_update_node (CajaFile *file, + FMTreeModel *model) +{ + TreeNode *root, *node = NULL; + + for (root = model->details->root_node; root != NULL; root = root->next) + { + node = get_node_from_file (root->root, file); + + if (node != NULL) + { + break; + } + } + + if (node == NULL) + { + return; + } + + update_node (model, node); +} + +void +fm_tree_model_set_highlight_for_files (FMTreeModel *model, + GList *files) +{ + GList *old_files; + + if (model->details->highlighted_files != NULL) + { + old_files = model->details->highlighted_files; + model->details->highlighted_files = NULL; + + g_list_foreach (old_files, + (GFunc) do_update_node, model); + + caja_file_list_free (old_files); + } + + if (files != NULL) + { + model->details->highlighted_files = + caja_file_list_copy (files); + g_list_foreach (model->details->highlighted_files, + (GFunc) do_update_node, model); + } +} + +static void +fm_tree_model_init (FMTreeModel *model) +{ + model->details = g_new0 (FMTreeModelDetails, 1); + + do + { + model->details->stamp = g_random_int (); + } + while (model->details->stamp == 0); +} + +static void +fm_tree_model_finalize (GObject *object) +{ + FMTreeModel *model; + TreeNode *root_node, *next_root; + FMTreeModelRoot *root; + + model = FM_TREE_MODEL (object); + + for (root_node = model->details->root_node; root_node != NULL; root_node = next_root) + { + next_root = root_node->next; + root = root_node->root; + destroy_node_without_reporting (model, root_node); + g_hash_table_destroy (root->file_to_node_map); + g_free (root); + } + + if (model->details->monitoring_update_idle_id != 0) + { + g_source_remove (model->details->monitoring_update_idle_id); + } + + if (model->details->highlighted_files != NULL) + { + caja_file_list_free (model->details->highlighted_files); + } + + g_free (model->details); + + G_OBJECT_CLASS (fm_tree_model_parent_class)->finalize (object); +} + +static void +fm_tree_model_class_init (FMTreeModelClass *class) +{ + G_OBJECT_CLASS (class)->finalize = fm_tree_model_finalize; + + tree_model_signals[ROW_LOADED] = + g_signal_new ("row_loaded", + FM_TYPE_TREE_MODEL, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMTreeModelClass, row_loaded), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_ITER); +} + +static void +fm_tree_model_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = fm_tree_model_get_flags; + iface->get_n_columns = fm_tree_model_get_n_columns; + iface->get_column_type = fm_tree_model_get_column_type; + iface->get_iter = fm_tree_model_get_iter; + iface->get_path = fm_tree_model_get_path; + iface->get_value = fm_tree_model_get_value; + iface->iter_next = fm_tree_model_iter_next; + iface->iter_children = fm_tree_model_iter_children; + iface->iter_has_child = fm_tree_model_iter_has_child; + iface->iter_n_children = fm_tree_model_iter_n_children; + iface->iter_nth_child = fm_tree_model_iter_nth_child; + iface->iter_parent = fm_tree_model_iter_parent; + iface->ref_node = fm_tree_model_ref_node; + iface->unref_node = fm_tree_model_unref_node; +} + + diff --git a/src/file-manager/fm-tree-model.h b/src/file-manager/fm-tree-model.h new file mode 100644 index 00000000..f74ce06c --- /dev/null +++ b/src/file-manager/fm-tree-model.h @@ -0,0 +1,104 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Bent Spoon Software + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Anders Carlsson <[email protected]> + */ + +/* fm-tree-model.h - Model for the tree view */ + +#ifndef FM_TREE_MODEL_H +#define FM_TREE_MODEL_H + +#include <glib-object.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <libcaja-private/caja-file.h> + +#define FM_TYPE_TREE_MODEL fm_tree_model_get_type() +#define FM_TREE_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_TREE_MODEL, FMTreeModel)) +#define FM_TREE_MODEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_MODEL, FMTreeModelClass)) +#define FM_IS_TREE_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_TREE_MODEL)) +#define FM_IS_TREE_MODEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_MODEL)) +#define FM_TREE_MODEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_TREE_MODEL, FMTreeModelClass)) + +enum +{ + FM_TREE_MODEL_DISPLAY_NAME_COLUMN, + FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + FM_TREE_MODEL_OPEN_PIXBUF_COLUMN, + FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN, + FM_TREE_MODEL_FONT_STYLE_COLUMN, + FM_TREE_MODEL_NUM_COLUMNS +}; + +typedef struct FMTreeModelDetails FMTreeModelDetails; + +typedef struct +{ + GObject parent; + FMTreeModelDetails *details; +} FMTreeModel; + +typedef struct +{ + GObjectClass parent_class; + + void (* row_loaded) (FMTreeModel *tree_model, + GtkTreeIter *iter); +} FMTreeModelClass; + +GType fm_tree_model_get_type (void); +FMTreeModel *fm_tree_model_new (void); +void fm_tree_model_set_show_hidden_files (FMTreeModel *model, + gboolean show_hidden_files); +void fm_tree_model_set_show_only_directories (FMTreeModel *model, + gboolean show_only_directories); +CajaFile * fm_tree_model_iter_get_file (FMTreeModel *model, + GtkTreeIter *iter); +void fm_tree_model_add_root_uri (FMTreeModel *model, + const char *root_uri, + const char *display_name, + GIcon *icon, + GMount *mount); +void fm_tree_model_remove_root_uri (FMTreeModel *model, + const char *root_uri); +gboolean fm_tree_model_iter_is_root (FMTreeModel *model, + GtkTreeIter *iter); +int fm_tree_model_iter_compare_roots (FMTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b); +gboolean fm_tree_model_file_get_iter (FMTreeModel *model, + GtkTreeIter *iter, + CajaFile *file, + GtkTreeIter *currentIter); + +GMount * fm_tree_model_get_mount_for_root_node_file +(FMTreeModel *model, + CajaFile *file); +void fm_tree_model_set_highlight_for_files (FMTreeModel *model, + GList *files); + +#endif /* FM_TREE_MODEL_H */ diff --git a/src/file-manager/fm-tree-view.c b/src/file-manager/fm-tree-view.c new file mode 100644 index 00000000..2a6cbd60 --- /dev/null +++ b/src/file-manager/fm-tree-view.c @@ -0,0 +1,1814 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Darin Adler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Maciej Stachowiak <[email protected]> + * Anders Carlsson <[email protected]> + * Darin Adler <[email protected]> + */ + +/* fm-tree-view.c - tree sidebar panel + */ + +#include <config.h> +#include "fm-tree-view.h" + +#include "fm-tree-model.h" +#include "fm-properties-window.h" +#include <string.h> +#include <eel/eel-alert-dialog.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-preferences.h> +#include <eel/eel-stock-dialogs.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-names.h> +#include <libcaja-private/caja-program-choosing.h> +#include <libcaja-private/caja-tree-view-drag-dest.h> +#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h> +#include <libcaja-private/caja-sidebar-provider.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-window-info.h> +#include <libcaja-private/caja-window-slot-info.h> + +typedef struct +{ + GObject parent; +} FMTreeViewProvider; + +typedef struct +{ + GObjectClass parent; +} FMTreeViewProviderClass; + + +struct FMTreeViewDetails +{ + CajaWindowInfo *window; + GtkTreeView *tree_widget; + GtkTreeModelSort *sort_model; + FMTreeModel *child_model; + + GVolumeMonitor *volume_monitor; + + CajaFile *activation_file; + CajaWindowOpenFlags activation_flags; + + CajaTreeViewDragDest *drag_dest; + + char *selection_location; + gboolean selecting; + + guint show_selection_idle_id; + gulong clipboard_handler_id; + + GtkWidget *popup; + GtkWidget *popup_open; + GtkWidget *popup_open_in_new_window; + GtkWidget *popup_create_folder; + GtkWidget *popup_cut; + GtkWidget *popup_copy; + GtkWidget *popup_paste; + GtkWidget *popup_rename; + GtkWidget *popup_trash; + GtkWidget *popup_delete; + GtkWidget *popup_properties; + GtkWidget *popup_unmount_separator; + GtkWidget *popup_unmount; + GtkWidget *popup_eject; + CajaFile *popup_file; + guint popup_file_idle_handler; + + guint selection_changed_timer; +}; + +typedef struct +{ + GList *uris; + FMTreeView *view; +} PrependURIParameters; + +static GdkAtom copied_files_atom; +static gboolean show_delete_command_auto_value; + +static void fm_tree_view_iface_init (CajaSidebarIface *iface); +static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface); +static void fm_tree_view_activate_file (FMTreeView *view, + CajaFile *file, + CajaWindowOpenFlags flags); +static GType fm_tree_view_provider_get_type (void); + +static void create_popup_menu (FMTreeView *view); + +G_DEFINE_TYPE_WITH_CODE (FMTreeView, fm_tree_view, GTK_TYPE_SCROLLED_WINDOW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR, + fm_tree_view_iface_init)); +#define parent_class fm_tree_view_parent_class + +G_DEFINE_TYPE_WITH_CODE (FMTreeViewProvider, fm_tree_view_provider, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER, + sidebar_provider_iface_init)); + +static void +notify_clipboard_info (CajaClipboardMonitor *monitor, + CajaClipboardInfo *info, + FMTreeView *view) +{ + if (info != NULL && info->cut) + { + fm_tree_model_set_highlight_for_files (view->details->child_model, info->files); + } + else + { + fm_tree_model_set_highlight_for_files (view->details->child_model, NULL); + } +} + + +static gboolean +show_iter_for_file (FMTreeView *view, CajaFile *file, GtkTreeIter *iter) +{ + GtkTreeModel *model; + CajaFile *parent_file; + GtkTreeIter parent_iter; + GtkTreePath *path, *sort_path; + GtkTreeIter cur_iter; + + if (view->details->child_model == NULL) + { + return FALSE; + } + model = GTK_TREE_MODEL (view->details->child_model); + + /* check if file is visible in the same root as the currently selected folder is */ + gtk_tree_view_get_cursor (view->details->tree_widget, &path, NULL); + if (path != NULL) + { + if (gtk_tree_model_get_iter (model, &cur_iter, path) && + fm_tree_model_file_get_iter (view->details->child_model, iter, + file, &cur_iter)) + { + gtk_tree_path_free (path); + return TRUE; + } + gtk_tree_path_free (path); + } + /* check if file is visible at all */ + if (fm_tree_model_file_get_iter (view->details->child_model, + iter, file, NULL)) + { + return TRUE; + } + + parent_file = caja_file_get_parent (file); + + if (parent_file == NULL) + { + return FALSE; + } + if (!show_iter_for_file (view, parent_file, &parent_iter)) + { + caja_file_unref (parent_file); + return FALSE; + } + caja_file_unref (parent_file); + + if (parent_iter.user_data == NULL || parent_iter.stamp == 0) + { + return FALSE; + } + path = gtk_tree_model_get_path (model, &parent_iter); + sort_path = gtk_tree_model_sort_convert_child_path_to_path + (view->details->sort_model, path); + gtk_tree_path_free (path); + gtk_tree_view_expand_row (view->details->tree_widget, sort_path, FALSE); + gtk_tree_path_free (sort_path); + + return FALSE; +} + +static void +refresh_highlight (FMTreeView *view) +{ + CajaClipboardMonitor *monitor; + CajaClipboardInfo *info; + + monitor = caja_clipboard_monitor_get (); + info = caja_clipboard_monitor_get_clipboard_info (monitor); + + notify_clipboard_info (monitor, info, view); +} + +static gboolean +show_selection_idle_callback (gpointer callback_data) +{ + FMTreeView *view; + CajaFile *file, *old_file; + GtkTreeIter iter; + GtkTreePath *path, *sort_path; + + view = FM_TREE_VIEW (callback_data); + + view->details->show_selection_idle_id = 0; + + file = caja_file_get_by_uri (view->details->selection_location); + if (file == NULL) + { + return FALSE; + } + + if (!caja_file_is_directory (file)) + { + old_file = file; + file = caja_file_get_parent (file); + caja_file_unref (old_file); + if (file == NULL) + { + return FALSE; + } + } + + view->details->selecting = TRUE; + if (!show_iter_for_file (view, file, &iter)) + { + caja_file_unref (file); + return FALSE; + } + view->details->selecting = FALSE; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->child_model), &iter); + sort_path = gtk_tree_model_sort_convert_child_path_to_path + (view->details->sort_model, path); + gtk_tree_path_free (path); + gtk_tree_view_set_cursor (view->details->tree_widget, sort_path, NULL, FALSE); + if (gtk_widget_get_realized (GTK_WIDGET (view->details->tree_widget))) + { + gtk_tree_view_scroll_to_cell (view->details->tree_widget, sort_path, NULL, FALSE, 0, 0); + } + gtk_tree_path_free (sort_path); + + caja_file_unref (file); + refresh_highlight (view); + + return FALSE; +} + +static void +schedule_show_selection (FMTreeView *view) +{ + if (view->details->show_selection_idle_id == 0) + { + view->details->show_selection_idle_id = g_idle_add (show_selection_idle_callback, view); + } +} + +static void +schedule_select_and_show_location (FMTreeView *view, char *location) +{ + if (view->details->selection_location != NULL) + { + g_free (view->details->selection_location); + } + view->details->selection_location = g_strdup (location); + schedule_show_selection (view); +} + +static void +row_loaded_callback (GtkTreeModel *tree_model, + GtkTreeIter *iter, + FMTreeView *view) +{ + CajaFile *file, *tmp_file, *selection_file; + + if (view->details->selection_location == NULL + || !view->details->selecting + || iter->user_data == NULL || iter->stamp == 0) + { + return; + } + + file = fm_tree_model_iter_get_file (view->details->child_model, iter); + if (file == NULL) + { + return; + } + if (!caja_file_is_directory (file)) + { + caja_file_unref(file); + return; + } + + /* if iter is ancestor of wanted selection_location then update selection */ + selection_file = caja_file_get_by_uri (view->details->selection_location); + while (selection_file != NULL) + { + if (file == selection_file) + { + caja_file_unref (file); + caja_file_unref (selection_file); + + schedule_show_selection (view); + return; + } + tmp_file = caja_file_get_parent (selection_file); + caja_file_unref (selection_file); + selection_file = tmp_file; + } + caja_file_unref (file); +} + +static CajaFile * +sort_model_iter_to_file (FMTreeView *view, GtkTreeIter *iter) +{ + GtkTreeIter child_iter; + + gtk_tree_model_sort_convert_iter_to_child_iter (view->details->sort_model, &child_iter, iter); + return fm_tree_model_iter_get_file (view->details->child_model, &child_iter); +} + +static CajaFile * +sort_model_path_to_file (FMTreeView *view, GtkTreePath *path) +{ + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->sort_model), &iter, path)) + { + return NULL; + } + return sort_model_iter_to_file (view, &iter); +} + +static void +got_activation_uri_callback (CajaFile *file, gpointer callback_data) +{ + char *uri, *file_uri; + FMTreeView *view; + GdkScreen *screen; + GFile *location; + CajaWindowSlotInfo *slot; + gboolean open_in_same_slot; + + view = FM_TREE_VIEW (callback_data); + + screen = gtk_widget_get_screen (GTK_WIDGET (view->details->tree_widget)); + + g_assert (file == view->details->activation_file); + + open_in_same_slot = + (view->details->activation_flags & + (CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW | + CAJA_WINDOW_OPEN_FLAG_NEW_TAB)) == 0; + + slot = caja_window_info_get_active_slot (view->details->window); + + uri = caja_file_get_activation_uri (file); + if (caja_file_is_launcher (file)) + { + file_uri = caja_file_get_uri (file); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "tree view launch_desktop_file window=%p: %s", + view->details->window, file_uri); + caja_launch_desktop_file (screen, file_uri, NULL, NULL); + g_free (file_uri); + } + else if (uri != NULL + && caja_file_is_executable (file) + && caja_file_can_execute (file) + && !caja_file_is_directory (file)) + { + + file_uri = g_filename_from_uri (uri, NULL, NULL); + + /* Non-local executables don't get launched. They act like non-executables. */ + if (file_uri == NULL) + { + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "tree view window_info_open_location window=%p: %s", + view->details->window, uri); + location = g_file_new_for_uri (uri); + caja_window_slot_info_open_location + (slot, + location, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + view->details->activation_flags, + NULL); + g_object_unref (location); + } + else + { + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "tree view launch_application_from_command window=%p: %s", + view->details->window, file_uri); + caja_launch_application_from_command (screen, NULL, file_uri, FALSE, NULL); + g_free (file_uri); + } + + } + else if (uri != NULL) + { + if (!open_in_same_slot || + view->details->selection_location == NULL || + strcmp (uri, view->details->selection_location) != 0) + { + if (open_in_same_slot) + { + if (view->details->selection_location != NULL) + { + g_free (view->details->selection_location); + } + view->details->selection_location = g_strdup (uri); + } + + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "tree view window_info_open_location window=%p: %s", + view->details->window, uri); + location = g_file_new_for_uri (uri); + caja_window_slot_info_open_location + (slot, + location, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + view->details->activation_flags, + NULL); + g_object_unref (location); + } + } + + g_free (uri); + caja_file_unref (view->details->activation_file); + view->details->activation_file = NULL; +} + +static void +cancel_activation (FMTreeView *view) +{ + if (view->details->activation_file == NULL) + { + return; + } + + caja_file_cancel_call_when_ready + (view->details->activation_file, + got_activation_uri_callback, view); + caja_file_unref (view->details->activation_file); + view->details->activation_file = NULL; +} + +static void +row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, FMTreeView *view) +{ + if (gtk_tree_view_row_expanded (view->details->tree_widget, path)) + { + gtk_tree_view_collapse_row (view->details->tree_widget, path); + } + else + { + gtk_tree_view_expand_row (view->details->tree_widget, + path, FALSE); + } +} + +static gboolean +selection_changed_timer_callback(FMTreeView *view) +{ + CajaFileAttributes attributes; + GtkTreeIter iter; + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget)); + + /* no activation if popup menu is open */ + if (view->details->popup_file != NULL) + { + return FALSE; + } + + cancel_activation (view); + + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + return FALSE; + } + + view->details->activation_file = sort_model_iter_to_file (view, &iter); + if (view->details->activation_file == NULL) + { + return FALSE; + } + view->details->activation_flags = 0; + + attributes = CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO; + caja_file_call_when_ready (view->details->activation_file, attributes, + got_activation_uri_callback, view); + return FALSE; /* remove timeout */ +} + +static void +selection_changed_callback (GtkTreeSelection *selection, + FMTreeView *view) +{ + GdkEvent *event; + gboolean is_keyboard; + + if (view->details->selection_changed_timer) + { + g_source_remove (view->details->selection_changed_timer); + view->details->selection_changed_timer = 0; + } + + event = gtk_get_current_event (); + if (event) + { + is_keyboard = (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE); + gdk_event_free (event); + + if (is_keyboard) + { + /* on keyboard event: delay the change */ + /* TODO: make dependent on keyboard repeat rate as per Markus Bertheau ? */ + view->details->selection_changed_timer = g_timeout_add (300, (GSourceFunc) selection_changed_timer_callback, view); + } + else + { + /* on mouse event: show the change immediately */ + selection_changed_timer_callback (view); + } + } +} + +static int +compare_rows (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer callback_data) +{ + CajaFile *file_a, *file_b; + int result; + + /* Dummy rows are always first */ + if (a->user_data == NULL) + { + return -1; + } + else if (b->user_data == NULL) + { + return 1; + } + + /* don't sort root nodes */ + if (fm_tree_model_iter_is_root (FM_TREE_MODEL (model), a) && + fm_tree_model_iter_is_root (FM_TREE_MODEL (model), b)) + { + return fm_tree_model_iter_compare_roots (FM_TREE_MODEL (model), a, b); + } + + file_a = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), a); + file_b = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), b); + + if (file_a == file_b) + { + result = 0; + } + else if (file_a == NULL) + { + result = -1; + } + else if (file_b == NULL) + { + result = +1; + } + else + { + result = caja_file_compare_for_sort (file_a, file_b, + CAJA_FILE_SORT_BY_DISPLAY_NAME, + FALSE, FALSE); + } + + caja_file_unref (file_a); + caja_file_unref (file_b); + + return result; +} + + +static char * +get_root_uri_callback (CajaTreeViewDragDest *dest, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + /* Don't allow drops on background */ + return NULL; +} + +static CajaFile * +get_file_for_path_callback (CajaTreeViewDragDest *dest, + GtkTreePath *path, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + return sort_model_path_to_file (view, path); +} + +static void +move_copy_items_callback (CajaTreeViewDragDest *dest, + const GList *item_uris, + const char *target_uri, + GdkDragAction action, + int x, + int y, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + item_uris, + copied_files_atom); + caja_file_operations_copy_move + (item_uris, + NULL, + target_uri, + action, + GTK_WIDGET (view->details->tree_widget), + NULL, NULL); +} + +static void +add_root_for_mount (FMTreeView *view, + GMount *mount) +{ + char *mount_uri, *name; + GFile *root; + GIcon *icon; + + if (g_mount_is_shadowed (mount)) + return; + + icon = g_mount_get_icon (mount); + root = g_mount_get_root (mount); + mount_uri = g_file_get_uri (root); + g_object_unref (root); + name = g_mount_get_name (mount); + + fm_tree_model_add_root_uri(view->details->child_model, + mount_uri, name, icon, mount); + + g_object_unref (icon); + g_free (name); + g_free (mount_uri); + +} + +static void +mount_added_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + FMTreeView *view) +{ + add_root_for_mount (view, mount); +} + +static void +mount_removed_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + FMTreeView *view) +{ + GFile *root; + char *mount_uri; + + root = g_mount_get_root (mount); + mount_uri = g_file_get_uri (root); + g_object_unref (root); + fm_tree_model_remove_root_uri (view->details->child_model, + mount_uri); + g_free (mount_uri); +} + +static void +clipboard_contents_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (data); + + if (gtk_selection_data_get_data_type (selection_data) == copied_files_atom + && gtk_selection_data_get_length (selection_data) > 0 && + view->details->popup != NULL) + { + gtk_widget_set_sensitive (view->details->popup_paste, TRUE); + } + + g_object_unref (view); +} + +static gboolean +is_parent_writable (CajaFile *file) +{ + CajaFile *parent; + gboolean result; + + parent = caja_file_get_parent (file); + + /* No parent directory, return FALSE */ + if (parent == NULL) + { + return FALSE; + } + + result = caja_file_can_write (parent); + caja_file_unref (parent); + + return result; +} + +static gboolean +button_pressed_callback (GtkTreeView *treeview, GdkEventButton *event, + FMTreeView *view) +{ + GtkTreePath *path, *cursor_path; + gboolean parent_file_is_writable; + gboolean file_is_home_or_desktop; + gboolean file_is_special_link; + gboolean can_move_file_to_trash; + gboolean can_delete_file; + + if (event->button == 3) + { + gboolean show_unmount = FALSE; + gboolean show_eject = FALSE; + GMount *mount = NULL; + + if (view->details->popup_file != NULL) + { + return FALSE; /* Already up, ignore */ + } + + if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, + &path, NULL, NULL, NULL)) + { + return FALSE; + } + + view->details->popup_file = sort_model_path_to_file (view, path); + if (view->details->popup_file == NULL) + { + gtk_tree_path_free (path); + return FALSE; + } + gtk_tree_view_get_cursor (view->details->tree_widget, &cursor_path, NULL); + gtk_tree_view_set_cursor (view->details->tree_widget, path, NULL, FALSE); + gtk_tree_path_free (path); + + create_popup_menu (view); + + gtk_widget_set_sensitive (view->details->popup_open_in_new_window, + caja_file_is_directory (view->details->popup_file)); + gtk_widget_set_sensitive (view->details->popup_create_folder, + caja_file_is_directory (view->details->popup_file) && + caja_file_can_write (view->details->popup_file)); + gtk_widget_set_sensitive (view->details->popup_paste, FALSE); + if (caja_file_is_directory (view->details->popup_file) && + caja_file_can_write (view->details->popup_file)) + { + gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), + copied_files_atom, + clipboard_contents_received_callback, g_object_ref (view)); + } + can_move_file_to_trash = caja_file_can_trash (view->details->popup_file); + gtk_widget_set_sensitive (view->details->popup_trash, can_move_file_to_trash); + + if (show_delete_command_auto_value) + { + parent_file_is_writable = is_parent_writable (view->details->popup_file); + file_is_home_or_desktop = caja_file_is_home (view->details->popup_file) + || caja_file_is_desktop_directory (view->details->popup_file); + file_is_special_link = CAJA_IS_DESKTOP_ICON_FILE (view->details->popup_file); + + can_delete_file = parent_file_is_writable + && !file_is_home_or_desktop + && !file_is_special_link; + + gtk_widget_show (view->details->popup_delete); + gtk_widget_set_sensitive (view->details->popup_delete, can_delete_file); + } + else + { + gtk_widget_hide (view->details->popup_delete); + } + + mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, view->details->popup_file); + if (mount) + { + show_unmount = g_mount_can_unmount (mount); + show_eject = g_mount_can_eject (mount); + } + + if (show_unmount) + { + gtk_widget_show (view->details->popup_unmount); + } + else + { + gtk_widget_hide (view->details->popup_unmount); + } + + if (show_eject) + { + gtk_widget_show (view->details->popup_eject); + } + else + { + gtk_widget_hide (view->details->popup_eject); + } + + if (show_unmount || show_eject) + { + gtk_widget_show (view->details->popup_unmount_separator); + } + else + { + gtk_widget_hide (view->details->popup_unmount_separator); + } + + gtk_menu_popup (GTK_MENU (view->details->popup), + NULL, NULL, NULL, NULL, + event->button, event->time); + + gtk_tree_view_set_cursor (view->details->tree_widget, cursor_path, NULL, FALSE); + gtk_tree_path_free (cursor_path); + + return TRUE; + } + else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) + { + CajaFile *file; + + if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, + &path, NULL, NULL, NULL)) + { + return FALSE; + } + + file = sort_model_path_to_file (view, path); + if (file) + { + fm_tree_view_activate_file (view, file, + (event->state & GDK_CONTROL_MASK) != 0 ? + CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW : + CAJA_WINDOW_OPEN_FLAG_NEW_TAB); + caja_file_unref (file); + } + + gtk_tree_path_free (path); + + return TRUE; + } + + return FALSE; +} + +static void +fm_tree_view_activate_file (FMTreeView *view, + CajaFile *file, + CajaWindowOpenFlags flags) +{ + CajaFileAttributes attributes; + + cancel_activation (view); + + view->details->activation_file = caja_file_ref (file); + view->details->activation_flags = flags; + + attributes = CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO; + caja_file_call_when_ready (view->details->activation_file, attributes, + got_activation_uri_callback, view); +} + +static void +fm_tree_view_open_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + fm_tree_view_activate_file (view, view->details->popup_file, 0); +} + +static void +fm_tree_view_open_in_new_tab_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + fm_tree_view_activate_file (view, view->details->popup_file, CAJA_WINDOW_OPEN_FLAG_NEW_TAB); +} + +static void +fm_tree_view_open_in_new_window_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + fm_tree_view_activate_file (view, view->details->popup_file, CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW); +} + +static void +new_folder_done (GFile *new_folder, gpointer data) +{ + GList *list; + + /* show the properties window for the newly created + * folder so the user can change its name + */ + list = g_list_prepend (NULL, caja_file_get (new_folder)); + + fm_properties_window_present (list, GTK_WIDGET (data)); + + caja_file_list_free (list); +} + +static void +fm_tree_view_create_folder_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + char *parent_uri; + + parent_uri = caja_file_get_uri (view->details->popup_file); + caja_file_operations_new_folder (GTK_WIDGET (view->details->tree_widget), + NULL, + parent_uri, + new_folder_done, view->details->tree_widget); + + g_free (parent_uri); +} + +static void +copy_or_cut_files (FMTreeView *view, + gboolean cut) +{ + char *status_string, *name; + CajaClipboardInfo info; + GtkTargetList *target_list; + GtkTargetEntry *targets; + int n_targets; + + info.cut = cut; + info.files = g_list_prepend (NULL, view->details->popup_file); + + target_list = gtk_target_list_new (NULL, 0); + gtk_target_list_add (target_list, copied_files_atom, 0, 0); + gtk_target_list_add_uri_targets (target_list, 0); + gtk_target_list_add_text_targets (target_list, 0); + + targets = gtk_target_table_new_from_list (target_list, &n_targets); + gtk_target_list_unref (target_list); + + gtk_clipboard_set_with_data (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), + targets, n_targets, + caja_get_clipboard_callback, caja_clear_clipboard_callback, + NULL); + gtk_target_table_free (targets, n_targets); + + caja_clipboard_monitor_set_clipboard_info (caja_clipboard_monitor_get (), + &info); + g_list_free (info.files); + + name = caja_file_get_display_name (view->details->popup_file); + if (cut) + { + status_string = g_strdup_printf (_("\"%s\" will be moved " + "if you select the Paste command"), + name); + } + else + { + status_string = g_strdup_printf (_("\"%s\" will be copied " + "if you select the Paste command"), + name); + } + g_free (name); + + caja_window_info_push_status (view->details->window, + status_string); + g_free (status_string); +} + +static void +fm_tree_view_cut_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + copy_or_cut_files (view, TRUE); +} + +static void +fm_tree_view_copy_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + copy_or_cut_files (view, FALSE); +} + +static void +paste_clipboard_data (FMTreeView *view, + GtkSelectionData *selection_data, + char *destination_uri) +{ + gboolean cut; + GList *item_uris; + + cut = FALSE; + item_uris = caja_clipboard_get_uri_list_from_selection_data (selection_data, &cut, + copied_files_atom); + + if (item_uris == NULL|| destination_uri == NULL) + { + caja_window_info_push_status (view->details->window, + _("There is nothing on the clipboard to paste.")); + } + else + { + caja_file_operations_copy_move + (item_uris, NULL, destination_uri, + cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY, + GTK_WIDGET (view->details->tree_widget), + NULL, NULL); + + /* If items are cut then remove from clipboard */ + if (cut) + { + gtk_clipboard_clear (caja_clipboard_get (GTK_WIDGET (view))); + } + + eel_g_list_free_deep (item_uris); + } +} + +static void +paste_into_clipboard_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + FMTreeView *view; + char *directory_uri; + + view = FM_TREE_VIEW (data); + + directory_uri = caja_file_get_uri (view->details->popup_file); + + paste_clipboard_data (view, selection_data, directory_uri); + + g_free (directory_uri); +} + +static void +fm_tree_view_paste_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), + copied_files_atom, + paste_into_clipboard_received_callback, view); +} + +static GtkWindow * +fm_tree_view_get_containing_window (FMTreeView *view) +{ + GtkWidget *window; + + g_assert (FM_IS_TREE_VIEW (view)); + + window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW); + if (window == NULL) + { + return NULL; + } + + return GTK_WINDOW (window); +} + +static void +fm_tree_view_trash_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + GList *list; + + if (!caja_file_can_trash (view->details->popup_file)) + { + return; + } + + list = g_list_prepend (NULL, + caja_file_get_location (view->details->popup_file)); + + caja_file_operations_trash_or_delete (list, + fm_tree_view_get_containing_window (view), + NULL, NULL); + eel_g_object_list_free (list); +} + +static void +fm_tree_view_delete_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + GList *location_list; + + if (!show_delete_command_auto_value) + { + return; + } + + location_list = g_list_prepend (NULL, + caja_file_get_location (view->details->popup_file)); + + caja_file_operations_delete (location_list, fm_tree_view_get_containing_window (view), NULL, NULL); + eel_g_object_list_free (location_list); +} + +static void +fm_tree_view_properties_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + GList *list; + + list = g_list_prepend (NULL, caja_file_ref (view->details->popup_file)); + + fm_properties_window_present (list, GTK_WIDGET (view->details->tree_widget)); + + caja_file_list_free (list); +} + +static void +fm_tree_view_unmount_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + CajaFile *file = view->details->popup_file; + GMount *mount; + + if (file == NULL) + { + return; + } + + mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, file); + + if (mount != NULL) + { + caja_file_operations_unmount_mount (fm_tree_view_get_containing_window (view), + mount, FALSE, TRUE); + } +} + +static void +fm_tree_view_eject_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + CajaFile *file = view->details->popup_file; + GMount *mount; + + if (file == NULL) + { + return; + } + + mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, file); + + if (mount != NULL) + { + caja_file_operations_unmount_mount (fm_tree_view_get_containing_window (view), + mount, TRUE, TRUE); + } +} + +static gboolean +free_popup_file_in_idle_cb (gpointer data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (data); + + if (view->details->popup_file != NULL) + { + caja_file_unref (view->details->popup_file); + view->details->popup_file = NULL; + } + view->details->popup_file_idle_handler = 0; + return FALSE; +} + +static void +popup_menu_deactivated (GtkMenuShell *menu_shell, gpointer data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (data); + + /* The popup menu is deactivated. (I.E. hidden) + We want to free popup_file, but can't right away as it might immediately get + used if we're deactivation due to activating a menu item. So, we free it in + idle */ + + if (view->details->popup_file != NULL && + view->details->popup_file_idle_handler == 0) + { + view->details->popup_file_idle_handler = g_idle_add (free_popup_file_in_idle_cb, view); + } +} + +static void +create_popup_menu (FMTreeView *view) +{ + GtkWidget *popup, *menu_item, *menu_image; + + if (view->details->popup != NULL) + { + /* already created */ + return; + } + + popup = gtk_menu_new (); + + g_signal_connect (popup, "deactivate", + G_CALLBACK (popup_menu_deactivated), + view); + + + /* add the "open" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_OPEN, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Open")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_open_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_open = menu_item; + + /* add the "open in new tab" menu item */ + menu_item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_open_in_new_tab_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_open_in_new_window = menu_item; + + /* add the "open in new window" menu item */ + menu_item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_open_in_new_window_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_open_in_new_window = menu_item; + + eel_gtk_menu_append_separator (GTK_MENU (popup)); + + /* add the "create folder" menu item */ + menu_item = gtk_image_menu_item_new_with_mnemonic (_("Create _Folder")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_create_folder_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_create_folder = menu_item; + + eel_gtk_menu_append_separator (GTK_MENU (popup)); + + /* add the "cut folder" menu item */ + menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, NULL); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_cut_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_cut = menu_item; + + /* add the "copy folder" menu item */ + menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_copy_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_copy = menu_item; + + /* add the "paste files into folder" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_PASTE, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Paste Into Folder")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_paste_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_paste = menu_item; + + eel_gtk_menu_append_separator (GTK_MENU (popup)); + + /* add the "move to trash" menu item */ + menu_image = gtk_image_new_from_icon_name (CAJA_ICON_TRASH_FULL, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_mnemonic (_("Mo_ve to Trash")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_trash_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_trash = menu_item; + + /* add the "delete" menu item */ + menu_image = gtk_image_new_from_icon_name (CAJA_ICON_DELETE, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Delete")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_delete_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_delete = menu_item; + + eel_gtk_menu_append_separator (GTK_MENU (popup)); + + /* add the "Unmount" menu item */ + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Unmount")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_unmount_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_unmount = menu_item; + + /* add the "Eject" menu item */ + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Eject")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_eject_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_eject = menu_item; + + /* add the unmount separator menu item */ + view->details->popup_unmount_separator = + GTK_WIDGET (eel_gtk_menu_append_separator (GTK_MENU (popup))); + + /* add the "properties" menu item */ + menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_PROPERTIES, NULL); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_properties_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_properties = menu_item; + + view->details->popup = popup; +} + +static void +create_tree (FMTreeView *view) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + GVolumeMonitor *volume_monitor; + char *home_uri; + GList *mounts, *l; + char *location; + GIcon *icon; + CajaWindowSlotInfo *slot; + + view->details->child_model = fm_tree_model_new (); + view->details->sort_model = GTK_TREE_MODEL_SORT + (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (view->details->child_model))); + view->details->tree_widget = GTK_TREE_VIEW + (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view->details->sort_model))); + g_object_unref (view->details->sort_model); + + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (view->details->sort_model), + compare_rows, view, NULL); + + g_signal_connect_object + (view->details->child_model, "row_loaded", + G_CALLBACK (row_loaded_callback), + view, G_CONNECT_AFTER); + home_uri = caja_get_home_directory_uri (); + icon = g_themed_icon_new (CAJA_ICON_HOME); + fm_tree_model_add_root_uri (view->details->child_model, home_uri, _("Home Folder"), icon, NULL); + g_object_unref (icon); + g_free (home_uri); + icon = g_themed_icon_new (CAJA_ICON_FILESYSTEM); + fm_tree_model_add_root_uri (view->details->child_model, "file:///", _("File System"), icon, NULL); + g_object_unref (icon); +#ifdef NOT_YET_USABLE /* Do we really want this? */ + icon = g_themed_icon_new (CAJA_ICON_NETWORK); + fm_tree_model_add_root_uri (view->details->child_model, "network:///", _("Network Neighbourhood"), icon, NULL); + g_object_unref (icon); +#endif + + volume_monitor = g_volume_monitor_get (); + view->details->volume_monitor = volume_monitor; + mounts = g_volume_monitor_get_mounts (volume_monitor); + for (l = mounts; l != NULL; l = l->next) + { + add_root_for_mount (view, l->data); + g_object_unref (l->data); + } + g_list_free (mounts); + + g_signal_connect_object (volume_monitor, "mount_added", + G_CALLBACK (mount_added_callback), view, 0); + g_signal_connect_object (volume_monitor, "mount_removed", + G_CALLBACK (mount_removed_callback), view, 0); + + g_object_unref (view->details->child_model); + + gtk_tree_view_set_headers_visible (view->details->tree_widget, FALSE); + + view->details->drag_dest = + caja_tree_view_drag_dest_new (view->details->tree_widget); + g_signal_connect_object (view->details->drag_dest, + "get_root_uri", + G_CALLBACK (get_root_uri_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "get_file_for_path", + G_CALLBACK (get_file_for_path_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "move_copy_items", + G_CALLBACK (move_copy_items_callback), + view, 0); + + /* Create column */ + column = gtk_tree_view_column_new (); + + cell = caja_cell_renderer_pixbuf_emblem_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "pixbuf", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + "pixbuf_expander_closed", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + "pixbuf_expander_open", FM_TREE_MODEL_OPEN_PIXBUF_COLUMN, + "pixbuf_emblem", FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN, + NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", FM_TREE_MODEL_DISPLAY_NAME_COLUMN, + "style", FM_TREE_MODEL_FONT_STYLE_COLUMN, + NULL); + + gtk_tree_view_append_column (view->details->tree_widget, column); + + gtk_widget_show (GTK_WIDGET (view->details->tree_widget)); + + gtk_container_add (GTK_CONTAINER (view), + GTK_WIDGET (view->details->tree_widget)); + + g_signal_connect_object (gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget)), "changed", + G_CALLBACK (selection_changed_callback), view, 0); + + g_signal_connect (G_OBJECT (view->details->tree_widget), + "row-activated", G_CALLBACK (row_activated_callback), + view); + + g_signal_connect (G_OBJECT (view->details->tree_widget), + "button_press_event", G_CALLBACK (button_pressed_callback), + view); + + slot = caja_window_info_get_active_slot (view->details->window); + location = caja_window_slot_info_get_current_location (slot); + schedule_select_and_show_location (view, location); + g_free (location); +} + +static void +update_filtering_from_preferences (FMTreeView *view) +{ + CajaWindowShowHiddenFilesMode mode; + + if (view->details->child_model == NULL) + { + return; + } + + mode = caja_window_info_get_hidden_files_mode (view->details->window); + + if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) + { + fm_tree_model_set_show_hidden_files + (view->details->child_model, + eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES)); + } + else + { + fm_tree_model_set_show_hidden_files + (view->details->child_model, + mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE); + } + fm_tree_model_set_show_only_directories + (view->details->child_model, + eel_preferences_get_boolean (CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES)); +} + +static void +parent_set_callback (GtkWidget *widget, + GtkWidget *previous_parent, + gpointer callback_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (callback_data); + + if (gtk_widget_get_parent (widget) != NULL && view->details->tree_widget == NULL) + { + create_tree (view); + update_filtering_from_preferences (view); + } +} + +static void +filtering_changed_callback (gpointer callback_data) +{ + update_filtering_from_preferences (FM_TREE_VIEW (callback_data)); +} + +static void +loading_uri_callback (CajaWindowInfo *window, + char *location, + gpointer callback_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (callback_data); + schedule_select_and_show_location (view, location); +} + +static void +fm_tree_view_init (FMTreeView *view) +{ + view->details = g_new0 (FMTreeViewDetails, 1); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (view), NULL); + gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (view), NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view), GTK_SHADOW_IN); + + gtk_widget_show (GTK_WIDGET (view)); + + g_signal_connect_object (view, "parent_set", + G_CALLBACK (parent_set_callback), view, 0); + + view->details->selection_location = NULL; + + view->details->selecting = FALSE; + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_HIDDEN_FILES, + filtering_changed_callback, view, G_OBJECT (view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_BACKUP_FILES, + filtering_changed_callback, view, G_OBJECT (view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES, + filtering_changed_callback, view, G_OBJECT (view)); + + view->details->popup_file = NULL; + + view->details->clipboard_handler_id = + g_signal_connect (caja_clipboard_monitor_get (), + "clipboard_info", + G_CALLBACK (notify_clipboard_info), view); +} + +static void +fm_tree_view_dispose (GObject *object) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (object); + + if (view->details->selection_changed_timer) + { + g_source_remove (view->details->selection_changed_timer); + view->details->selection_changed_timer = 0; + } + + if (view->details->drag_dest) + { + g_object_unref (view->details->drag_dest); + view->details->drag_dest = NULL; + } + + if (view->details->show_selection_idle_id) + { + g_source_remove (view->details->show_selection_idle_id); + view->details->show_selection_idle_id = 0; + } + + if (view->details->clipboard_handler_id != 0) + { + g_signal_handler_disconnect (caja_clipboard_monitor_get (), + view->details->clipboard_handler_id); + view->details->clipboard_handler_id = 0; + } + + cancel_activation (view); + + if (view->details->popup != NULL) + { + gtk_widget_destroy (view->details->popup); + view->details->popup = NULL; + } + + if (view->details->popup_file_idle_handler != 0) + { + g_source_remove (view->details->popup_file_idle_handler); + view->details->popup_file_idle_handler = 0; + } + + if (view->details->popup_file != NULL) + { + caja_file_unref (view->details->popup_file); + view->details->popup_file = NULL; + } + + if (view->details->selection_location != NULL) + { + g_free (view->details->selection_location); + view->details->selection_location = NULL; + } + + if (view->details->volume_monitor != NULL) + { + g_object_unref (view->details->volume_monitor); + view->details->volume_monitor = NULL; + } + + view->details->window = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +fm_tree_view_finalize (GObject *object) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (object); + + g_free (view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_tree_view_class_init (FMTreeViewClass *class) +{ + G_OBJECT_CLASS (class)->dispose = fm_tree_view_dispose; + G_OBJECT_CLASS (class)->finalize = fm_tree_view_finalize; + + copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE); + + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ENABLE_DELETE, + &show_delete_command_auto_value); +} + +static const char * +fm_tree_view_get_sidebar_id (CajaSidebar *sidebar) +{ + return TREE_SIDEBAR_ID; +} + +static char * +fm_tree_view_get_tab_label (CajaSidebar *sidebar) +{ + return g_strdup (_("Tree")); +} + +static char * +fm_tree_view_get_tab_tooltip (CajaSidebar *sidebar) +{ + return g_strdup (_("Show Tree")); +} + +static GdkPixbuf * +fm_tree_view_get_tab_icon (CajaSidebar *sidebar) +{ + return NULL; +} + +static void +fm_tree_view_is_visible_changed (CajaSidebar *sidebar, + gboolean is_visible) +{ + /* Do nothing */ +} + +static void +hidden_files_mode_changed_callback (CajaWindowInfo *window, + FMTreeView *view) +{ + update_filtering_from_preferences (view); +} + +static void +fm_tree_view_iface_init (CajaSidebarIface *iface) +{ + iface->get_sidebar_id = fm_tree_view_get_sidebar_id; + iface->get_tab_label = fm_tree_view_get_tab_label; + iface->get_tab_tooltip = fm_tree_view_get_tab_tooltip; + iface->get_tab_icon = fm_tree_view_get_tab_icon; + iface->is_visible_changed = fm_tree_view_is_visible_changed; +} + +static void +fm_tree_view_set_parent_window (FMTreeView *sidebar, + CajaWindowInfo *window) +{ + char *location; + CajaWindowSlotInfo *slot; + + sidebar->details->window = window; + + slot = caja_window_info_get_active_slot (window); + + g_signal_connect_object (window, "loading_uri", + G_CALLBACK (loading_uri_callback), sidebar, 0); + location = caja_window_slot_info_get_current_location (slot); + loading_uri_callback (window, location, sidebar); + g_free (location); + + g_signal_connect_object (window, "hidden_files_mode_changed", + G_CALLBACK (hidden_files_mode_changed_callback), sidebar, 0); + +} + +static CajaSidebar * +fm_tree_view_create (CajaSidebarProvider *provider, + CajaWindowInfo *window) +{ + FMTreeView *sidebar; + + sidebar = g_object_new (fm_tree_view_get_type (), NULL); + fm_tree_view_set_parent_window (sidebar, window); + g_object_ref_sink (sidebar); + + return CAJA_SIDEBAR (sidebar); +} + +static void +sidebar_provider_iface_init (CajaSidebarProviderIface *iface) +{ + iface->create = fm_tree_view_create; +} + +static void +fm_tree_view_provider_init (FMTreeViewProvider *sidebar) +{ +} + +static void +fm_tree_view_provider_class_init (FMTreeViewProviderClass *class) +{ +} + +void +fm_tree_view_register (void) +{ + caja_module_add_type (fm_tree_view_provider_get_type ()); +} diff --git a/src/file-manager/fm-tree-view.h b/src/file-manager/fm-tree-view.h new file mode 100644 index 00000000..a1bbe458 --- /dev/null +++ b/src/file-manager/fm-tree-view.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Maciej Stachowiak <[email protected]> + * Anders Carlsson <[email protected]> + */ + +/* fm-tree-view.h - tree view. */ + + +#ifndef FM_TREE_VIEW_H +#define FM_TREE_VIEW_H + +#include <gtk/gtk.h> + +#define FM_TYPE_TREE_VIEW fm_tree_view_get_type() +#define FM_TREE_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_TREE_VIEW, FMTreeView)) +#define FM_TREE_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_VIEW, FMTreeViewClass)) +#define FM_IS_TREE_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_TREE_VIEW)) +#define FM_IS_TREE_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_VIEW)) +#define FM_TREE_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_TREE_VIEW, FMTreeViewClass)) + +#define TREE_SIDEBAR_ID "CajaTreeSidebar" + +typedef struct FMTreeViewDetails FMTreeViewDetails; + +typedef struct +{ + GtkScrolledWindow parent; + + FMTreeViewDetails *details; +} FMTreeView; + +typedef struct +{ + GtkScrolledWindowClass parent_class; +} FMTreeViewClass; + +GType fm_tree_view_get_type (void); +void fm_tree_view_register (void); + +#endif /* FM_TREE_VIEW_H */ 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; |