diff options
Diffstat (limited to 'gtk/gtkplacessidebar.c')
-rw-r--r-- | gtk/gtkplacessidebar.c | 4335 |
1 files changed, 4335 insertions, 0 deletions
diff --git a/gtk/gtkplacessidebar.c b/gtk/gtkplacessidebar.c new file mode 100644 index 0000000000..98cd47f1d8 --- /dev/null +++ b/gtk/gtkplacessidebar.c @@ -0,0 +1,4335 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * GtkPlacesSidebar - sidebar widget for places in the filesystem + * + * This code comes from Nautilus, GNOME's file manager. + * + * 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 : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk) + * Cosimo Cecchi <cosimoc@gnome.org> + * Federico Mena Quintero <federico@gnome.org> + * + */ + +/* TODO: + * + * * Fix instances of "#if DO_NOT_COMPILE" + * + * * Fix instances of "#if 0" + * + * * Fix FIXMEs + * + * * Grep for "NULL-GError" and see if they should be taken care of + * + * * Although we do g_mount_unmount_with_operation(), Nautilus used to do + * nautilus_file_operations_unmount_mount_full() to unmount a volume. With + * that, Nautilus does the "volume has trash, empty it first?" dance. Cosimo + * suggests that this logic should be part of GtkMountOperation, which can + * have Unix-specific code for emptying trash. + * + * * Sync nautilus commit 17a85b78acc78b573c2e1776b348ed348e19adb7 + * + */ + +#include "config.h" + +#include <gio/gio.h> + +#include "gdk/gdkkeysyms.h" +#include "gtkbookmarksmanager.h" +#include "gtkcelllayout.h" +#include "gtkcellrenderertext.h" +#include "gtkcellrendererpixbuf.h" +#include "gtkfilechooserprivate.h" +#include "gtkicontheme.h" +#include "gtkimagemenuitem.h" +#include "gtkintl.h" +#include "gtkmain.h" +#include "gtkmarshalers.h" +#include "gtkmenuitem.h" +#include "gtkmountoperation.h" +#include "gtkplacessidebar.h" +#include "gtkscrolledwindow.h" +#include "gtkseparatormenuitem.h" +#include "gtksettings.h" +#include "gtkstock.h" +#include "gtktrashmonitor.h" +#include "gtktreeselection.h" +#include "gtktreednd.h" +#include "gtktypebuiltins.h" +#include "gtkwindow.h" + +/** + * SECTION:gtkplacessidebar + * @Short_description: Sidebar that displays frequently-used places in the file system + * @Title: GtkPlacesSidebar + * @See_also: #GtkFileChooser + * + * #GtkPlacesSidebar is a widget that displays a list of frequently-used places in the + * file system: the user's home directory, the user's bookmarks, and volumes and drives. + * This widget is used as a sidebar in #GtkFileChooser and may be used by file managers + * and similar programs. + * + * The places sidebar displays drives and volumes, and will automatically mount + * or unmount them when the user selects them. + * + * Applications can hook to various signals in the places sidebar to customize + * its behavior. For example, they can add extra commands to the context menu + * of the sidebar. + * + * While bookmarks are completely in control of the user, the places sidebar also + * allows individual applications to provide extra shortcut folders that are unique + * to each application. For example, a Paint program may want to add a shortcut + * for a Clipart folder. You can do this with gtk_places_sidebar_add_shortcut(). + * + * To make use of the places sidebar, an application at least needs to connect + * to the #GtkPlacesSidebar::open-location signal. This is emitted when the + * user selects in the sidebar a location to open. The application should also + * call gtk_places_sidebar_set_location() when it changes the currently-viewed + * location. + */ + +#define EJECT_BUTTON_XPAD 6 +#define ICON_CELL_XPAD 6 + +#define DO_NOT_COMPILE 0 + +struct _GtkPlacesSidebar { + GtkScrolledWindow parent; + + GtkTreeView *tree_view; + GtkCellRenderer *eject_icon_cell_renderer; + GtkListStore *store; + GtkBookmarksManager *bookmarks_manager; + GVolumeMonitor *volume_monitor; + GtkTrashMonitor *trash_monitor; + + gulong trash_monitor_changed_id; + + gboolean devices_header_added; + gboolean bookmarks_header_added; + + /* DnD */ + GList *drag_list; /* list of GFile */ + gboolean drag_data_received; + int drag_data_info; + gboolean drop_occured; + + GtkWidget *popup_menu; + + /* volume mounting - delayed open process */ + gboolean mounting; + GtkPlacesOpenFlags go_to_after_mount_open_flags; + + GSList *shortcuts; + + GDBusProxy *hostnamed_proxy; + GCancellable *hostnamed_cancellable; + char *hostname; + + GtkPlacesOpenFlags open_flags; + + guint show_desktop : 1; +}; + +struct _GtkPlacesSidebarClass { + GtkScrolledWindowClass parent; + + void (* open_location) (GtkPlacesSidebar *sidebar, + GFile *location, + GtkPlacesOpenFlags open_flags); + void (* populate_popup) (GtkPlacesSidebar *sidebar, + GtkMenu *menu, + GFile *selected_item); + void (* show_error_message) (GtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary); + GdkDragAction (* drag_action_requested) (GtkPlacesSidebar *sidebar, + GdkDragContext *context, + GFile *dest_file, + GList *source_file_list); + GdkDragAction (* drag_action_ask) (GtkPlacesSidebar *sidebar, + GdkDragAction actions); + void (* drag_perform_drop) (GtkPlacesSidebar *sidebar, + GFile *dest_file, + GList *source_file_list, + GdkDragAction action); +}; + +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_GICON, + PLACES_SIDEBAR_COLUMN_INDEX, + PLACES_SIDEBAR_COLUMN_EJECT, + PLACES_SIDEBAR_COLUMN_NO_EJECT, + PLACES_SIDEBAR_COLUMN_BOOKMARK, + PLACES_SIDEBAR_COLUMN_TOOLTIP, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, + PLACES_SIDEBAR_COLUMN_HEADING_TEXT, + + PLACES_SIDEBAR_COLUMN_COUNT +}; + +typedef enum { + PLACES_BUILT_IN, + PLACES_XDG_DIR, + PLACES_MOUNTED_VOLUME, + PLACES_BOOKMARK, + PLACES_HEADING, +} PlaceType; + +typedef enum { + SECTION_DEVICES, + SECTION_BOOKMARKS, + SECTION_COMPUTER, + SECTION_NETWORK, +} SectionType; + +enum { + OPEN_LOCATION, + POPULATE_POPUP, + SHOW_ERROR_MESSAGE, + DRAG_ACTION_REQUESTED, + DRAG_ACTION_ASK, + DRAG_PERFORM_DROP, + LAST_SIGNAL, +}; + +enum { + PROP_LOCATION = 1, + PROP_OPEN_FLAGS, + PROP_SHOW_DESKTOP, + NUM_PROPERTIES, +}; + +/* Names for themed icons */ +#define ICON_NAME_HOME "user-home-symbolic" +#define ICON_NAME_DESKTOP "user-desktop" +#define ICON_NAME_FILESYSTEM "drive-harddisk-symbolic" +#define ICON_NAME_EJECT "media-eject-symbolic" +#define ICON_NAME_NETWORK "network-workgroup-symbolic" + +#define ICON_NAME_FOLDER_DESKTOP "user-desktop" +#define ICON_NAME_FOLDER_DOCUMENTS "folder-documents-symbolic" +#define ICON_NAME_FOLDER_DOWNLOAD "folder-download-symbolic" +#define ICON_NAME_FOLDER_MUSIC "folder-music-symbolic" +#define ICON_NAME_FOLDER_PICTURES "folder-pictures-symbolic" +#define ICON_NAME_FOLDER_PUBLIC_SHARE "folder-publicshare-symbolic" +#define ICON_NAME_FOLDER_TEMPLATES "folder-templates-symbolic" +#define ICON_NAME_FOLDER_VIDEOS "folder-videos-symbolic" +#define ICON_NAME_FOLDER_SAVED_SEARCH "folder-saved-search-symbolic" + +static guint places_sidebar_signals [LAST_SIGNAL] = { 0 }; +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static void open_selected_bookmark (GtkPlacesSidebar *sidebar, + GtkTreeModel *model, + GtkTreeIter *iter, + GtkPlacesOpenFlags open_flags); +static gboolean eject_or_unmount_bookmark (GtkPlacesSidebar *sidebar, + GtkTreePath *path); +static gboolean eject_or_unmount_selection (GtkPlacesSidebar *sidebar); +static void check_unmount_and_eject (GMount *mount, + GVolume *volume, + GDrive *drive, + gboolean *show_unmount, + gboolean *show_eject); + +/* Identifiers for target types */ +enum { + GTK_TREE_MODEL_ROW, + TEXT_URI_LIST +}; + +/* Target types for dragging from the shortcuts list */ +static const GtkTargetEntry dnd_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 dnd_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 { + GtkListStore parent; + + GtkPlacesSidebar *sidebar; +} ShortcutsModel; + +typedef struct { + GtkListStoreClass parent_class; +} ShortcutsModelClass; + +#define SHORTCUTS_MODEL_TYPE (shortcuts_model_get_type ()) +#define SHORTCUTS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHORTCUTS_MODEL_TYPE, ShortcutsModel)) + +static GType shortcuts_model_get_type (void); +static void shortcuts_model_drag_source_iface_init (GtkTreeDragSourceIface *iface); + +G_DEFINE_TYPE_WITH_CODE (ShortcutsModel, + shortcuts_model, + GTK_TYPE_LIST_STORE, + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, + shortcuts_model_drag_source_iface_init)); + +static GtkListStore *shortcuts_model_new (GtkPlacesSidebar *sidebar); + +G_DEFINE_TYPE (GtkPlacesSidebar, gtk_places_sidebar, GTK_TYPE_SCROLLED_WINDOW); + +static void +emit_open_location (GtkPlacesSidebar *sidebar, GFile *location, GtkPlacesOpenFlags open_flags) +{ + if ((open_flags & sidebar->open_flags) == 0) + open_flags = GTK_PLACES_OPEN_NORMAL; + + g_signal_emit (sidebar, places_sidebar_signals[OPEN_LOCATION], 0, + location, open_flags); +} + +static void +emit_populate_popup (GtkPlacesSidebar *sidebar, GtkMenu *menu, GFile *selected_item) +{ + g_signal_emit (sidebar, places_sidebar_signals[POPULATE_POPUP], 0, + menu, selected_item); +} + +static void +emit_show_error_message (GtkPlacesSidebar *sidebar, const char *primary, const char *secondary) +{ + g_signal_emit (sidebar, places_sidebar_signals[SHOW_ERROR_MESSAGE], 0, + primary, secondary); +} + +static GdkDragAction +emit_drag_action_requested (GtkPlacesSidebar *sidebar, + GdkDragContext *context, + GFile *dest_file, + GList *source_file_list) +{ + GdkDragAction ret_action; + + ret_action = 0; + + g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_REQUESTED], 0, + context, + dest_file, + source_file_list, + &ret_action); + + return ret_action; +} + +static GdkDragAction +emit_drag_action_ask (GtkPlacesSidebar *sidebar, + GdkDragAction actions) +{ + GdkDragAction ret_action; + + ret_action = 0; + + g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_ASK], 0, + actions, + &ret_action); + return ret_action; +} + +static void +emit_drag_perform_drop (GtkPlacesSidebar *sidebar, + GFile *dest_file, + GList *source_file_list, + GdkDragAction action) +{ + g_signal_emit (sidebar, places_sidebar_signals[DRAG_PERFORM_DROP], 0, + dest_file, + source_file_list, + action); +} + +static gint +get_icon_size (GtkPlacesSidebar *sidebar) +{ + GtkSettings *settings; + gint width, height; + + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (sidebar))); + + if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU, &width, &height)) + return MAX (width, height); + else + return 16; +} + +static GtkTreeIter +add_heading (GtkPlacesSidebar *sidebar, + SectionType section_type, + const gchar *title) +{ + GtkTreeIter iter; + + gtk_list_store_append (sidebar->store, &iter); + gtk_list_store_set (sidebar->store, &iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, PLACES_HEADING, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, section_type, + PLACES_SIDEBAR_COLUMN_HEADING_TEXT, title, + PLACES_SIDEBAR_COLUMN_EJECT, FALSE, + PLACES_SIDEBAR_COLUMN_NO_EJECT, TRUE, + -1); + + return iter; +} + +static void +check_heading_for_section (GtkPlacesSidebar *sidebar, + SectionType section_type) +{ + switch (section_type) { + case SECTION_DEVICES: + if (!sidebar->devices_header_added) { + add_heading (sidebar, SECTION_DEVICES, + _("Devices")); + sidebar->devices_header_added = TRUE; + } + + break; + case SECTION_BOOKMARKS: + if (!sidebar->bookmarks_header_added) { + add_heading (sidebar, SECTION_BOOKMARKS, + _("Bookmarks")); + sidebar->bookmarks_header_added = TRUE; + } + + break; + default: + break; + } +} + +static void +add_place (GtkPlacesSidebar *sidebar, + PlaceType place_type, + SectionType section_type, + const char *name, + GIcon *icon, + const char *uri, + GDrive *drive, + GVolume *volume, + GMount *mount, + const int index, + const char *tooltip) +{ + GtkTreeIter iter; + gboolean show_eject, show_unmount; + gboolean show_eject_button; + + check_heading_for_section (sidebar, section_type); + + 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_GICON, icon, + 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_TOOLTIP, tooltip, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, section_type, + -1); +} + +static GIcon * +special_directory_get_gicon (GUserDirectory directory) +{ +#define ICON_CASE(x) \ + case G_USER_DIRECTORY_ ## x: \ + return g_themed_icon_new (ICON_NAME_FOLDER_ ## x); + + switch (directory) { + + ICON_CASE (DOCUMENTS); + ICON_CASE (DOWNLOAD); + ICON_CASE (MUSIC); + ICON_CASE (PICTURES); + ICON_CASE (PUBLIC_SHARE); + ICON_CASE (TEMPLATES); + ICON_CASE (VIDEOS); + + default: + return g_themed_icon_new ("folder-symbolic"); + } + +#undef ICON_CASE +} + +static gboolean +recent_files_setting_is_enabled (GtkPlacesSidebar *sidebar) +{ + GtkSettings *settings; + gboolean enabled; + + if (gtk_widget_has_screen (GTK_WIDGET (sidebar))) + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (sidebar))); + else + settings = gtk_settings_get_default (); + + g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL); + return enabled; +} + +static gboolean +recent_scheme_is_supported (void) +{ + const char * const *supported; + int i; + + supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + if (!supported) { + return FALSE; + } + + for (i = 0; supported[i] != NULL; i++) { + if (strcmp ("recent", supported[i]) == 0) { + return TRUE; + } + } + return FALSE; +} + +static gboolean +should_show_recent (GtkPlacesSidebar *sidebar) +{ + return recent_files_setting_is_enabled (sidebar) && recent_scheme_is_supported (); +} + +static void +add_special_dirs (GtkPlacesSidebar *sidebar) +{ + GList *dirs; + int index; + + dirs = NULL; + for (index = 0; index < G_USER_N_DIRECTORIES; index++) { + const char *path; + GFile *root; + GIcon *icon; + char *name; + char *mount_uri; + char *tooltip; + + if (!_gtk_bookmarks_manager_get_is_xdg_dir_builtin (index)) { + continue; + } + + path = g_get_user_special_dir (index); + + /* xdg resets special dirs to the home directory in case + * it's not finiding what it expects. We don't want the home + * to be added multiple times in that weird configuration. + */ + if (path == NULL + || g_strcmp0 (path, g_get_home_dir ()) == 0 + || g_list_find_custom (dirs, path, (GCompareFunc) g_strcmp0) != NULL) { + continue; + } + + root = g_file_new_for_path (path); + + name = _gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root); + if (!name) { + name = g_file_get_basename (root); + } + + icon = special_directory_get_gicon (index); + mount_uri = g_file_get_uri (root); + tooltip = g_file_get_parse_name (root); + + add_place (sidebar, PLACES_XDG_DIR, + SECTION_COMPUTER, + name, icon, mount_uri, + NULL, NULL, NULL, 0, + tooltip); + g_free (name); + g_object_unref (root); + g_object_unref (icon); + g_free (mount_uri); + g_free (tooltip); + + dirs = g_list_prepend (dirs, (char *)path); + } + + g_list_free (dirs); +} + +static char * +get_home_directory_uri (void) +{ + const char *home; + + home = g_get_home_dir (); + if (!home) + return NULL; + + return g_strconcat ("file://", home, NULL); +} + +static char * +get_desktop_directory_uri (void) +{ + const char *name; + + name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); + /* "To disable a directory, point it to the homedir." + * See http://freedesktop.org/wiki/Software/xdg-user-dirs + **/ + if (!g_strcmp0 (name, g_get_home_dir ())) { + return NULL; + } else { + return g_strconcat ("file://", name, NULL); + } +} + +static void +add_application_shortcuts (GtkPlacesSidebar *sidebar) +{ + GSList *l; + + for (l = sidebar->shortcuts; l; l = l->next) { + GFile *file; + GFileInfo *info; + + file = G_FILE (l->data); + + /* FIXME: we are getting file info synchronously. We may want to do it async at some point. */ + info = g_file_query_info (file, + "standard::display-name,standard::symbolic-icon", + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); /* NULL-GError */ + + if (info) { + char *uri; + char *tooltip; + const char *name; + GIcon *icon; + + name = g_file_info_get_display_name (info); + icon = g_file_info_get_symbolic_icon (info); + + uri = g_file_get_uri (file); + tooltip = g_file_get_parse_name (file); + + add_place (sidebar, PLACES_BUILT_IN, + SECTION_COMPUTER, + name, icon, uri, + NULL, NULL, NULL, 0, + tooltip); + + g_free (uri); + g_free (tooltip); + + g_object_unref (info); + } + } +} + +static gboolean +get_selected_iter (GtkPlacesSidebar *sidebar, + GtkTreeIter *iter) +{ + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (sidebar->tree_view); + + return gtk_tree_selection_get_selected (selection, NULL, iter); +} + +static void +update_places (GtkPlacesSidebar *sidebar) +{ + GtkTreeIter iter; + GVolumeMonitor *volume_monitor; + GList *mounts, *l, *ll; + GMount *mount; + GList *drives; + GDrive *drive; + GList *volumes; + GVolume *volume; + GSList *bookmarks, *sl; + int index; + char *original_uri, *mount_uri, *name, *identifier; + char *home_uri; + char *bookmark_name; + GIcon *icon; + GFile *root; + char *tooltip; + GList *network_mounts, *network_volumes; + + /* save original selection */ + if (get_selected_iter (sidebar, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), + &iter, + PLACES_SIDEBAR_COLUMN_URI, &original_uri, -1); + } else + original_uri = NULL; + + gtk_list_store_clear (sidebar->store); + + sidebar->devices_header_added = FALSE; + sidebar->bookmarks_header_added = FALSE; + + network_mounts = network_volumes = NULL; + volume_monitor = sidebar->volume_monitor; + + /* add built in bookmarks */ + + add_heading (sidebar, SECTION_COMPUTER, + _("Places")); + + if (should_show_recent (sidebar)) { + mount_uri = "recent:///"; /* No need to strdup */ + icon = g_themed_icon_new ("document-open-recent-symbolic"); + add_place (sidebar, PLACES_BUILT_IN, + SECTION_COMPUTER, + _("Recent"), icon, mount_uri, + NULL, NULL, NULL, 0, + _("Recent files")); + g_object_unref (icon); + } + + /* home folder */ + home_uri = get_home_directory_uri (); + icon = g_themed_icon_new (ICON_NAME_HOME); + add_place (sidebar, PLACES_BUILT_IN, + SECTION_COMPUTER, + _("Home"), icon, + home_uri, NULL, NULL, NULL, 0, + _("Open your personal folder")); + g_object_unref (icon); + g_free (home_uri); + + if (sidebar->show_desktop) { + /* desktop */ + mount_uri = get_desktop_directory_uri (); + icon = g_themed_icon_new (ICON_NAME_DESKTOP); + add_place (sidebar, PLACES_BUILT_IN, + SECTION_COMPUTER, + _("Desktop"), icon, + mount_uri, NULL, NULL, NULL, 0, + _("Open the contents of your desktop in a folder")); + g_object_unref (icon); + g_free (mount_uri); + } + + /* XDG directories */ + add_special_dirs (sidebar); + + /* Trash */ + mount_uri = "trash:///"; /* No need to strdup */ + icon = _gtk_trash_monitor_get_icon (sidebar->trash_monitor); + add_place (sidebar, PLACES_BUILT_IN, + SECTION_COMPUTER, + _("Trash"), icon, mount_uri, + NULL, NULL, NULL, 0, + _("Open the trash")); + g_object_unref (icon); + + /* Application-side shortcuts */ + add_application_shortcuts (sidebar); + + /* 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; + identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + + if (g_strcmp0 (identifier, "network") == 0) { + g_free (identifier); + network_volumes = g_list_prepend (network_volumes, volume); + continue; + } + g_free (identifier); + + mount = g_volume_get_mount (volume); + if (mount != NULL) { + /* Show mounted volume in the sidebar */ + icon = g_mount_get_symbolic_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); + + add_place (sidebar, PLACES_MOUNTED_VOLUME, + SECTION_DEVICES, + name, icon, mount_uri, + drive, volume, mount, 0, tooltip); + 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_symbolic_icon (volume); + name = g_volume_get_name (volume); + tooltip = g_strdup_printf (_("Mount and open %s"), name); + + add_place (sidebar, PLACES_MOUNTED_VOLUME, + SECTION_DEVICES, + 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_symbolic_icon (drive); + name = g_drive_get_name (drive); + tooltip = g_strdup_printf (_("Mount and open %s"), name); + + add_place (sidebar, PLACES_BUILT_IN, + SECTION_DEVICES, + 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; + } + + identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + + if (g_strcmp0 (identifier, "network") == 0) { + g_free (identifier); + network_volumes = g_list_prepend (network_volumes, volume); + continue; + } + g_free (identifier); + + mount = g_volume_get_mount (volume); + if (mount != NULL) { + icon = g_mount_get_symbolic_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); + add_place (sidebar, PLACES_MOUNTED_VOLUME, + SECTION_DEVICES, + name, icon, mount_uri, + NULL, volume, mount, 0, tooltip); + 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_symbolic_icon (volume); + name = g_volume_get_name (volume); + add_place (sidebar, PLACES_MOUNTED_VOLUME, + SECTION_DEVICES, + name, icon, NULL, + NULL, volume, NULL, 0, name); + g_object_unref (icon); + g_free (name); + } + g_object_unref (volume); + } + g_list_free (volumes); + + /* file system root */ + + mount_uri = "file:///"; /* No need to strdup */ + icon = g_themed_icon_new (ICON_NAME_FILESYSTEM); + add_place (sidebar, PLACES_BUILT_IN, + SECTION_DEVICES, + sidebar->hostname, icon, + mount_uri, NULL, NULL, NULL, 0, + _("Open the contents of the File System")); + g_object_unref (icon); + + /* 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; + } + root = g_mount_get_default_location (mount); + + if (!g_file_is_native (root)) { + network_mounts = g_list_prepend (network_mounts, mount); + g_object_unref (root); + continue; + } + + icon = g_mount_get_symbolic_icon (mount); + mount_uri = g_file_get_uri (root); + name = g_mount_get_name (mount); + tooltip = g_file_get_parse_name (root); + add_place (sidebar, PLACES_MOUNTED_VOLUME, + SECTION_COMPUTER, + name, icon, mount_uri, + NULL, NULL, mount, 0, tooltip); + 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); + + /* add bookmarks */ + + bookmarks = _gtk_bookmarks_manager_list_bookmarks (sidebar->bookmarks_manager); + + for (sl = bookmarks, index = 0; sl; sl = sl->next, index++) { + GFileInfo *info; + + root = sl->data; + +#if 0 + /* FIXME: remove this? If we *do* show bookmarks for nonexistent files, the user will eventually clean them up */ + if (!nautilus_bookmark_get_exists (bookmark)) { + continue; + } +#endif + + if (_gtk_bookmarks_manager_get_is_builtin (sidebar->bookmarks_manager, root)) { + continue; + } + + /* FIXME: we are getting file info synchronously. We may want to do it async at some point. */ + info = g_file_query_info (root, + "standard::display-name,standard::symbolic-icon", + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); /* NULL-GError */ + + if (info) { + bookmark_name = _gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root); + + if (bookmark_name == NULL) + bookmark_name = g_strdup (g_file_info_get_display_name (info)); + + icon = g_file_info_get_symbolic_icon (info); + + mount_uri = g_file_get_uri (root); + tooltip = g_file_get_parse_name (root); + + add_place (sidebar, PLACES_BOOKMARK, + SECTION_BOOKMARKS, + bookmark_name, icon, mount_uri, + NULL, NULL, NULL, index, + tooltip); + + g_free (mount_uri); + g_free (tooltip); + g_free (bookmark_name); + + g_object_unref (info); + } + } + + g_slist_foreach (bookmarks, (GFunc) g_object_unref, NULL); + g_slist_free (bookmarks); + + /* network */ + add_heading (sidebar, SECTION_NETWORK, + _("Network")); + + mount_uri = "network:///"; /* No need to strdup */ + icon = g_themed_icon_new (ICON_NAME_NETWORK); + add_place (sidebar, PLACES_BUILT_IN, + SECTION_NETWORK, + _("Browse Network"), icon, + mount_uri, NULL, NULL, NULL, 0, + _("Browse the contents of the network")); + g_object_unref (icon); + + network_volumes = g_list_reverse (network_volumes); + for (l = network_volumes; l != NULL; l = l->next) { + volume = l->data; + mount = g_volume_get_mount (volume); + + if (mount != NULL) { + network_mounts = g_list_prepend (network_mounts, mount); + continue; + } else { + icon = g_volume_get_symbolic_icon (volume); + name = g_volume_get_name (volume); + tooltip = g_strdup_printf (_("Mount and open %s"), name); + + add_place (sidebar, PLACES_MOUNTED_VOLUME, + SECTION_NETWORK, + name, icon, NULL, + NULL, volume, NULL, 0, tooltip); + g_object_unref (icon); + g_free (name); + g_free (tooltip); + } + } + + g_list_free_full (network_volumes, g_object_unref); + + network_mounts = g_list_reverse (network_mounts); + for (l = network_mounts; l != NULL; l = l->next) { + mount = l->data; + root = g_mount_get_default_location (mount); + icon = g_mount_get_symbolic_icon (mount); + mount_uri = g_file_get_uri (root); + name = g_mount_get_name (mount); + tooltip = g_file_get_parse_name (root); + add_place (sidebar, PLACES_MOUNTED_VOLUME, + SECTION_NETWORK, + name, icon, mount_uri, + NULL, NULL, mount, 0, tooltip); + 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_full (network_mounts, g_object_unref); + + /* restore original selection */ + if (original_uri) { + GFile *restore; + + restore = g_file_new_for_uri (original_uri); + gtk_places_sidebar_set_location (sidebar, restore); + g_object_unref (restore); + + g_free (original_uri); + } +} + +static void +mount_added_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +mount_removed_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +mount_changed_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +volume_added_callback (GVolumeMonitor *volume_monitor, + GVolume *volume, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +volume_removed_callback (GVolumeMonitor *volume_monitor, + GVolume *volume, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +volume_changed_callback (GVolumeMonitor *volume_monitor, + GVolume *volume, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +drive_disconnected_callback (GVolumeMonitor *volume_monitor, + GDrive *drive, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +drive_connected_callback (GVolumeMonitor *volume_monitor, + GDrive *drive, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +drive_changed_callback (GVolumeMonitor *volume_monitor, + GDrive *drive, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static gboolean +over_eject_button (GtkPlacesSidebar *sidebar, + gint x, + gint y, + GtkTreePath **path) +{ + GtkTreeViewColumn *column; + int width, x_offset, hseparator; + int eject_button_size; + gboolean show_eject; + GtkTreeIter iter; + GtkTreeModel *model; + + *path = NULL; + model = gtk_tree_view_get_model (sidebar->tree_view); + + if (gtk_tree_view_get_path_at_pos (sidebar->tree_view, + x, 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; + } + + + gtk_widget_style_get (GTK_WIDGET (sidebar->tree_view), + "horizontal-separator", &hseparator, + NULL); + + /* Reload cell attributes for this particular row */ + gtk_tree_view_column_cell_set_cell_data (column, + model, &iter, FALSE, FALSE); + + gtk_tree_view_column_cell_get_position (column, + sidebar->eject_icon_cell_renderer, + &x_offset, &width); + + eject_button_size = get_icon_size (sidebar); + + /* This is kinda weird, but we have to do it to workaround gtk+ expanding + * the eject cell renderer (even thought we told it not to) and we then + * had to set it right-aligned */ + x_offset += width - hseparator - EJECT_BUTTON_XPAD - eject_button_size; + + if (x - x_offset >= 0 && + x - x_offset <= eject_button_size) { + return TRUE; + } + } + + out: + if (*path != NULL) { + gtk_tree_path_free (*path); + *path = NULL; + } + + return FALSE; +} + +static gboolean +clicked_eject_button (GtkPlacesSidebar *sidebar, + GtkTreePath **path) +{ + GdkEvent *event = gtk_get_current_event (); + GdkEventButton *button_event = (GdkEventButton *) event; + + if ((event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) && + over_eject_button (sidebar, button_event->x, button_event->y, path)) { + return TRUE; + } + + return FALSE; +} + +static gboolean +pos_is_into_or_before (GtkTreeViewDropPosition pos) +{ + return (pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE); +} + +/* Computes the appropriate row and position for dropping */ +static gboolean +compute_drop_position (GtkTreeView *tree_view, + int x, + int y, + GtkTreePath **path, + GtkTreeViewDropPosition *pos, + GtkPlacesSidebar *sidebar) +{ + GtkTreeModel *model; + GtkTreeIter iter; + PlaceType place_type; + SectionType section_type; + gboolean drop_possible; + + if (!gtk_tree_view_get_dest_row_at_pos (tree_view, + x, y, + path, pos)) { + return FALSE; + } + + model = gtk_tree_view_get_model (tree_view); + + gtk_tree_model_get_iter (model, &iter, *path); + gtk_tree_model_get (model, &iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, §ion_type, + -1); + + drop_possible = TRUE; + + /* Never drop on headings, but special case the bookmarks heading, + * so we can drop bookmarks in between it and the first bookmark. + */ + if (place_type == PLACES_HEADING + && section_type != SECTION_BOOKMARKS) + drop_possible = FALSE; + + /* Dragging a bookmark? */ + if (sidebar->drag_data_received + && sidebar->drag_data_info == GTK_TREE_MODEL_ROW) { + /* Don't allow reordering bookmarks into non-bookmark areas */ + if (section_type != SECTION_BOOKMARKS) + drop_possible = FALSE; + + /* Bookmarks can only be reordered. Disallow dropping directly into them; only allow dropping between them. */ + if (place_type == PLACES_HEADING) { + if (pos_is_into_or_before (*pos)) + drop_possible = FALSE; + else + *pos = GTK_TREE_VIEW_DROP_AFTER; + } else { + if (pos_is_into_or_before (*pos)) + *pos = GTK_TREE_VIEW_DROP_BEFORE; + else + *pos = GTK_TREE_VIEW_DROP_AFTER; + } + } else { + /* Dragging a file */ + + /* Outside the bookmarks section, URIs can only be dropped + * directly into places items. Inside the bookmarks section, + * they can be dropped between items (to create new bookmarks) + * or in items themselves (to request a move/copy file + * operation). + */ + if (section_type != SECTION_BOOKMARKS) + *pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE; + else { + if (place_type == PLACES_HEADING) { + if (pos_is_into_or_before (*pos)) + drop_possible = FALSE; + else + *pos = GTK_TREE_VIEW_DROP_AFTER; + } + } + } + + /* Disallow drops on recent:/// */ + if (place_type == PLACES_BUILT_IN) { + char *uri; + + gtk_tree_model_get (model, &iter, + PLACES_SIDEBAR_COLUMN_URI, &uri, + -1); + + if (strcmp (uri, "recent:///") == 0) + drop_possible = FALSE; + + g_free (uri); + } + + if (!drop_possible) { + gtk_tree_path_free (*path); + *path = NULL; + + return FALSE; + } + + return TRUE; +} + +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 (GtkPlacesSidebar *sidebar) +{ + sidebar->drag_data_received = FALSE; + + if (sidebar->drag_list) { + g_list_free_full (sidebar->drag_list, g_object_unref); + sidebar->drag_list = NULL; + } +} + +static gboolean +drag_motion_callback (GtkTreeView *tree_view, + GdkDragContext *context, + int x, + int y, + unsigned int time, + GtkPlacesSidebar *sidebar) +{ + GtkTreePath *path; + GtkTreeViewDropPosition pos; + int action; + GtkTreeIter iter; + gboolean res; + + action = 0; + + if (!sidebar->drag_data_received) { + if (!get_drag_data (tree_view, context, time)) { + goto out; + } + } + + path = NULL; + res = compute_drop_position (tree_view, x, y, &path, &pos, sidebar); + + if (!res) { + goto out; + } + + if (sidebar->drag_data_received && + sidebar->drag_data_info == GTK_TREE_MODEL_ROW) { + /* Dragging bookmarks always moves them to another position in the bookmarks list */ + action = GDK_ACTION_MOVE; + } else { + /* URIs are being dragged. See if the caller wants to handle a + * file move/copy operation itself, or if we should only try to + * create bookmarks out of the dragged URIs. + */ + if (sidebar->drag_list != NULL) { + SectionType section_type; + PlaceType place_type; + gboolean drop_as_bookmarks; + + 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_SECTION_TYPE, §ion_type, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type, + -1); + + drop_as_bookmarks = FALSE; + + if (section_type == SECTION_BOOKMARKS) { + if (pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER) { + action = GDK_ACTION_COPY; + drop_as_bookmarks = TRUE; + } + } + + if (!drop_as_bookmarks) { + char *uri; + + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), + &iter, + PLACES_SIDEBAR_COLUMN_URI, &uri, + -1); + + if (uri != NULL) { + GFile *dest_file = g_file_new_for_uri (uri); + + action = emit_drag_action_requested (sidebar, context, dest_file, sidebar->drag_list); + + g_object_unref (dest_file); + g_free (uri); + } /* uri may be NULL for unmounted volumes, for example, so we don't allow drops there */ + } + } + } + + out: + if (action != 0) + gtk_tree_view_set_drag_dest_row (tree_view, path, pos); + else + gtk_tree_view_set_drag_dest_row (tree_view, NULL, pos); + + if (path != NULL) { + gtk_tree_path_free (path); + } + + g_signal_stop_emission_by_name (tree_view, "drag-motion"); + + gdk_drag_status (context, action, time); + + return TRUE; +} + +static void +drag_leave_callback (GtkTreeView *tree_view, + GdkDragContext *context, + unsigned int time, + GtkPlacesSidebar *sidebar) +{ + free_drag_data (sidebar); + gtk_tree_view_set_drag_dest_row (tree_view, NULL, 0); + g_signal_stop_emission_by_name (tree_view, "drag-leave"); +} + +/* Takes an array of URIs and turns it into a list of GFile */ +static GList * +build_file_list_from_uris (const char **uris) +{ + GList *result; + int i; + + result = NULL; + for (i = 0; uris[i]; i++) { + GFile *file; + + file = g_file_new_for_uri (uris[i]); + result = g_list_prepend (result, file); + } + + return g_list_reverse (result); +} + +/* Reorders the selected bookmark to the specified position */ +static void +reorder_bookmarks (GtkPlacesSidebar *sidebar, + int new_position) +{ + GtkTreeIter iter; + char *uri; + GFile *file; + + /* Get the selected path */ + if (!get_selected_iter (sidebar, &iter)) { + return; + } + + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter, + PLACES_SIDEBAR_COLUMN_URI, &uri, + -1); + + file = g_file_new_for_uri (uri); + _gtk_bookmarks_manager_reorder_bookmark (sidebar->bookmarks_manager, file, new_position, NULL); /* NULL-GError */ + + g_object_unref (file); + g_free (uri); +} + +/* Creates bookmarks for the specified files at the given position in the bookmarks list */ +static void +drop_files_as_bookmarks (GtkPlacesSidebar *sidebar, + GList *files, + int position) +{ + GList *l; + + for (l = files; l; l = l->next) { + GFile *f = G_FILE (l->data); + + _gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, f, position++, NULL); /* NULL-GError */ + } +} + +static void +drag_data_received_callback (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + GtkSelectionData *selection_data, + unsigned int info, + unsigned int time, + GtkPlacesSidebar *sidebar) +{ + GtkTreeView *tree_view; + GtkTreePath *tree_path; + GtkTreeViewDropPosition tree_pos; + GtkTreeIter iter; + int position; + GtkTreeModel *model; + PlaceType place_type; + SectionType section_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) { + char **uris; + + uris = gtk_selection_data_get_uris (selection_data); + sidebar->drag_list = build_file_list_from_uris ((const char **) uris); + g_strfreev (uris); + } 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 */ + success = compute_drop_position (tree_view, x, y, &tree_path, &tree_pos, sidebar); + if (!success) { + goto out; + } + + success = FALSE; + + if (sidebar->drag_data_info == GTK_TREE_MODEL_ROW) { + /* A bookmark got reordered */ + + 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_SECTION_TYPE, §ion_type, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type, + PLACES_SIDEBAR_COLUMN_INDEX, &position, + -1); + + if (section_type != SECTION_BOOKMARKS) { + goto out; + } + + if (place_type == PLACES_HEADING) + position = 0; + else if (tree_pos == GTK_TREE_VIEW_DROP_AFTER) + position++; + + reorder_bookmarks (sidebar, position); + success = TRUE; + } else { + /* Dropping URIs! */ + + GdkDragAction real_action; + char **uris; + GList *source_file_list; + + /* file transfer requested */ + real_action = gdk_drag_context_get_selected_action (context); + + if (real_action == GDK_ACTION_ASK) + real_action = emit_drag_action_ask (sidebar, gdk_drag_context_get_actions (context)); + + if (real_action > 0) { + char *uri; + GFile *dest_file; + gboolean drop_as_bookmarks; + + 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_SECTION_TYPE, §ion_type, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type, + PLACES_SIDEBAR_COLUMN_INDEX, &position, + -1); + + drop_as_bookmarks = FALSE; + + uris = gtk_selection_data_get_uris (selection_data); + source_file_list = build_file_list_from_uris ((const char **) uris); + + if (section_type == SECTION_BOOKMARKS) { + if (place_type == PLACES_HEADING) { + position = 0; + tree_pos = GTK_TREE_VIEW_DROP_BEFORE; + } + + if (tree_pos == GTK_TREE_VIEW_DROP_AFTER) + position++; + + if (tree_pos == GTK_TREE_VIEW_DROP_BEFORE + || tree_pos == GTK_TREE_VIEW_DROP_AFTER) { + drop_files_as_bookmarks (sidebar, source_file_list, position); + success = TRUE; + drop_as_bookmarks = TRUE; + } + } + + if (!drop_as_bookmarks) { + gtk_tree_model_get_iter (model, &iter, tree_path); + gtk_tree_model_get (model, &iter, + PLACES_SIDEBAR_COLUMN_URI, &uri, + -1); + + dest_file = g_file_new_for_uri (uri); + + emit_drag_perform_drop (sidebar, dest_file, source_file_list, real_action); + success = TRUE; + + g_object_unref (dest_file); + g_free (uri); + } + + g_list_free_full (source_file_list, g_object_unref); + g_strfreev (uris); + } + } + +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, + GtkPlacesSidebar *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) +{ + GtkPlacesSidebar *sidebar; + + sidebar = GTK_PLACES_SIDEBAR (attach_widget); + + sidebar->popup_menu = 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_start, + gboolean *show_stop) +{ + *show_mount = 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); + } +} + +typedef struct { + PlaceType type; + GDrive *drive; + GVolume *volume; + GMount *mount; + char *uri; +} SelectionInfo; + +static void +get_selection_info (GtkPlacesSidebar *sidebar, SelectionInfo *info) +{ + GtkTreeIter iter; + + info->type = PLACES_BUILT_IN; + info->drive = NULL; + info->volume = NULL; + info->mount = NULL; + info->uri = NULL; + + if (get_selected_iter (sidebar, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &info->type, + PLACES_SIDEBAR_COLUMN_DRIVE, &info->drive, + PLACES_SIDEBAR_COLUMN_VOLUME, &info->volume, + PLACES_SIDEBAR_COLUMN_MOUNT, &info->mount, + PLACES_SIDEBAR_COLUMN_URI, &info->uri, + -1); + } +} + +static void +free_selection_info (SelectionInfo *info) +{ + g_clear_object (&info->drive); + g_clear_object (&info->volume); + g_clear_object (&info->mount); + + g_clear_pointer (&info->uri, g_free); +} + +typedef struct { + GtkWidget *add_shortcut_item; + GtkWidget *remove_item; + GtkWidget *rename_item; + GtkWidget *separator_item; + GtkWidget *mount_item; + GtkWidget *unmount_item; + GtkWidget *eject_item; + GtkWidget *rescan_item; + GtkWidget *start_item; + GtkWidget *stop_item; +} PopupMenuData; + +static void +check_popup_sensitivity (GtkPlacesSidebar *sidebar, PopupMenuData *data, SelectionInfo *info) +{ + gboolean show_mount; + gboolean show_unmount; + gboolean show_eject; + gboolean show_rescan; + gboolean show_start; + gboolean show_stop; + + gtk_widget_set_visible (data->add_shortcut_item, (info->type == PLACES_MOUNTED_VOLUME)); + + gtk_widget_set_sensitive (data->remove_item, (info->type == PLACES_BOOKMARK)); + gtk_widget_set_sensitive (data->rename_item, (info->type == PLACES_BOOKMARK || info->type == PLACES_XDG_DIR)); + + check_visibility (info->mount, info->volume, info->drive, + &show_mount, &show_unmount, &show_eject, &show_rescan, &show_start, &show_stop); + + gtk_widget_set_visible (data->separator_item, show_mount || show_unmount || show_eject); + gtk_widget_set_visible (data->mount_item, show_mount); + gtk_widget_set_visible (data->unmount_item, show_unmount); + gtk_widget_set_visible (data->eject_item, show_eject); + gtk_widget_set_visible (data->rescan_item, show_rescan); + gtk_widget_set_visible (data->start_item, show_start); + gtk_widget_set_visible (data->stop_item, show_stop); + + /* Adjust start/stop items to reflect the type of the drive */ + gtk_menu_item_set_label (GTK_MENU_ITEM (data->start_item), _("_Start")); + gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Stop")); + if ((show_start || show_stop) && info->drive != NULL) { + switch (g_drive_get_start_stop_type (info->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 (data->start_item), _("_Power On")); + gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Safely Remove Drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_menu_item_set_label (GTK_MENU_ITEM (data->start_item), _("_Connect Drive")); + gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Disconnect Drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_menu_item_set_label (GTK_MENU_ITEM (data->start_item), _("_Start Multi-disk Device")); + gtk_menu_item_set_label (GTK_MENU_ITEM (data->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 (data->start_item), _("_Unlock Drive")); + gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Lock Drive")); + break; + + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + /* uses defaults set above */ + break; + } + } +} + +static void +drive_start_from_bookmark_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = GTK_PLACES_SIDEBAR (user_data); + + 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); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } +} + +/* Callback from g_volume_mount() */ +static void +volume_mount_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) +{ + GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data); + GVolume *volume; + GError *error; + char *primary; + char *name; + GMount *mount; + + volume = G_VOLUME (source_object); + + error = NULL; + if (!g_volume_mount_finish (volume, result, &error)) { + if (error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED) { + name = g_volume_get_name (G_VOLUME (source_object)); + primary = g_strdup_printf (_("Unable to access “%s”"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + sidebar->mounting = FALSE; + + mount = g_volume_get_mount (volume); + if (mount != NULL) { + GFile *location; + + location = g_mount_get_default_location (mount); + emit_open_location (sidebar, location, sidebar->go_to_after_mount_open_flags); + + g_object_unref (G_OBJECT (location)); + g_object_unref (G_OBJECT (mount)); + } + + g_object_unref (sidebar); +} + +/* This was nautilus_file_operations_mount_volume_full() */ +static void +mount_volume (GtkPlacesSidebar *sidebar, GVolume *volume) +{ + GMountOperation *mount_op; + + mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar)))); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + + g_object_ref (sidebar); + g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, sidebar); +} + +static void +open_selected_bookmark (GtkPlacesSidebar *sidebar, + GtkTreeModel *model, + GtkTreeIter *iter, + GtkPlacesOpenFlags open_flags) +{ + GFile *location; + char *uri; + + if (!iter) { + return; + } + + gtk_tree_model_get (model, iter, PLACES_SIDEBAR_COLUMN_URI, &uri, -1); + + if (uri != NULL) { + location = g_file_new_for_uri (uri); + emit_open_location (sidebar, location, open_flags); + + g_object_unref (location); + g_free (uri); + + } else { + GDrive *drive; + GVolume *volume; + + 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; + + sidebar->go_to_after_mount_open_flags = open_flags; + + mount_volume (sidebar, volume); + } 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, sidebar); + g_object_unref (mount_op); + } + + g_clear_object (&drive); + g_clear_object (&volume); + } +} + +static void +open_shortcut_from_menu (GtkPlacesSidebar *sidebar, + GtkPlacesOpenFlags open_flags) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path = NULL; + + model = gtk_tree_view_get_model (sidebar->tree_view); + gtk_tree_view_get_cursor (sidebar->tree_view, &path, NULL); + + if (path != NULL && gtk_tree_model_get_iter (model, &iter, path)) { + open_selected_bookmark (sidebar, model, &iter, open_flags); + } + + gtk_tree_path_free (path); +} + +/* Callback used for the "Open" menu item in the context menu */ +static void +open_shortcut_cb (GtkMenuItem *item, + GtkPlacesSidebar *sidebar) +{ + open_shortcut_from_menu (sidebar, GTK_PLACES_OPEN_NORMAL); +} + +/* Callback used for the "Open in new tab" menu item in the context menu */ +static void +open_shortcut_in_new_tab_cb (GtkMenuItem *item, + GtkPlacesSidebar *sidebar) +{ + open_shortcut_from_menu (sidebar, GTK_PLACES_OPEN_NEW_TAB); +} + +/* Callback used for the "Open in new window" menu item in the context menu */ +static void +open_shortcut_in_new_window_cb (GtkMenuItem *item, + GtkPlacesSidebar *sidebar) +{ + open_shortcut_from_menu (sidebar, GTK_PLACES_OPEN_NEW_WINDOW); +} + +/* Add bookmark for the selected item - just used from mount points */ +static void +add_shortcut_cb (GtkMenuItem *item, + GtkPlacesSidebar *sidebar) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *uri; + char *name; + GFile *location; + + model = gtk_tree_view_get_model (sidebar->tree_view); + + if (get_selected_iter (sidebar, &iter)) { + gtk_tree_model_get (model, &iter, + PLACES_SIDEBAR_COLUMN_URI, &uri, + PLACES_SIDEBAR_COLUMN_NAME, &name, + -1); + + if (uri == NULL) { + return; + } + + location = g_file_new_for_uri (uri); + if (_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, location, -1, NULL)) + _gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, location, name, NULL); + + g_object_unref (location); + g_free (uri); + g_free (name); + } +} + +/* Rename the selected bookmark */ +static void +rename_selected_bookmark (GtkPlacesSidebar *sidebar) +{ + GtkTreeIter iter; + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GList *renderers; + PlaceType type; + + if (get_selected_iter (sidebar, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type, + -1); + + if (type != PLACES_BOOKMARK && type != PLACES_XDG_DIR) { + return; + } + + 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, 6); + 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, + GtkPlacesSidebar *sidebar) +{ + rename_selected_bookmark (sidebar); +} + +/* Removes the selected bookmarks */ +static void +remove_selected_bookmarks (GtkPlacesSidebar *sidebar) +{ + GtkTreeIter iter; + PlaceType type; + char *uri; + GFile *file; + + 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_URI, &uri, + -1); + + file = g_file_new_for_uri (uri); + _gtk_bookmarks_manager_remove_bookmark (sidebar->bookmarks_manager, file, NULL); + + g_object_unref (file); + g_free (uri); +} + +static void +remove_shortcut_cb (GtkMenuItem *item, + GtkPlacesSidebar *sidebar) +{ + remove_selected_bookmarks (sidebar); +} + +static void +mount_shortcut_cb (GtkMenuItem *item, + GtkPlacesSidebar *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) { + mount_volume (sidebar, volume); + g_object_unref (volume); + } +} + +/* Callback used from g_mount_unmount_with_operation() */ +static void +unmount_mount_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data); + GMount *mount; + GError *error; + + mount = G_MOUNT (source_object); + + error = NULL; + if (!g_mount_unmount_with_operation_finish (mount, result, &error)) { + if (error->code != G_IO_ERROR_FAILED_HANDLED) { + char *name; + char *primary; + + name = g_mount_get_name (mount); + primary = g_strdup_printf (_("Unable to unmount %s"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + + g_error_free (error); + } + + /* FIXME: we need to switch to a path that is available now - $HOME? */ + + g_object_unref (sidebar); +} + +static void +show_unmount_progress_cb (GMountOperation *op, + const gchar *message, + gint64 time_left, + gint64 bytes_left, + gpointer user_data) +{ + /* FIXME: These are just libnotify notifications, but GTK+ doesn't do notifications right now. + * Should we just call D-Bus directly? + */ +#if DO_NOT_COMPILE + NautilusApplication *app = NAUTILUS_APPLICATION (g_application_get_default ()); + + if (bytes_left == 0) { + nautilus_application_notify_unmount_done (app, message); + } else { + nautilus_application_notify_unmount_show (app, message); + } +#endif +} + +static void +show_unmount_progress_aborted_cb (GMountOperation *op, + gpointer user_data) +{ + /* FIXME: These are just libnotify notifications, but GTK+ doesn't do notifications right now. + * Should we just call D-Bus directly? + */ +#if DO_NOT_COMPILE + NautilusApplication *app = NAUTILUS_APPLICATION (g_application_get_default ()); + nautilus_application_notify_unmount_done (app, NULL); +#endif +} + +static GMountOperation * +get_unmount_operation (GtkPlacesSidebar *sidebar) +{ + GMountOperation *mount_op; + + mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar)))); + g_signal_connect (mount_op, "show-unmount-progress", + G_CALLBACK (show_unmount_progress_cb), sidebar); + g_signal_connect (mount_op, "aborted", + G_CALLBACK (show_unmount_progress_aborted_cb), sidebar); + + return mount_op; +} + +static void +do_unmount (GMount *mount, + GtkPlacesSidebar *sidebar) +{ + if (mount != NULL) { + GMountOperation *mount_op; + + mount_op = get_unmount_operation (sidebar); + g_mount_unmount_with_operation (mount, + 0, + mount_op, + NULL, + unmount_mount_cb, + g_object_ref (sidebar)); + g_object_unref (mount_op); + } +} + +static void +do_unmount_selection (GtkPlacesSidebar *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, + GtkPlacesSidebar *sidebar) +{ + do_unmount_selection (sidebar); +} + +static void +drive_eject_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = user_data; + + 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); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +volume_eject_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = user_data; + + 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); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +mount_eject_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = user_data; + + 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); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +do_eject (GMount *mount, + GVolume *volume, + GDrive *drive, + GtkPlacesSidebar *sidebar) +{ + GMountOperation *mount_op; + + mount_op = get_unmount_operation (sidebar); + if (mount != NULL) { + g_mount_eject_with_operation (mount, 0, mount_op, NULL, mount_eject_cb, + g_object_ref (sidebar)); + } else if (volume != NULL) { + g_volume_eject_with_operation (volume, 0, mount_op, NULL, volume_eject_cb, + g_object_ref (sidebar)); + } else if (drive != NULL) { + g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb, + g_object_ref (sidebar)); + } + g_object_unref (mount_op); +} + +static void +eject_shortcut_cb (GtkMenuItem *item, + GtkPlacesSidebar *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 (GtkPlacesSidebar *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->store); + + 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; + } + + g_clear_object (&mount); + g_clear_object (&volume); + g_clear_object (&drive); + + return ret; +} + +static gboolean +eject_or_unmount_selection (GtkPlacesSidebar *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) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = GTK_PLACES_SIDEBAR (user_data); + + 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); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + /* FIXME: drive_stop_cb() gets a reffed sidebar, and unrefs it. Do we need to do the same here? */ +} + +static void +rescan_shortcut_cb (GtkMenuItem *item, + GtkPlacesSidebar *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, sidebar); + g_object_unref (drive); + } +} + +static void +drive_start_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = GTK_PLACES_SIDEBAR (user_data); + + 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); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + /* FIXME: drive_stop_cb() gets a reffed sidebar, and unrefs it. Do we need to do the same here? */ +} + +static void +start_shortcut_cb (GtkMenuItem *item, + GtkPlacesSidebar *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, sidebar); + + g_object_unref (mount_op); + g_object_unref (drive); + } +} + +static void +drive_stop_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = user_data; + + 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); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +stop_shortcut_cb (GtkMenuItem *item, + GtkPlacesSidebar *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 = get_unmount_operation (sidebar); + g_drive_stop (drive, G_MOUNT_UNMOUNT_NONE, mount_op, NULL, drive_stop_cb, + g_object_ref (sidebar)); + + g_object_unref (mount_op); + g_object_unref (drive); + } +} + +static gboolean +find_prev_or_next_row (GtkPlacesSidebar *sidebar, + GtkTreeIter *iter, + gboolean go_up) +{ + GtkTreeModel *model = GTK_TREE_MODEL (sidebar->store); + gboolean res; + int place_type; + + if (go_up) { + res = gtk_tree_model_iter_previous (model, iter); + } else { + res = gtk_tree_model_iter_next (model, iter); + } + + if (res) { + gtk_tree_model_get (model, iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type, + -1); + if (place_type == PLACES_HEADING) { + if (go_up) { + res = gtk_tree_model_iter_previous (model, iter); + } else { + res = gtk_tree_model_iter_next (model, iter); + } + } + } + + return res; +} + +static gboolean +find_prev_row (GtkPlacesSidebar *sidebar, GtkTreeIter *iter) +{ + return find_prev_or_next_row (sidebar, iter, TRUE); +} + +static gboolean +find_next_row (GtkPlacesSidebar *sidebar, GtkTreeIter *iter) +{ + return find_prev_or_next_row (sidebar, iter, FALSE); +} + +static gboolean +gtk_places_sidebar_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (widget); + GtkTreePath *path; + GtkTreeIter iter; + gboolean res; + + res = get_selected_iter (sidebar, &iter); + + if (!res) { + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store), &iter); + res = find_next_row (sidebar, &iter); + if (res) { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &iter); + gtk_tree_view_set_cursor (sidebar->tree_view, path, NULL, FALSE); + gtk_tree_path_free (path); + } + } + + return GTK_WIDGET_CLASS (gtk_places_sidebar_parent_class)->focus (widget, direction); +} + +/* Handler for GtkWidget::key-press-event on the shortcuts list */ +static gboolean +bookmarks_key_press_event_cb (GtkWidget *widget, + GdkEventKey *event, + GtkPlacesSidebar *sidebar) +{ + guint modifiers; + GtkTreeIter selected_iter; + GtkTreePath *path; + + if (!get_selected_iter (sidebar, &selected_iter)) { + return FALSE; + } + + modifiers = gtk_accelerator_get_default_mod_mask (); + + if ((event->keyval == GDK_KEY_Return || + event->keyval == GDK_KEY_KP_Enter || + event->keyval == GDK_KEY_ISO_Enter || + event->keyval == GDK_KEY_space)) { + + GtkPlacesOpenFlags open_flags = GTK_PLACES_OPEN_NORMAL; + + if ((event->state & modifiers) == GDK_SHIFT_MASK) { + open_flags = GTK_PLACES_OPEN_NEW_TAB; + } else if ((event->state & modifiers) == GDK_CONTROL_MASK) { + open_flags = GTK_PLACES_OPEN_NEW_WINDOW; + } + + open_selected_bookmark (sidebar, GTK_TREE_MODEL (sidebar->store), + &selected_iter, open_flags); + + return TRUE; + } + + if (event->keyval == GDK_KEY_Down && + (event->state & modifiers) == GDK_MOD1_MASK) { + return eject_or_unmount_selection (sidebar); + } + + if (event->keyval == GDK_KEY_Up) { + if (find_prev_row (sidebar, &selected_iter)) { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &selected_iter); + gtk_tree_view_set_cursor (sidebar->tree_view, path, NULL, FALSE); + gtk_tree_path_free (path); + } + return TRUE; + } + + if (event->keyval == GDK_KEY_Down) { + if (find_next_row (sidebar, &selected_iter)) { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &selected_iter); + gtk_tree_view_set_cursor (sidebar->tree_view, path, NULL, FALSE); + gtk_tree_path_free (path); + } + return TRUE; + } + + if ((event->keyval == GDK_KEY_Delete + || event->keyval == GDK_KEY_KP_Delete) + && (event->state & modifiers) == 0) { + remove_selected_bookmarks (sidebar); + return TRUE; + } + + if ((event->keyval == GDK_KEY_F2) + && (event->state & modifiers) == 0) { + rename_selected_bookmark (sidebar); + return TRUE; + } + + return FALSE; +} + +static GtkMenuItem * +append_menu_separator (GtkMenu *menu) +{ + GtkWidget *menu_item; + + menu_item = gtk_separator_menu_item_new (); + gtk_widget_show (menu_item); + gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menu_item, -1); + + return GTK_MENU_ITEM (menu_item); +} + +/* Constructs the popup menu for the file list if needed */ +static void +bookmarks_build_popup_menu (GtkPlacesSidebar *sidebar) +{ + PopupMenuData menu_data; + GtkWidget *item; + SelectionInfo sel_info; + GFile *file; + + 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); + + if (sidebar->open_flags & GTK_PLACES_OPEN_NEW_TAB) { + item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab")); + 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); + } + + if (sidebar->open_flags & GTK_PLACES_OPEN_NEW_WINDOW) { + 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); + } + + append_menu_separator (GTK_MENU (sidebar->popup_menu)); + + item = gtk_menu_item_new_with_mnemonic (_("_Add Bookmark")); + menu_data.add_shortcut_item = item; + g_signal_connect (item, "activate", + G_CALLBACK (add_shortcut_cb), sidebar); + gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item); + + item = gtk_image_menu_item_new_with_label (_("Remove")); + menu_data.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…")); + menu_data.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 */ + + menu_data.separator_item = GTK_WIDGET (append_menu_separator (GTK_MENU (sidebar->popup_menu))); + + item = gtk_menu_item_new_with_mnemonic (_("_Mount")); + menu_data.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")); + menu_data.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")); + menu_data.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")); + menu_data.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 (_("_Start")); + menu_data.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")); + menu_data.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); + + /* Update everything! */ + + get_selection_info (sidebar, &sel_info); + + check_popup_sensitivity (sidebar, &menu_data, &sel_info); + + /* And let the caller spice things up */ + + if (sel_info.uri) + file = g_file_new_for_uri (sel_info.uri); + else + file = NULL; + + emit_populate_popup (sidebar, GTK_MENU (sidebar->popup_menu), file); + + g_object_unref (file); + + free_selection_info (&sel_info); +} + +static void +bookmarks_popup_menu (GtkPlacesSidebar *sidebar, + GdkEventButton *event) +{ + int button; + + if (sidebar->popup_menu) + gtk_widget_destroy (sidebar->popup_menu); + + bookmarks_build_popup_menu (sidebar); + + /* The event button needs to be 0 if we're popping up this menu from + * a button release, else a 2nd click outside the menu with any button + * other than the one that invoked the menu will be ignored (instead + * of dismissing the menu). This is a subtle fragility of the GTK menu code. + */ + if (event) { + if (event->type == GDK_BUTTON_RELEASE) + button = 0; + else + button = event->button; + } else { + button = 0; + } + + gtk_menu_popup (GTK_MENU (sidebar->popup_menu), /* menu */ + NULL, /* parent_menu_shell */ + NULL, /* parent_menu_item */ + NULL, /* popup_position_func */ + NULL, /* popup_position_user_data */ + button, /* button */ + event ? event->time : gtk_get_current_event_time ()); /* activate_time */ +} + +/* Callback used for the GtkWidget::popup-menu signal of the shortcuts list */ +static gboolean +bookmarks_popup_menu_cb (GtkWidget *widget, + GtkPlacesSidebar *sidebar) +{ + bookmarks_popup_menu (sidebar, NULL); + return TRUE; +} + +static gboolean +bookmarks_button_release_event_cb (GtkWidget *widget, + GdkEventButton *event, + GtkPlacesSidebar *sidebar) +{ + GtkTreePath *path; + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreeView *tree_view; + gboolean ret = FALSE; + gboolean res; + + path = NULL; + + if (event->type != GDK_BUTTON_RELEASE) { + return TRUE; + } + + if (clicked_eject_button (sidebar, &path)) { + eject_or_unmount_bookmark (sidebar, path); + gtk_tree_path_free (path); + + return FALSE; + } + + tree_view = GTK_TREE_VIEW (widget); + model = gtk_tree_view_get_model (tree_view); + + if (event->window != gtk_tree_view_get_bin_window (tree_view)) { + return FALSE; + } + + res = gtk_tree_view_get_path_at_pos (tree_view, (int) event->x, (int) event->y, + &path, NULL, NULL, NULL); + + if (!res || path == NULL) { + return FALSE; + } + + res = gtk_tree_model_get_iter (model, &iter, path); + if (!res) { + gtk_tree_path_free (path); + return FALSE; + } + + if (event->button == 1) { + open_selected_bookmark (sidebar, model, &iter, 0); + } else if (event->button == 2) { + GtkPlacesOpenFlags open_flags = GTK_PLACES_OPEN_NORMAL; + + open_flags = ((event->state & GDK_CONTROL_MASK) ? + GTK_PLACES_OPEN_NEW_WINDOW : + GTK_PLACES_OPEN_NEW_TAB); + + open_selected_bookmark (sidebar, model, &iter, open_flags); + ret = TRUE; + } else if (event->button == 3) { + PlaceType row_type; + + gtk_tree_model_get (model, &iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &row_type, + -1); + + if (row_type != PLACES_HEADING) { + bookmarks_popup_menu (sidebar, event); + } + } + + gtk_tree_path_free (path); + + return ret; +} + +static void +bookmarks_edited (GtkCellRenderer *cell, + gchar *path_string, + gchar *new_text, + GtkPlacesSidebar *sidebar) +{ + GtkTreePath *path; + GtkTreeIter iter; + char *uri; + GFile *file; + + 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_URI, &uri, + -1); + gtk_tree_path_free (path); + + file = g_file_new_for_uri (uri); + if (!_gtk_bookmarks_manager_has_bookmark (sidebar->bookmarks_manager, file)) { + _gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, file, -1, NULL); + } + + _gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, file, new_text, NULL); /* NULL-GError */ + + g_object_unref (file); + g_free (uri); +} + +static void +bookmarks_editing_canceled (GtkCellRenderer *cell, + GtkPlacesSidebar *sidebar) +{ + g_object_set (cell, "editable", FALSE, NULL); +} + +static gboolean +tree_selection_func (GtkTreeSelection *selection, + GtkTreeModel *model, + GtkTreePath *path, + gboolean path_currently_selected, + gpointer user_data) +{ + GtkTreeIter iter; + PlaceType row_type; + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &row_type, + -1); + + if (row_type == PLACES_HEADING) { + return FALSE; + } + + return TRUE; +} + +static void +icon_cell_renderer_func (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + PlaceType type; + + gtk_tree_model_get (model, iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type, + -1); + + if (type == PLACES_HEADING) { + g_object_set (cell, + "visible", FALSE, + NULL); + } else { + g_object_set (cell, + "visible", TRUE, + NULL); + } +} + +static void +padding_cell_renderer_func (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + PlaceType type; + + gtk_tree_model_get (model, iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type, + -1); + + if (type == PLACES_HEADING) { + g_object_set (cell, + "visible", FALSE, + "xpad", 0, + "ypad", 0, + NULL); + } else { + g_object_set (cell, + "visible", TRUE, + "xpad", 3, + "ypad", 3, + NULL); + } +} + +static void +heading_cell_renderer_func (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + PlaceType type; + + gtk_tree_model_get (model, iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type, + -1); + + if (type == PLACES_HEADING) { + g_object_set (cell, + "visible", TRUE, + NULL); + } else { + g_object_set (cell, + "visible", FALSE, + NULL); + } +} + +static gint +places_sidebar_sort_func (GtkTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b, + gpointer user_data) +{ + SectionType section_type_a, section_type_b; + PlaceType place_type_a, place_type_b; + gint retval = 0; + + gtk_tree_model_get (model, iter_a, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, §ion_type_a, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type_a, + -1); + gtk_tree_model_get (model, iter_b, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, §ion_type_b, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type_b, + -1); + + /* fall back to the default order if we're not in the + * XDG part of the computer section. + */ + if ((section_type_a == section_type_b) && + (section_type_a == SECTION_COMPUTER) && + (place_type_a == place_type_b) && + (place_type_a == PLACES_XDG_DIR)) { + gchar *name_a, *name_b; + + gtk_tree_model_get (model, iter_a, + PLACES_SIDEBAR_COLUMN_NAME, &name_a, + -1); + gtk_tree_model_get (model, iter_b, + PLACES_SIDEBAR_COLUMN_NAME, &name_b, + -1); + + retval = g_utf8_collate (name_a, name_b); + + g_free (name_a); + g_free (name_b); + } + + return retval; +} + +static void +update_hostname (GtkPlacesSidebar *sidebar) +{ + GVariant *variant; + gsize len; + const gchar *hostname; + + if (sidebar->hostnamed_proxy == NULL) + return; + + variant = g_dbus_proxy_get_cached_property (sidebar->hostnamed_proxy, + "PrettyHostname"); + if (variant == NULL) { + return; + } + + hostname = g_variant_get_string (variant, &len); + if (len > 0 && + g_strcmp0 (sidebar->hostname, hostname) != 0) { + g_free (sidebar->hostname); + sidebar->hostname = g_strdup (hostname); + update_places (sidebar); + } + + g_variant_unref (variant); +} + +static void +hostname_proxy_new_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar = user_data; + GError *error = NULL; + + sidebar->hostnamed_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + g_clear_object (&sidebar->hostnamed_cancellable); + + g_object_unref (sidebar); + + if (error != NULL) { + g_debug ("Failed to create D-Bus proxy: %s", error->message); + g_error_free (error); + return; + } + + g_signal_connect_swapped (sidebar->hostnamed_proxy, + "g-properties-changed", + G_CALLBACK (update_hostname), + sidebar); + update_hostname (sidebar); +} + +static void +create_volume_monitor (GtkPlacesSidebar *sidebar) +{ + g_assert (sidebar->volume_monitor == NULL); + + sidebar->volume_monitor = g_volume_monitor_get (); + + 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); +} + +static void +bookmarks_changed_cb (gpointer data) +{ + GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (data); + + update_places (sidebar); +} + +static gboolean +tree_view_button_press_callback (GtkWidget *tree_view, + GdkEventButton *event, + gpointer data) +{ + GtkTreePath *path; + GtkTreeViewColumn *column; + + if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (tree_view), + event->x, event->y, + &path, + &column, + NULL, + NULL)) { + gtk_tree_view_row_activated (GTK_TREE_VIEW (tree_view), path, column); + } + } + + return FALSE; +} + +static void +tree_view_set_activate_on_single_click (GtkTreeView *tree_view) +{ + g_signal_connect (tree_view, "button_press_event", + G_CALLBACK (tree_view_button_press_callback), + NULL); +} + +static void +trash_monitor_trash_state_changed_cb (GtkTrashMonitor *monitor, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + + +static void +gtk_places_sidebar_init (GtkPlacesSidebar *sidebar) +{ + GtkTreeView *tree_view; + GtkTreeViewColumn *col; + GtkCellRenderer *cell; + GtkTreeSelection *selection; + GIcon *eject; + + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (sidebar)), GTK_STYLE_CLASS_SIDEBAR); + + create_volume_monitor (sidebar); + + sidebar->open_flags = GTK_PLACES_OPEN_NORMAL; + + sidebar->bookmarks_manager = _gtk_bookmarks_manager_new (bookmarks_changed_cb, sidebar); + + sidebar->trash_monitor = _gtk_trash_monitor_get (); + sidebar->trash_monitor_changed_id = g_signal_connect (sidebar->trash_monitor, "trash-state-changed", + G_CALLBACK (trash_monitor_trash_state_changed_cb), sidebar); + + sidebar->shortcuts = NULL; + + 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); + + gtk_style_context_set_junction_sides (gtk_widget_get_style_context (GTK_WIDGET (sidebar)), + GTK_JUNCTION_RIGHT | GTK_JUNCTION_LEFT); + + /* 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 ()); + + /* initial padding */ + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + g_object_set (cell, + "xpad", 6, + NULL); + + /* headings */ + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_attributes (col, cell, + "text", PLACES_SIDEBAR_COLUMN_HEADING_TEXT, + NULL); + g_object_set (cell, + "weight", PANGO_WEIGHT_BOLD, + "weight-set", TRUE, + "ypad", 6, + "xpad", 0, + NULL); + gtk_tree_view_column_set_cell_data_func (col, cell, + heading_cell_renderer_func, + sidebar, NULL); + + /* icon padding */ + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_cell_data_func (col, cell, + padding_cell_renderer_func, + sidebar, NULL); + + /* icon renderer */ + cell = gtk_cell_renderer_pixbuf_new (); + g_object_set (cell, "follow-state", TRUE, NULL); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_attributes (col, cell, + "gicon", PLACES_SIDEBAR_COLUMN_GICON, + NULL); + gtk_tree_view_column_set_cell_data_func (col, cell, + icon_cell_renderer_func, + sidebar, NULL); + + /* eject text renderer */ + cell = gtk_cell_renderer_text_new (); + 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); + + /* eject icon renderer */ + cell = gtk_cell_renderer_pixbuf_new (); + sidebar->eject_icon_cell_renderer = cell; + eject = g_themed_icon_new_with_default_fallbacks ("media-eject-symbolic"); + g_object_set (cell, + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + "stock-size", GTK_ICON_SIZE_MENU, + "xpad", EJECT_BUTTON_XPAD, + /* align right, because for some reason gtk+ expands + this even though we tell it not to. */ + "xalign", 1.0, + "follow-state", TRUE, + "gicon", eject, + NULL); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_attributes (col, cell, + "visible", PLACES_SIDEBAR_COLUMN_EJECT, + NULL); + g_object_unref (eject); + + /* normal text renderer */ + 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); + + /* this is required to align the eject buttons to the right */ + gtk_tree_view_column_set_max_width (GTK_TREE_VIEW_COLUMN (col), 24); + gtk_tree_view_append_column (tree_view, col); + + sidebar->store = shortcuts_model_new (sidebar); + gtk_tree_view_set_tooltip_column (tree_view, PLACES_SIDEBAR_COLUMN_TOOLTIP); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sidebar->store), + PLACES_SIDEBAR_COLUMN_NAME, + GTK_SORT_ASCENDING); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sidebar->store), + PLACES_SIDEBAR_COLUMN_NAME, + places_sidebar_sort_func, + sidebar, NULL); + + gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (sidebar->store)); + gtk_container_add (GTK_CONTAINER (sidebar), GTK_WIDGET (tree_view)); + gtk_widget_show (GTK_WIDGET (tree_view)); + gtk_tree_view_set_enable_search (tree_view, FALSE); + + 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_selection_set_select_function (selection, + tree_selection_func, + sidebar, + NULL); + + gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (tree_view), + GDK_BUTTON1_MASK, + dnd_source_targets, G_N_ELEMENTS (dnd_source_targets), + GDK_ACTION_MOVE); + gtk_drag_dest_set (GTK_WIDGET (tree_view), + 0, + dnd_drop_targets, G_N_ELEMENTS (dnd_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 (tree_view, "popup-menu", + G_CALLBACK (bookmarks_popup_menu_cb), sidebar); + g_signal_connect (tree_view, "button-release-event", + G_CALLBACK (bookmarks_button_release_event_cb), sidebar); + + tree_view_set_activate_on_single_click (sidebar->tree_view); + + sidebar->hostname = g_strdup (_("Computer")); + sidebar->hostnamed_cancellable = g_cancellable_new (); + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, + NULL, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + sidebar->hostnamed_cancellable, + hostname_proxy_new_cb, + g_object_ref (sidebar)); +} + +static void +gtk_places_sidebar_set_property (GObject *obj, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (obj); + + switch (property_id) { + case PROP_LOCATION: + gtk_places_sidebar_set_location (sidebar, g_value_get_object (value)); + break; + case PROP_OPEN_FLAGS: + gtk_places_sidebar_set_open_flags (sidebar, g_value_get_flags (value)); + break; + case PROP_SHOW_DESKTOP: + gtk_places_sidebar_set_show_desktop (sidebar, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); + break; + } +} + +static void +gtk_places_sidebar_get_property (GObject *obj, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (obj); + + switch (property_id) { + case PROP_LOCATION: + g_value_take_object (value, gtk_places_sidebar_get_location (sidebar)); + break; + case PROP_OPEN_FLAGS: + g_value_set_flags (value, gtk_places_sidebar_get_open_flags (sidebar)); + break; + case PROP_SHOW_DESKTOP: + g_value_set_boolean (value, gtk_places_sidebar_get_show_desktop (sidebar)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); + break; + } +} + +static void +gtk_places_sidebar_dispose (GObject *object) +{ + GtkPlacesSidebar *sidebar; + + sidebar = GTK_PLACES_SIDEBAR (object); + + sidebar->tree_view = NULL; + + free_drag_data (sidebar); + + if (sidebar->bookmarks_manager != NULL) { + _gtk_bookmarks_manager_free (sidebar->bookmarks_manager); + sidebar->bookmarks_manager = NULL; + } + + if (sidebar->popup_menu) { + gtk_widget_destroy (sidebar->popup_menu); + sidebar->popup_menu = NULL; + } + + if (sidebar->trash_monitor) { + g_signal_handler_disconnect (sidebar->trash_monitor, sidebar->trash_monitor_changed_id); + sidebar->trash_monitor_changed_id = 0; + g_clear_object (&sidebar->trash_monitor); + } + + g_clear_object (&sidebar->store); + + g_slist_free_full (sidebar->shortcuts, g_object_unref); + sidebar->shortcuts = NULL; + + if (sidebar->volume_monitor != NULL) { + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + volume_added_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + volume_removed_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + volume_changed_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + mount_added_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + mount_removed_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + mount_changed_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + drive_disconnected_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + drive_connected_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + drive_changed_callback, sidebar); + + g_clear_object (&sidebar->volume_monitor); + } + + if (sidebar->hostnamed_cancellable != NULL) { + g_cancellable_cancel (sidebar->hostnamed_cancellable); + g_clear_object (&sidebar->hostnamed_cancellable); + } + + g_clear_object (&sidebar->hostnamed_proxy); + g_free (sidebar->hostname); + sidebar->hostname = NULL; + + G_OBJECT_CLASS (gtk_places_sidebar_parent_class)->dispose (object); +} + +static void +gtk_places_sidebar_class_init (GtkPlacesSidebarClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) class; + + gobject_class->dispose = gtk_places_sidebar_dispose; + gobject_class->set_property = gtk_places_sidebar_set_property; + gobject_class->get_property = gtk_places_sidebar_get_property; + + GTK_WIDGET_CLASS (class)->focus = gtk_places_sidebar_focus; + + /** + * GtkPlacesSidebar::open-location: + * @sidebar: the object which received the signal. + * @location: #GFile to which the caller should switch. + * @open_flags: a single value from #GtkPlacesOpenFlags specifying how the @location should be opened. + * + * The places sidebar emits this signal when the user selects a location + * in it. The calling application should display the contents of that + * location; for example, a file manager should show a list of files in + * the specified location. + * + * Since: 3.8 + */ + places_sidebar_signals [OPEN_LOCATION] = + g_signal_new (I_("open-location"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkPlacesSidebarClass, open_location), + NULL, NULL, + _gtk_marshal_VOID__OBJECT_FLAGS, + G_TYPE_NONE, 2, + G_TYPE_OBJECT, + GTK_TYPE_PLACES_OPEN_FLAGS); + + /** + * GtkPlacesSidebar::populate-popup: + * @sidebar: the object which received the signal. + * @menu: a #GtkMenu. + * @selected_item: #GFile with the item to which the menu should refer. + * + * The places sidebar emits this signal when the user invokes a contextual + * menu on one of its items. In the signal handler, the application may + * add extra items to the menu as appropriate. For example, a file manager + * may want to add a "Properties" command to the menu. + * + * It is not necessary to store the @selected_item for each menu item; + * during their GtkMenuItem::activate callbacks, the application can use + * gtk_places_sidebar_get_location() to get the file to which the item + * refers. + * + * The @menu and all its menu items are destroyed after the user + * dismisses the menu. The menu is re-created (and thus, this signal is + * emitted) every time the user activates the contextual menu. + * + * Since: 3.8 + */ + places_sidebar_signals [POPULATE_POPUP] = + g_signal_new (I_("populate-popup"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkPlacesSidebarClass, populate_popup), + NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + G_TYPE_OBJECT, + G_TYPE_OBJECT); + + /** + * GtkPlacesSidebar::show-error-message: + * @sidebar: the object which received the signal. + * @primary: primary message with a summary of the error to show. + * @secondary: secondary message with details of the error to show. + * + * The places sidebar emits this signal when it needs the calling + * application to present an error message. Most of these messages + * refer to mounting or unmounting media, for example, when a drive + * cannot be started for some reason. + * + * Since: 3.8 + */ + places_sidebar_signals [SHOW_ERROR_MESSAGE] = + g_signal_new (I_("show-error-message"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkPlacesSidebarClass, show_error_message), + NULL, NULL, + _gtk_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_STRING); + + /** + * GtkPlacesSidebar::drag-action-requested: + * @sidebar: the object which received the signal. + * @context: #GdkDragContext with information about the drag operation + * @dest_file: #GFile with the tentative location that is being hovered for a drop + * @source_file_list: (element-type GFile) (transfer none): List of #GFile that are being dragged + * + * When the user starts a drag-and-drop operation and the sidebar needs + * to ask the application for which drag action to perform, then the + * sidebar will emit this signal. + * + * The application can evaluate the @context for customary actions, or + * it can check the type of the files indicated by @source_file_list against the + * possible actions for the destination @dest_file. + * + * The drag action to use must be the return value of the signal handler. + * + * Return value: The drag action to use, for example, #GDK_ACTION_COPY + * or #GDK_ACTION_MOVE, or 0 if no action is allowed here (i.e. drops + * are not allowed in the specified @dest_file). + * + * Since: 3.8 + */ + places_sidebar_signals [DRAG_ACTION_REQUESTED] = + g_signal_new (I_("drag-action-requested"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_action_requested), + NULL, NULL, + _gtk_marshal_INT__OBJECT_OBJECT_POINTER, + G_TYPE_INT, 3, + GDK_TYPE_DRAG_CONTEXT, + G_TYPE_OBJECT, + G_TYPE_POINTER /* GList of GFile */ ); + + /** + * GtkPlacesSidebar::drag-action-ask: + * @sidebar: the object which received the signal. + * @actions: Possible drag actions that need to be asked for. + * + * The places sidebar emits this signal when it needs to ask the application + * to pop up a menu to ask the user for which drag action to perform. + * + * Return value: the final drag action that the sidebar should pass to the drag side + * of the drag-and-drop operation. + * + * Since: 3.8 + */ + places_sidebar_signals [DRAG_ACTION_ASK] = + g_signal_new (I_("drag-action-ask"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_action_ask), + NULL, NULL, + _gtk_marshal_INT__INT, + G_TYPE_INT, 1, + G_TYPE_INT); + + /** + * GtkPlacesSidebar::drag-perform-drop: + * @sidebar: the object which received the signal. + * @dest_file: Destination #GFile. + * @source_file_list: (element-type GFile) (transfer none): #GList of #GFile that got dropped. + * @action: Drop action to perform. + * + * The places sidebar emits this signal when the user completes a + * drag-and-drop operation and one of the sidebar's items is the + * destination. This item is in the @dest_file, and the + * @source_file_list has the list of files that are dropped into it and + * which should be copied/moved/etc. based on the specified @action. + * + * Since: 3.8 + */ + places_sidebar_signals [DRAG_PERFORM_DROP] = + g_signal_new (I_("drag-perform-drop"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_perform_drop), + NULL, NULL, + _gtk_marshal_VOID__OBJECT_POINTER_INT, + G_TYPE_NONE, 3, + G_TYPE_OBJECT, + G_TYPE_POINTER, /* GList of GFile */ + G_TYPE_INT); + + properties[PROP_LOCATION] = + g_param_spec_object ("location", + P_("Location to select"), + P_("The location to highlight in the sidebar"), + G_TYPE_FILE, + G_PARAM_READWRITE); + properties[PROP_OPEN_FLAGS] = + g_param_spec_flags ("open-flags", + P_("The open modes supported for this widget"), + P_("The set of open modes supported for this widget"), + GTK_TYPE_PLACES_OPEN_FLAGS, + GTK_PLACES_OPEN_NORMAL, + G_PARAM_READWRITE); + properties[PROP_SHOW_DESKTOP] = + g_param_spec_boolean ("show-desktop", + P_("Whether to show desktop"), + P_("Whether the sidebar includes a builtin shortcut to the desktop folder"), + FALSE, + G_PARAM_READWRITE); + + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); +} + +/** + * gtk_places_sidebar_new: + * + * Creates a new #GtkPlacesSidebar widget. The application should connect + * to at least the #GtkPlacesSidebar::open-location signal to be notified + * when the user makes a selection in the sidebar. + */ +GtkWidget * +gtk_places_sidebar_new (void) +{ + return GTK_WIDGET (g_object_new (gtk_places_sidebar_get_type (), NULL)); +} + + + +/* Drag and drop interfaces */ + +/* GtkTreeDragSource::row_draggable implementation for the shortcuts model */ +static gboolean +shortcuts_model_row_draggable (GtkTreeDragSource *drag_source, + GtkTreePath *path) +{ + GtkTreeModel *model; + GtkTreeIter iter; + PlaceType place_type; + SectionType section_type; + + model = GTK_TREE_MODEL (drag_source); + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, §ion_type, + -1); + + if (place_type != PLACES_HEADING && section_type == SECTION_BOOKMARKS) + return TRUE; + + return FALSE; +} + +/* Fill the GtkTreeDragSourceIface vtable */ +static void +shortcuts_model_class_init (ShortcutsModelClass *klass) +{ +} + +static void +shortcuts_model_init (ShortcutsModel *model) +{ + model->sidebar = NULL; +} + +static void +shortcuts_model_drag_source_iface_init (GtkTreeDragSourceIface *iface) +{ + iface->row_draggable = shortcuts_model_row_draggable; +} + +static GtkListStore * +shortcuts_model_new (GtkPlacesSidebar *sidebar) +{ + ShortcutsModel *model; + GType model_types[PLACES_SIDEBAR_COLUMN_COUNT] = { + G_TYPE_INT, + G_TYPE_STRING, + G_TYPE_DRIVE, + G_TYPE_VOLUME, + G_TYPE_MOUNT, + G_TYPE_STRING, + G_TYPE_ICON, + G_TYPE_INT, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, + G_TYPE_STRING, + G_TYPE_INT, + G_TYPE_STRING + }; + + model = g_object_new (shortcuts_model_get_type (), NULL); + model->sidebar = sidebar; + + gtk_list_store_set_column_types (GTK_LIST_STORE (model), + PLACES_SIDEBAR_COLUMN_COUNT, + model_types); + + return GTK_LIST_STORE (model); +} + + + +/* Public methods for GtkPlacesSidebar */ + +/** + * gtk_places_sidebar_set_open_flags: + * @sidebar: a places sidebar + * @flags: Bitmask of modes in which the calling application can open locations + * + * Sets the way in which the calling application can open new locations from + * the places sidebar. For example, some applications only open locations + * "directly" into their main view, while others may support opening locations + * in a new notebook tab or a new window. + * + * This function is used to tell the places @sidebar about the ways in which the + * application can open new locations, so that the sidebar can display (or not) + * the "Open in new tab" and "Open in new window" menu items as appropriate. + * + * When the #GtkPlacesSidebar::open-location signal is emitted, its flags + * argument will be set to one of the @flags that was passed in + * gtk_places_sidebar_set_open_flags(). + * + * Passing 0 for @flags will cause #GTK_PLACES_OPEN_NORMAL to always be sent + * to callbacks for the "open-location" signal. + * + * Since: 3.8 + */ +void +gtk_places_sidebar_set_open_flags (GtkPlacesSidebar *sidebar, GtkPlacesOpenFlags flags) +{ + g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar)); + + if (sidebar->open_flags != flags) { + sidebar->open_flags = flags; + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_OPEN_FLAGS]); + } +} + +GtkPlacesOpenFlags +gtk_places_sidebar_get_open_flags (GtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), 0); + + return sidebar->open_flags; +} + +/** + * gtk_places_sidebar_set_location: + * @sidebar: a places sidebar + * @location: (allow-none): location to select, or #NULL for no current path + * + * Sets the location that is being shown in the widgets surrounding the + * @sidebar, for example, in a folder view in a file manager. In turn, the + * @sidebar will highlight that location if it is being shown in the list of + * places, or it will unhighlight everything if the @location is not among the + * places in the list. + * + * Since: 3.8 + */ +void +gtk_places_sidebar_set_location (GtkPlacesSidebar *sidebar, GFile *location) +{ + GtkTreeSelection *selection; + GtkTreeIter iter; + gboolean valid; + char *iter_uri; + char *uri; + + g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar)); + + selection = gtk_tree_view_get_selection (sidebar->tree_view); + gtk_tree_selection_unselect_all (selection); + + if (location == NULL) + goto out; + + uri = g_file_get_uri (location); + + valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store), &iter); + while (valid) { + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter, + PLACES_SIDEBAR_COLUMN_URI, &iter_uri, + -1); + if (iter_uri != NULL) { + if (strcmp (iter_uri, uri) == 0) { + g_free (iter_uri); + gtk_tree_selection_select_iter (selection, &iter); + break; + } + g_free (iter_uri); + } + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (sidebar->store), &iter); + } + + g_free (uri); + + out: + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_LOCATION]); +} + +/** + * gtk_places_sidebar_get_location: + * @sidebar: a places sidebar + * + * Gets the currently-selected location in the @sidebar. This can be #NULL when + * nothing is selected, for example, when gtk_places_sidebar_set_location() has + * been called with a location that is not among the sidebar's list of places to + * show. + * + * You can use this function to get the selection in the @sidebar. Also, if you + * connect to the #GtkPlacesSidebar::popup-menu signal, you can use this + * function to get the location that is being referred to during the callbacks + * for your menu items. + * + * Returns: (transfer full): a GFile with the selected location, or #NULL if nothing is visually + * selected. + * + * Since: 3.8 + */ +GFile * +gtk_places_sidebar_get_location (GtkPlacesSidebar *sidebar) +{ + GtkTreeIter iter; + GFile *file; + + g_return_val_if_fail (sidebar != NULL, NULL); + + file = NULL; + + if (get_selected_iter (sidebar, &iter)) { + char *uri; + + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter, + PLACES_SIDEBAR_COLUMN_URI, &uri, + -1); + + file = g_file_new_for_uri (uri); + g_free (uri); + } + + return file; +} + +/** + * gtk_places_sidebar_set_show_desktop: + * @sidebar: a places sidebar + * @show_desktop: whether to show an item for the Desktop folder + * + * Sets whether the @sidebar should show an item for the Desktop folder; this is off by default. + * An application may want to turn this on if the desktop environment actually supports the + * notion of a desktop. + * + * Since: 3.8 + */ +void +gtk_places_sidebar_set_show_desktop (GtkPlacesSidebar *sidebar, gboolean show_desktop) +{ + g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar)); + + show_desktop = !!show_desktop; + if (sidebar->show_desktop != show_desktop) { + sidebar->show_desktop = show_desktop; + update_places (sidebar); + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]); + } +} + +/** + * gtk_places_sidebar_get_show_desktop: + * @sidebar: a places sidebar + * + * Returns the value previously set with gtk_places_sidebar_set_show_desktop() + * + * Return value: %TRUE if the sidebar will display a builtin shortcut to the desktop folder. + * + * Since: 3.8 + */ +gboolean +gtk_places_sidebar_get_show_desktop (GtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE); + + return sidebar->show_desktop; +} + +static GSList * +find_shortcut_link (GtkPlacesSidebar *sidebar, GFile *location) +{ + GSList *l; + + for (l = sidebar->shortcuts; l; l = l->next) { + GFile *shortcut; + + shortcut = G_FILE (l->data); + if (g_file_equal (shortcut, location)) + return l; + } + + return NULL; +} + +/** + * gtk_places_sidebar_add_shortcut: + * @sidebar: a places sidebar + * @location: location to add as an application-specific shortcut + * + * Applications may want to present some folders in the places sidebar if + * they could be immediately useful to users. For example, a drawing + * program could add a "/usr/share/clipart" location when the sidebar is + * being used in an "Insert Clipart" dialog box. + * + * This function adds the specified @location to a special place for immutable + * shortcuts. The shortcuts are application-specific; they are not shared + * across applications, and they are not persistent. If this function + * is called multiple times with different locations, then they are added + * to the sidebar's list in the same order as the function is called. + * + * Since: 3.8 + */ +void +gtk_places_sidebar_add_shortcut (GtkPlacesSidebar *sidebar, GFile *location) +{ + g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar)); + g_return_if_fail (G_IS_FILE (location)); + + g_object_ref (location); + sidebar->shortcuts = g_slist_append (sidebar->shortcuts, location); + + update_places (sidebar); +} + +/** + * gtk_places_sidebar_remove_shortcut: + * @sidebar: a places sidebar + * @location: location to remove + * + * Removes an application-specific shortcut that has been previously been + * inserted with gtk_places_sidebar_add_shortcut(). If the @location is not a + * shortcut in the sidebar, then nothing is done. + * + * Since: 3.8 + */ +void +gtk_places_sidebar_remove_shortcut (GtkPlacesSidebar *sidebar, GFile *location) +{ + GSList *link; + GFile *shortcut; + + g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar)); + g_return_if_fail (G_IS_FILE (location)); + + link = find_shortcut_link (sidebar, location); + if (!link) + return; + + shortcut = G_FILE (link->data); + g_object_unref (shortcut); + + sidebar->shortcuts = g_slist_delete_link (sidebar->shortcuts, link); + update_places (sidebar); +} + +/** + * gtk_places_sidebar_list_shortcuts: + * @sidebar: a places sidebar + * + * Return value: (element-type GFile) (transfer full): A #GSList of #GFile of the locations + * that have been added as application-specific shortcuts with gtk_places_sidebar_add_shortcut(). + * To free this list, you can use + * |[ + * g_slist_free_full (list, (GDestroyNotify) g_object_unref); + * ]| + * + * Since: 3.8 + */ +GSList * +gtk_places_sidebar_list_shortcuts (GtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE); + + return g_slist_copy_deep (sidebar->shortcuts, (GCopyFunc) g_object_ref, NULL); +} + +/** + * gtk_places_sidebar_get_nth_bookmark: + * @sidebar: a places sidebar + * @n: index of the bookmark to query + * + * This function queries the bookmarks added by the user to the places sidebar, + * and returns one of them. This function is used by #GtkFileChooser to implement + * the "Alt-1", "Alt-2", etc. shortcuts, which activate the cooresponding bookmark. + * + * Return value: (transfer full): The bookmark specified by the index @n, or + * #NULL if no such index exist. Note that the indices start at 0, even though + * the file chooser starts them with the keyboard shortcut "Alt-1". + * + * Since: 3.8 + */ +GFile * +gtk_places_sidebar_get_nth_bookmark (GtkPlacesSidebar *sidebar, int n) +{ + GtkTreeIter iter; + int k; + GFile *file; + + g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), NULL); + + file = NULL; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store), &iter)) { + k = 0; + + do { + PlaceType place_type; + char *uri; + + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type, + PLACES_SIDEBAR_COLUMN_URI, &uri, + -1); + + if (place_type == PLACES_BOOKMARK) { + if (k == n) { + file = g_file_new_for_uri (uri); + g_free (uri); + break; + } + + g_free (uri); + k++; + } + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (sidebar->store), &iter)); + } + + return file; +} |