/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/*
 *  Caja
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of the
 *  License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *  Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
 *            Cosimo Cecchi <cosimoc@gnome.org>
 *
 */

#include <config.h>

#include <eel/eel-debug.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-glib-extensions.h>
#include <eel/eel-string.h>
#include <eel/eel-stock-dialogs.h>
#include <eel/eel-gdk-pixbuf-extensions.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libcaja-private/caja-debug-log.h>
#include <libcaja-private/caja-dnd.h>
#include <libcaja-private/caja-bookmark.h>
#include <libcaja-private/caja-global-preferences.h>
#include <libcaja-private/caja-sidebar-provider.h>
#include <libcaja-private/caja-module.h>
#include <libcaja-private/caja-file.h>
#include <libcaja-private/caja-file-utilities.h>
#include <libcaja-private/caja-file-operations.h>
#include <libcaja-private/caja-trash-monitor.h>
#include <libcaja-private/caja-icon-names.h>
#include <libcaja-private/caja-autorun.h>
#include <libcaja-private/caja-window-info.h>
#include <libcaja-private/caja-window-slot-info.h>
#include <gio/gio.h>

#include "caja-bookmark-list.h"
#include "caja-places-sidebar.h"
#include "caja-window.h"

#include "glibcompat.h" /* for g_list_free_full and g_clear_object */

#define EJECT_BUTTON_XPAD 6
#define ICON_CELL_XPAD 6

typedef struct
{
    GtkScrolledWindow  parent;
    GtkTreeView        *tree_view;
    GtkCellRenderer    *eject_text_cell_renderer;
    GtkCellRenderer    *icon_cell_renderer;
    GtkCellRenderer    *icon_padding_cell_renderer;
    GtkCellRenderer    *padding_cell_renderer;
    char               *uri;
    GtkListStore       *store;
    GtkTreeModel       *filter_model;
    CajaWindowInfo *window;
    CajaBookmarkList *bookmarks;
    GVolumeMonitor *volume_monitor;

    gboolean devices_header_added;
    gboolean bookmarks_header_added;

    /* DnD */
    GList     *drag_list;
    gboolean  drag_data_received;
    int       drag_data_info;
    gboolean  drop_occured;

    GtkWidget *popup_menu;
    GtkWidget *popup_menu_open_in_new_tab_item;
    GtkWidget *popup_menu_remove_item;
    GtkWidget *popup_menu_rename_item;
    GtkWidget *popup_menu_separator_item;
    GtkWidget *popup_menu_mount_item;
    GtkWidget *popup_menu_unmount_item;
    GtkWidget *popup_menu_eject_item;
    GtkWidget *popup_menu_rescan_item;
    GtkWidget *popup_menu_format_item;
    GtkWidget *popup_menu_empty_trash_item;
    GtkWidget *popup_menu_start_item;
    GtkWidget *popup_menu_stop_item;

    /* volume mounting - delayed open process */
    gboolean mounting;
    CajaWindowSlotInfo *go_to_after_mount_slot;
    CajaWindowOpenFlags go_to_after_mount_flags;

    GtkTreePath *eject_highlight_path;
} CajaPlacesSidebar;

typedef struct
{
    GtkScrolledWindowClass parent;
} CajaPlacesSidebarClass;

typedef struct
{
    GObject parent;
} CajaPlacesSidebarProvider;

typedef struct
{
    GObjectClass parent;
} CajaPlacesSidebarProviderClass;

enum
{
    PLACES_SIDEBAR_COLUMN_ROW_TYPE,
    PLACES_SIDEBAR_COLUMN_URI,
    PLACES_SIDEBAR_COLUMN_DRIVE,
    PLACES_SIDEBAR_COLUMN_VOLUME,
    PLACES_SIDEBAR_COLUMN_MOUNT,
    PLACES_SIDEBAR_COLUMN_NAME,
    PLACES_SIDEBAR_COLUMN_ICON,
    PLACES_SIDEBAR_COLUMN_INDEX,
    PLACES_SIDEBAR_COLUMN_EJECT,
    PLACES_SIDEBAR_COLUMN_NO_EJECT,
    PLACES_SIDEBAR_COLUMN_BOOKMARK,
    PLACES_SIDEBAR_COLUMN_TOOLTIP,
    PLACES_SIDEBAR_COLUMN_EJECT_ICON,
    PLACES_SIDEBAR_COLUMN_SECTION_TYPE,
    PLACES_SIDEBAR_COLUMN_HEADING_TEXT,

    PLACES_SIDEBAR_COLUMN_COUNT
};

typedef enum
{
    PLACES_BUILT_IN,
    PLACES_MOUNTED_VOLUME,
    PLACES_BOOKMARK,
    PLACES_HEADING,
} PlaceType;

typedef enum {
    SECTION_DEVICES,
    SECTION_BOOKMARKS,
    SECTION_COMPUTER,
    SECTION_NETWORK,
} SectionType;

static void  caja_places_sidebar_iface_init        (CajaSidebarIface         *iface);
static void  sidebar_provider_iface_init               (CajaSidebarProviderIface *iface);
static GType caja_places_sidebar_provider_get_type (void);
static void  open_selected_bookmark                    (CajaPlacesSidebar        *sidebar,
        GtkTreeModel                 *model,
        GtkTreePath                  *path,
        CajaWindowOpenFlags flags);
static void  caja_places_sidebar_style_set         (GtkWidget                    *widget,
        GtkStyle                     *previous_style);
static gboolean eject_or_unmount_bookmark              (CajaPlacesSidebar *sidebar,
        GtkTreePath *path);
static gboolean eject_or_unmount_selection             (CajaPlacesSidebar *sidebar);
static void  check_unmount_and_eject                   (GMount *mount,
        GVolume *volume,
        GDrive *drive,
        gboolean *show_unmount,
        gboolean *show_eject);

static void bookmarks_check_popup_sensitivity          (CajaPlacesSidebar *sidebar);

/* Identifiers for target types */
enum
{
    GTK_TREE_MODEL_ROW,
    TEXT_URI_LIST
};

/* Target types for dragging from the shortcuts list */
static const GtkTargetEntry caja_shortcuts_source_targets[] =
{
    { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW }
};

/* Target types for dropping into the shortcuts list */
static const GtkTargetEntry caja_shortcuts_drop_targets [] =
{
    { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW },
    { "text/uri-list", 0, TEXT_URI_LIST }
};

/* Drag and drop interface declarations */
typedef struct
{
    GtkTreeModelFilter parent;

    CajaPlacesSidebar *sidebar;
} CajaShortcutsModelFilter;

typedef struct
{
    GtkTreeModelFilterClass parent_class;
} CajaShortcutsModelFilterClass;

#define CAJA_SHORTCUTS_MODEL_FILTER_TYPE (_caja_shortcuts_model_filter_get_type ())
#define CAJA_SHORTCUTS_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_SHORTCUTS_MODEL_FILTER_TYPE, CajaShortcutsModelFilter))

GType _caja_shortcuts_model_filter_get_type (void);
static void caja_shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface);

G_DEFINE_TYPE_WITH_CODE (CajaShortcutsModelFilter,
                         _caja_shortcuts_model_filter,
                         GTK_TYPE_TREE_MODEL_FILTER,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
                                 caja_shortcuts_model_filter_drag_source_iface_init));

static GtkTreeModel *caja_shortcuts_model_filter_new (CajaPlacesSidebar *sidebar,
        GtkTreeModel          *child_model,
        GtkTreePath           *root);

G_DEFINE_TYPE_WITH_CODE (CajaPlacesSidebar, caja_places_sidebar, GTK_TYPE_SCROLLED_WINDOW,
                         G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR,
                                 caja_places_sidebar_iface_init));

G_DEFINE_TYPE_WITH_CODE (CajaPlacesSidebarProvider, caja_places_sidebar_provider, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER,
                                 sidebar_provider_iface_init));

static GdkPixbuf *
get_eject_icon (gboolean highlighted)
{
    GdkPixbuf *eject;
    CajaIconInfo *eject_icon_info;
    int icon_size;

    icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);

    eject_icon_info = caja_icon_info_lookup_from_name ("media-eject", icon_size);
    eject = caja_icon_info_get_pixbuf_at_size (eject_icon_info, icon_size);

    if (highlighted) {
        GdkPixbuf *high;
        high = eel_gdk_pixbuf_render (eject, 1, 255, 255, 0, 0);
        g_object_unref (eject);
        eject = high;
    }

    g_object_unref (eject_icon_info);

    return eject;
}

static gboolean
is_built_in_bookmark (CajaFile *file)
{
    gboolean built_in;
    gint idx;

    built_in = FALSE;

    for (idx = 0; idx < G_USER_N_DIRECTORIES; idx++) {
        /* PUBLIC_SHARE and TEMPLATES are not in our built-in list */
        if (caja_file_is_user_special_directory (file, idx)) {
            if (idx != G_USER_DIRECTORY_PUBLIC_SHARE &&  idx != G_USER_DIRECTORY_TEMPLATES) {
                built_in = TRUE;
            }

            break;
        }
    }

    return built_in;
}

static GtkTreeIter
add_heading (CajaPlacesSidebar *sidebar,
         SectionType section_type,
         const gchar *title)
{
    GtkTreeIter iter, child_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);

    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sidebar->filter_model));
    gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (sidebar->filter_model),
                              &child_iter,
                              &iter);

    return child_iter;
}

static void
check_heading_for_section (CajaPlacesSidebar *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 GtkTreeIter
add_place (CajaPlacesSidebar *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)
{
    GdkPixbuf      *pixbuf;
    GtkTreeIter     iter, child_iter;
    GdkPixbuf      *eject;
    CajaIconInfo   *icon_info;
    int             icon_size;
    gboolean        show_eject;
    gboolean        show_unmount;
    gboolean        show_eject_button;

    check_heading_for_section (sidebar, section_type);

    icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
    icon_info = caja_icon_info_lookup (icon, icon_size);

    pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
    g_object_unref (icon_info);

    check_unmount_and_eject (mount, volume, drive,
                             &show_unmount, &show_eject);

    if (show_unmount || show_eject)
    {
        g_assert (place_type != PLACES_BOOKMARK);
    }

    if (mount == NULL)
    {
        show_eject_button = FALSE;
    }
    else
    {
        show_eject_button = (show_unmount || show_eject);
    }

    if (show_eject_button) {
        eject = get_eject_icon (FALSE);
    } else {
        eject = NULL;
    }

    gtk_list_store_append (sidebar->store, &iter);
    gtk_list_store_set (sidebar->store, &iter,
                        PLACES_SIDEBAR_COLUMN_ICON, pixbuf,
                        PLACES_SIDEBAR_COLUMN_NAME, name,
                        PLACES_SIDEBAR_COLUMN_URI, uri,
                        PLACES_SIDEBAR_COLUMN_DRIVE, drive,
                        PLACES_SIDEBAR_COLUMN_VOLUME, volume,
                        PLACES_SIDEBAR_COLUMN_MOUNT, mount,
                        PLACES_SIDEBAR_COLUMN_ROW_TYPE, place_type,
                        PLACES_SIDEBAR_COLUMN_INDEX, index,
                        PLACES_SIDEBAR_COLUMN_EJECT, show_eject_button,
                        PLACES_SIDEBAR_COLUMN_NO_EJECT, !show_eject_button,
                        PLACES_SIDEBAR_COLUMN_BOOKMARK, place_type != PLACES_BOOKMARK,
                        PLACES_SIDEBAR_COLUMN_TOOLTIP, tooltip,
                        PLACES_SIDEBAR_COLUMN_EJECT_ICON, eject,
                        PLACES_SIDEBAR_COLUMN_SECTION_TYPE, section_type,
                        -1);

    if (pixbuf != NULL)
    {
        g_object_unref (pixbuf);
    }
    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sidebar->filter_model));
    gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (sidebar->filter_model),
            &child_iter,
            &iter);
    return child_iter;
}

static void
compare_for_selection (CajaPlacesSidebar *sidebar,
                       const gchar *location,
                       const gchar *added_uri,
                       const gchar *last_uri,
                       GtkTreeIter *iter,
                       GtkTreePath **path)
{
    int res;

    res = g_strcmp0 (added_uri, last_uri);

    if (res == 0)
    {
        /* last_uri always comes first */
        if (*path != NULL)
        {
            gtk_tree_path_free (*path);
        }
        *path = gtk_tree_model_get_path (sidebar->filter_model,
                                         iter);
    }
    else if (g_strcmp0 (location, added_uri) == 0)
    {
        if (*path == NULL)
        {
            *path = gtk_tree_model_get_path (sidebar->filter_model,
                                             iter);
        }
    }
}

static void
update_places (CajaPlacesSidebar *sidebar)
{
    CajaBookmark *bookmark;
    GtkTreeSelection *selection;
    GtkTreeIter last_iter;
    GtkTreePath *select_path;
    GtkTreeModel *model;
    GVolumeMonitor *volume_monitor;
    GList *mounts, *l, *ll;
    GMount *mount;
    GList *drives;
    GDrive *drive;
    GList *volumes;
    GVolume *volume;
    int bookmark_count, index;
    char *location, *mount_uri, *name, *desktop_path, *last_uri;
    const gchar *path;
    GIcon *icon;
    GFile *root;
    CajaWindowSlotInfo *slot;
    char *tooltip;
    GList *network_mounts;
    GList *xdg_dirs;
    CajaFile *file;

    model = NULL;
    last_uri = NULL;
    select_path = NULL;

    selection = gtk_tree_view_get_selection (sidebar->tree_view);
    if (gtk_tree_selection_get_selected (selection, &model, &last_iter))
    {
        gtk_tree_model_get (model,
                            &last_iter,
                            PLACES_SIDEBAR_COLUMN_URI, &last_uri, -1);
    }
    gtk_list_store_clear (sidebar->store);

    sidebar->devices_header_added = FALSE;
    sidebar->bookmarks_header_added = FALSE;

    slot = caja_window_info_get_active_slot (sidebar->window);
    location = caja_window_slot_info_get_current_location (slot);

    volume_monitor = sidebar->volume_monitor;

    /* first go through all connected drives */
    drives = g_volume_monitor_get_connected_drives (volume_monitor);

    for (l = drives; l != NULL; l = l->next)
    {
        drive = l->data;

        volumes = g_drive_get_volumes (drive);
        if (volumes != NULL)
        {
            for (ll = volumes; ll != NULL; ll = ll->next)
            {
                volume = ll->data;
                mount = g_volume_get_mount (volume);
                if (mount != NULL)
                {
                    /* Show mounted volume in the sidebar */
                    icon = g_mount_get_icon (mount);
                    root = g_mount_get_default_location (mount);
                    mount_uri = g_file_get_uri (root);
                    name = g_mount_get_name (mount);
                    tooltip = g_file_get_parse_name (root);

                    last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
                                           SECTION_DEVICES,
                                           name, icon, mount_uri,
                                           drive, volume, mount, 0, tooltip);
                    compare_for_selection (sidebar,
                                           location, mount_uri, last_uri,
                                           &last_iter, &select_path);
                    g_object_unref (root);
                    g_object_unref (mount);
                    g_object_unref (icon);
                    g_free (tooltip);
                    g_free (name);
                    g_free (mount_uri);
                }
                else
                {
                    /* Do show the unmounted volumes in the sidebar;
                     * this is so the user can mount it (in case automounting
                     * is off).
                     *
                     * Also, even if automounting is enabled, this gives a visual
                     * cue that the user should remember to yank out the media if
                     * he just unmounted it.
                     */
                    icon = g_volume_get_icon (volume);
                    name = g_volume_get_name (volume);
                    tooltip = g_strdup_printf (_("Mount and open %s"), name);

                    last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
                                           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_icon (drive);
                name = g_drive_get_name (drive);
                tooltip = g_strdup_printf (_("Mount and open %s"), name);

                last_iter = add_place (sidebar, PLACES_BUILT_IN,
                                       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;
        }
        mount = g_volume_get_mount (volume);
        if (mount != NULL)
        {
            icon = g_mount_get_icon (mount);
            root = g_mount_get_default_location (mount);
            mount_uri = g_file_get_uri (root);
            tooltip = g_file_get_parse_name (root);
            g_object_unref (root);
            name = g_mount_get_name (mount);
            last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
                                   SECTION_DEVICES,
                                   name, icon, mount_uri,
                                   NULL, volume, mount, 0, tooltip);
            compare_for_selection (sidebar,
                                   location, mount_uri, last_uri,
                                   &last_iter, &select_path);
            g_object_unref (mount);
            g_object_unref (icon);
            g_free (name);
            g_free (tooltip);
            g_free (mount_uri);
        }
        else
        {
            /* see comment above in why we add an icon for an unmounted mountable volume */
            icon = g_volume_get_icon (volume);
            name = g_volume_get_name (volume);
            last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
                                   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);

    /* add bookmarks */
    bookmark_count = caja_bookmark_list_length (sidebar->bookmarks);

    for (index = 0; index < bookmark_count; ++index) {
        bookmark = caja_bookmark_list_item_at (sidebar->bookmarks, index);

        if (caja_bookmark_uri_known_not_to_exist (bookmark)) {
            continue;
        }

        root = caja_bookmark_get_location (bookmark);
        file = caja_file_get (root);

        if (is_built_in_bookmark (file)) {
            g_object_unref (root);
            caja_file_unref (file);
            continue;
        }

        name = caja_bookmark_get_name (bookmark);
        icon = caja_bookmark_get_icon (bookmark);
        mount_uri = caja_bookmark_get_uri (bookmark);
        tooltip = g_file_get_parse_name (root);

        last_iter = add_place (sidebar, PLACES_BOOKMARK,
                               SECTION_BOOKMARKS,
                               name, icon, mount_uri,
                               NULL, NULL, NULL, index,
                               tooltip);
        compare_for_selection (sidebar,
                               location, mount_uri, last_uri,
                               &last_iter, &select_path);
        g_free (name);
        g_object_unref (root);
        g_object_unref (icon);
        g_free (mount_uri);
        g_free (tooltip);
    }

    last_iter = add_heading (sidebar, SECTION_COMPUTER,
                             _("Computer"));

    /* add built in bookmarks */
    desktop_path = caja_get_desktop_directory ();

    /* home folder */
    if (strcmp (g_get_home_dir(), desktop_path) != 0) {
        char *display_name;

        mount_uri = caja_get_home_directory_uri ();
        display_name = g_filename_display_basename (g_get_home_dir ());
        icon = g_themed_icon_new (CAJA_ICON_HOME);
        last_iter = add_place (sidebar, PLACES_BUILT_IN,
                               SECTION_COMPUTER,
                               display_name, icon,
                               mount_uri, NULL, NULL, NULL, 0,
                               _("Open your personal folder"));
        g_object_unref (icon);
        g_free (display_name);
        compare_for_selection (sidebar,
                               location, mount_uri, last_uri,
                               &last_iter, &select_path);
        g_free (mount_uri);
    }

    /* desktop */
    mount_uri = g_filename_to_uri (desktop_path, NULL, NULL);
    icon = g_themed_icon_new (CAJA_ICON_DESKTOP);
    last_iter = add_place (sidebar, PLACES_BUILT_IN,
                           SECTION_COMPUTER,
                           _("Desktop"), icon,
                           mount_uri, NULL, NULL, NULL, 0,
                           _("Open the contents of your desktop in a folder"));
    g_object_unref (icon);
    compare_for_selection (sidebar,
                           location, mount_uri, last_uri,
                           &last_iter, &select_path);
    g_free (mount_uri);
    g_free (desktop_path);

    /* file system root */
    mount_uri = "file:///"; /* No need to strdup */
    icon = g_themed_icon_new (CAJA_ICON_FILESYSTEM);
    last_iter = add_place (sidebar, PLACES_BUILT_IN,
                           SECTION_COMPUTER,
                           _("File System"), icon,
                           mount_uri, NULL, NULL, NULL, 0,
                           _("Open the contents of the File System"));
    g_object_unref (icon);
    compare_for_selection (sidebar,
                           location, mount_uri, last_uri,
                           &last_iter, &select_path);

    
    /* XDG directories */
    xdg_dirs = NULL;
    for (index = 0; index < G_USER_N_DIRECTORIES; index++) {

        if (index == G_USER_DIRECTORY_DESKTOP ||
            index == G_USER_DIRECTORY_TEMPLATES ||
            index == G_USER_DIRECTORY_PUBLIC_SHARE) {
            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 (xdg_dirs, path, (GCompareFunc) g_strcmp0) != NULL) {
            continue;
        }

        root = g_file_new_for_path (path);
        name = g_file_get_basename (root);
        icon = caja_user_special_directory_get_gicon (index);
        mount_uri = g_file_get_uri (root);
        tooltip = g_file_get_parse_name (root);

        last_iter = add_place (sidebar, PLACES_BUILT_IN,
                               SECTION_COMPUTER,
                               name, icon, mount_uri,
                               NULL, NULL, NULL, 0,
                               tooltip);
        compare_for_selection (sidebar,
                               location, mount_uri, last_uri,
                               &last_iter, &select_path);
        g_free (name);
        g_object_unref (root);
        g_object_unref (icon);
        g_free (mount_uri);
        g_free (tooltip);

        xdg_dirs = g_list_prepend (xdg_dirs, (char *)path);
    }
    g_list_free (xdg_dirs);

    /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
    network_mounts = NULL;
    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, g_object_ref (mount));
            continue;
        }

        icon = g_mount_get_icon (mount);
        mount_uri = g_file_get_uri (root);
        name = g_mount_get_name (mount);
        tooltip = g_file_get_parse_name (root);
        last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
                               SECTION_COMPUTER,
                               name, icon, mount_uri,
                               NULL, NULL, mount, 0, tooltip);
        compare_for_selection (sidebar,
                               location, mount_uri, last_uri,
                               &last_iter, &select_path);
        g_object_unref (root);
        g_object_unref (mount);
        g_object_unref (icon);
        g_free (name);
        g_free (mount_uri);
        g_free (tooltip);
    }
    g_list_free (mounts);

    mount_uri = "trash:///"; /* No need to strdup */
    icon = caja_trash_monitor_get_icon ();
    last_iter = add_place (sidebar, PLACES_BUILT_IN,
                           SECTION_COMPUTER,
                           _("Trash"), icon, mount_uri,
                           NULL, NULL, NULL, 0,
                           _("Open the trash"));
    compare_for_selection (sidebar,
                           location, mount_uri, last_uri,
                           &last_iter, &select_path);
    g_object_unref (icon);

    /* network */
    last_iter = add_heading (sidebar, SECTION_NETWORK,
                             _("Network"));

    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_icon (mount);
        mount_uri = g_file_get_uri (root);
        name = g_mount_get_name (mount);
        tooltip = g_file_get_parse_name (root);
        last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
                               SECTION_NETWORK,
                               name, icon, mount_uri,
                               NULL, NULL, mount, 0, tooltip);
        compare_for_selection (sidebar,
                               location, mount_uri, last_uri,
                               &last_iter, &select_path);
        g_object_unref (root);
        g_object_unref (mount);
        g_object_unref (icon);
        g_free (name);
        g_free (mount_uri);
        g_free (tooltip);
    }

    g_list_free_full (network_mounts, g_object_unref);

    /* network:// */
    mount_uri = "network:///"; /* No need to strdup */
    icon = g_themed_icon_new (CAJA_ICON_NETWORK);
    last_iter = 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);
    compare_for_selection (sidebar,
                           location, mount_uri, last_uri,
                           &last_iter, &select_path);
    
    g_free (location);

    if (select_path != NULL) {
        gtk_tree_selection_select_path (selection, select_path);
    }

    if (select_path != NULL) {
        gtk_tree_path_free (select_path);
    }

    g_free (last_uri);
}

static void
mount_added_callback (GVolumeMonitor *volume_monitor,
                      GMount *mount,
                      CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static void
mount_removed_callback (GVolumeMonitor *volume_monitor,
                        GMount *mount,
                        CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static void
mount_changed_callback (GVolumeMonitor *volume_monitor,
                        GMount *mount,
                        CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static void
volume_added_callback (GVolumeMonitor *volume_monitor,
                       GVolume *volume,
                       CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static void
volume_removed_callback (GVolumeMonitor *volume_monitor,
                         GVolume *volume,
                         CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static void
volume_changed_callback (GVolumeMonitor *volume_monitor,
                         GVolume *volume,
                         CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static void
drive_disconnected_callback (GVolumeMonitor *volume_monitor,
                             GDrive         *drive,
                             CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static void
drive_connected_callback (GVolumeMonitor *volume_monitor,
                          GDrive         *drive,
                          CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static void
drive_changed_callback (GVolumeMonitor *volume_monitor,
                        GDrive         *drive,
                        CajaPlacesSidebar *sidebar)
{
    update_places (sidebar);
}

static gboolean
over_eject_button (CajaPlacesSidebar *sidebar,
                   gint x,
                   gint y,
                   GtkTreePath **path)
{
    GtkTreeViewColumn *column;
    GtkTextDirection direction;
    int width, total_width;
    int eject_button_size;
    gboolean show_eject;
    GtkTreeIter iter;
    GtkTreeModel *model;

    *path = NULL;
    model = gtk_tree_view_get_model (sidebar->tree_view);

   if (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;
        }

        total_width = 0;

        gtk_widget_style_get (GTK_WIDGET (sidebar->tree_view),
                              "horizontal-separator", &width,
                              NULL);
        total_width += width;

        direction = gtk_widget_get_direction (GTK_WIDGET (sidebar->tree_view));
        if (direction != GTK_TEXT_DIR_RTL) {
            gtk_tree_view_column_cell_get_position (column,
                                                    sidebar->padding_cell_renderer,
                                                    NULL, &width);
            total_width += width;

            gtk_tree_view_column_cell_get_position (column,
                                                    sidebar->icon_padding_cell_renderer,
                                                    NULL, &width);
            total_width += width;
            
            gtk_tree_view_column_cell_get_position (column,
                                                    sidebar->icon_cell_renderer,
                                                    NULL, &width);
            total_width += width;

            gtk_tree_view_column_cell_get_position (column,
                                                    sidebar->eject_text_cell_renderer,
                                                    NULL, &width);
            total_width += width;
        }

        total_width += EJECT_BUTTON_XPAD;

        eject_button_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);

        if (x - total_width >= 0 &&
            x - total_width <= eject_button_size) {
            return TRUE;
        }
    }

out:
    if (*path != NULL) {
        gtk_tree_path_free (*path);
        *path = NULL;
    }

    return FALSE;
}

static gboolean
clicked_eject_button (CajaPlacesSidebar *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 void
desktop_location_changed_callback (gpointer user_data)
{
    CajaPlacesSidebar *sidebar;

    sidebar = CAJA_PLACES_SIDEBAR (user_data);

    update_places (sidebar);
}

static void
loading_uri_callback (CajaWindowInfo *window,
                      char *location,
                      CajaPlacesSidebar *sidebar)
{
    GtkTreeSelection *selection;
    GtkTreeIter       iter;
    gboolean          valid;
    char             *uri;

    if (strcmp (sidebar->uri, location) != 0)
    {
        g_free (sidebar->uri);
        sidebar->uri = g_strdup (location);

        /* set selection if any place matches location */
        selection = gtk_tree_view_get_selection (sidebar->tree_view);
        gtk_tree_selection_unselect_all (selection);
        valid = gtk_tree_model_get_iter_first (sidebar->filter_model, &iter);

        while (valid)
        {
            gtk_tree_model_get (sidebar->filter_model, &iter,
                                PLACES_SIDEBAR_COLUMN_URI, &uri,
                                -1);
            if (uri != NULL)
            {
                if (strcmp (uri, location) == 0)
                {
                    g_free (uri);
                    gtk_tree_selection_select_iter (selection, &iter);
                    break;
                }
                g_free (uri);
            }
            valid = gtk_tree_model_iter_next (sidebar->filter_model, &iter);
        }
    }
}

/* Computes the appropriate row and position for dropping */
static gboolean
compute_drop_position (GtkTreeView *tree_view,
                       int                      x,
                       int                      y,
                       GtkTreePath            **path,
                       GtkTreeViewDropPosition *pos,
                       CajaPlacesSidebar *sidebar)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    PlaceType place_type;
    SectionType section_type;

    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, &section_type,
                        -1);

    if (place_type == PLACES_HEADING && section_type != SECTION_BOOKMARKS) {
        /* never drop on headings, but special case the bookmarks heading,
         * so we can drop bookmarks in between it and the first item.
         */

        gtk_tree_path_free (*path);
        *path = NULL;

        return FALSE;
    }

    if (section_type != SECTION_BOOKMARKS &&
        sidebar->drag_data_received &&
        sidebar->drag_data_info == GTK_TREE_MODEL_ROW) {
        /* don't allow dropping bookmarks into non-bookmark areas */

    gtk_tree_path_free (*path);
    *path = NULL;

        return FALSE;
    }

    if (section_type == SECTION_BOOKMARKS) {
        *pos = GTK_TREE_VIEW_DROP_AFTER;
    } else {
        /* non-bookmark shortcuts can only be dragged into */
        *pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
    }

    if (*pos != GTK_TREE_VIEW_DROP_BEFORE &&
        sidebar->drag_data_received &&
        sidebar->drag_data_info == GTK_TREE_MODEL_ROW) {
        /* bookmark rows are never dragged into other bookmark rows */
        *pos = GTK_TREE_VIEW_DROP_AFTER;
    }

    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 (CajaPlacesSidebar *sidebar)
{
    sidebar->drag_data_received = FALSE;

    if (sidebar->drag_list)
    {
        caja_drag_destroy_selection_list (sidebar->drag_list);
        sidebar->drag_list = NULL;
    }
}

static gboolean
can_accept_file_as_bookmark (CajaFile *file)
{
    return (caja_file_is_directory (file) &&
            !is_built_in_bookmark (file));
}

static gboolean
can_accept_items_as_bookmarks (const GList *items)
{
    int max;
    char *uri;
    CajaFile *file;

    /* Iterate through selection checking if item will get accepted as a bookmark.
     * If more than 100 items selected, return an over-optimistic result.
     */
    for (max = 100; items != NULL && max >= 0; items = items->next, max--)
    {
        uri = ((CajaDragSelectionItem *)items->data)->uri;
        file = caja_file_get_by_uri (uri);
        if (!can_accept_file_as_bookmark (file))
        {
            caja_file_unref (file);
            return FALSE;
        }
        caja_file_unref (file);
    }

    return TRUE;
}

static gboolean
drag_motion_callback (GtkTreeView *tree_view,
                      GdkDragContext *context,
                      int x,
                      int y,
                      unsigned int time,
                      CajaPlacesSidebar *sidebar)
{
    GtkTreePath *path;
    GtkTreeViewDropPosition pos;
    int action;
    GtkTreeIter iter;
    char *uri;
    gboolean res;

    if (!sidebar->drag_data_received)
    {
        if (!get_drag_data (tree_view, context, time))
        {
            return FALSE;
        }
    }

    path = NULL;
    res = compute_drop_position (tree_view, x, y, &path, &pos, sidebar);

    if (!res) {
        goto out;
    }

    if (pos == GTK_TREE_VIEW_DROP_BEFORE ||
            pos == GTK_TREE_VIEW_DROP_AFTER )
    {
        if (sidebar->drag_data_received &&
                sidebar->drag_data_info == GTK_TREE_MODEL_ROW)
        {
            action = GDK_ACTION_MOVE;
        }
        else if (can_accept_items_as_bookmarks (sidebar->drag_list))
        {
            action = GDK_ACTION_COPY;
        }
        else
        {
            action = 0;
        }
    }
    else
    {
        if (sidebar->drag_list == NULL)
        {
            action = 0;
        }
        else
        {
            gtk_tree_model_get_iter (sidebar->filter_model,
                                     &iter, path);
            gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model),
                                &iter,
                                PLACES_SIDEBAR_COLUMN_URI, &uri,
                                -1);
            caja_drag_default_drop_action_for_icons (context, uri,
                    sidebar->drag_list,
                    &action);
            g_free (uri);
        }
    }

    if (action != 0) {
        gtk_tree_view_set_drag_dest_row (tree_view, path, pos);
    }

    if (path != NULL) {
        gtk_tree_path_free (path);
    }

 out:
    g_signal_stop_emission_by_name (tree_view, "drag-motion");

    if (action != 0)
    {
        gdk_drag_status (context, action, time);
    }
    else
    {
        gdk_drag_status (context, 0, time);
    }

    return TRUE;
}

static void
drag_leave_callback (GtkTreeView *tree_view,
                     GdkDragContext *context,
                     unsigned int time,
                     CajaPlacesSidebar *sidebar)
{
    free_drag_data (sidebar);
    gtk_tree_view_set_drag_dest_row (tree_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
    g_signal_stop_emission_by_name (tree_view, "drag-leave");
}

/* Parses a "text/uri-list" string and inserts its URIs as bookmarks */
static void
bookmarks_drop_uris (CajaPlacesSidebar *sidebar,
                     GtkSelectionData      *selection_data,
                     int                    position)
{
    CajaBookmark *bookmark;
    CajaFile *file;
    char *uri, *name;
    char **uris;
    int i;
    GFile *location;
    GIcon *icon;

    uris = gtk_selection_data_get_uris (selection_data);
    if (!uris)
        return;

    for (i = 0; uris[i]; i++)
    {
        uri = uris[i];
        file = caja_file_get_by_uri (uri);

        if (!can_accept_file_as_bookmark (file))
        {
            caja_file_unref (file);
            continue;
        }

        uri = caja_file_get_drop_target_uri (file);
        location = g_file_new_for_uri (uri);
        caja_file_unref (file);

        name = caja_compute_title_for_location (location);
        icon = g_themed_icon_new (CAJA_ICON_FOLDER);
        bookmark = caja_bookmark_new (location, name, TRUE, icon);

        if (!caja_bookmark_list_contains (sidebar->bookmarks, bookmark))
        {
            caja_bookmark_list_insert_item (sidebar->bookmarks, bookmark, position++);
        }

        g_object_unref (location);
        g_object_unref (bookmark);
        g_object_unref (icon);
        g_free (name);
        g_free (uri);
    }

    g_strfreev (uris);
}

static GList *
uri_list_from_selection (GList *selection)
{
    CajaDragSelectionItem *item;
    GList *ret;
    GList *l;

    ret = NULL;
    for (l = selection; l != NULL; l = l->next)
    {
        item = l->data;
        ret = g_list_prepend (ret, item->uri);
    }

    return g_list_reverse (ret);
}

static GList*
build_selection_list (const char *data)
{
    CajaDragSelectionItem *item;
    GList *result;
    char **uris;
    char *uri;
    int i;

    uris = g_uri_list_extract_uris (data);

    result = NULL;
    for (i = 0; uris[i]; i++)
    {
        uri = uris[i];
        item = caja_drag_selection_item_new ();
        item->uri = g_strdup (uri);
        item->got_icon_position = FALSE;
        result = g_list_prepend (result, item);
    }

    g_strfreev (uris);

    return g_list_reverse (result);
}

static gboolean
get_selected_iter (CajaPlacesSidebar *sidebar,
                   GtkTreeIter *iter)
{
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection (sidebar->tree_view);

    return gtk_tree_selection_get_selected (selection, NULL, iter);
}

/* Reorders the selected bookmark to the specified position */
static void
reorder_bookmarks (CajaPlacesSidebar *sidebar,
                   int                new_position)
{
    GtkTreeIter iter;
    PlaceType type;
    int old_position;

    /* Get the selected path */

    if (!get_selected_iter (sidebar, &iter))
        g_assert_not_reached ();

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
                        PLACES_SIDEBAR_COLUMN_INDEX, &old_position,
                        -1);

    if (type != PLACES_BOOKMARK ||
            old_position < 0 ||
            old_position >= caja_bookmark_list_length (sidebar->bookmarks))
    {
        return;
    }

    caja_bookmark_list_move_item (sidebar->bookmarks, old_position,
                                  new_position);
}

static void
drag_data_received_callback (GtkWidget *widget,
                             GdkDragContext *context,
                             int x,
                             int y,
                             GtkSelectionData *selection_data,
                             unsigned int info,
                             unsigned int time,
                             CajaPlacesSidebar *sidebar)
{
    GtkTreeView *tree_view;
    GtkTreePath *tree_path;
    GtkTreeViewDropPosition tree_pos;
    GtkTreeIter iter;
    int position;
    GtkTreeModel *model;
    char *drop_uri;
    GList *selection_list, *uris;
    PlaceType 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)
        {
            sidebar->drag_list = build_selection_list (gtk_selection_data_get_data (selection_data));
        }
        else
        {
            sidebar->drag_list = NULL;
        }
        sidebar->drag_data_received = TRUE;
        sidebar->drag_data_info = info;
    }

    g_signal_stop_emission_by_name (widget, "drag-data-received");

    if (!sidebar->drop_occured)
    {
        return;
    }

    /* Compute position */
    compute_drop_position (tree_view, x, y, &tree_path, &tree_pos, sidebar);

    success = FALSE;

    if (tree_pos == GTK_TREE_VIEW_DROP_BEFORE ||
            tree_pos == GTK_TREE_VIEW_DROP_AFTER)
    {
        model = gtk_tree_view_get_model (tree_view);

        if (!gtk_tree_model_get_iter (model, &iter, tree_path))
        {
            goto out;
        }

        gtk_tree_model_get (model, &iter,
                            PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type,
                                PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
                            PLACES_SIDEBAR_COLUMN_INDEX, &position,
                            -1);

        if (section_type != SECTION_BOOKMARKS) {
            goto out;
        }

        if (tree_pos == GTK_TREE_VIEW_DROP_AFTER && place_type != PLACES_HEADING) {
            /* heading already has position 0 */
            position++;
        }

        switch (info)
        {
        case TEXT_URI_LIST:
            bookmarks_drop_uris (sidebar, selection_data, position);
            success = TRUE;
            break;
        case GTK_TREE_MODEL_ROW:
            reorder_bookmarks (sidebar, position);
            success = TRUE;
            break;
        default:
            g_assert_not_reached ();
            break;
        }
    }
    else
    {
        GdkDragAction real_action;

        /* file transfer requested */
        real_action = gdk_drag_context_get_selected_action (context);

        if (real_action == GDK_ACTION_ASK)
        {
            real_action =
                caja_drag_drop_action_ask (GTK_WIDGET (tree_view),
                                           gdk_drag_context_get_actions (context));
        }

        if (real_action > 0)
        {
            model = gtk_tree_view_get_model (tree_view);

            gtk_tree_model_get_iter (model, &iter, tree_path);
            gtk_tree_model_get (model, &iter,
                                PLACES_SIDEBAR_COLUMN_URI, &drop_uri,
                                -1);

            switch (info)
            {
            case TEXT_URI_LIST:
                selection_list = build_selection_list (gtk_selection_data_get_data (selection_data));
                uris = uri_list_from_selection (selection_list);
                caja_file_operations_copy_move (uris, NULL, drop_uri,
                                                real_action, GTK_WIDGET (tree_view),
                                                NULL, NULL);
                caja_drag_destroy_selection_list (selection_list);
                g_list_free (uris);
                success = TRUE;
                break;
            case GTK_TREE_MODEL_ROW:
                success = FALSE;
                break;
            default:
                g_assert_not_reached ();
                break;
            }

            g_free (drop_uri);
        }
    }

out:
    sidebar->drop_occured = FALSE;
    free_drag_data (sidebar);
    gtk_drag_finish (context, success, FALSE, time);

    gtk_tree_path_free (tree_path);
}

static gboolean
drag_drop_callback (GtkTreeView *tree_view,
                    GdkDragContext *context,
                    int x,
                    int y,
                    unsigned int time,
                    CajaPlacesSidebar *sidebar)
{
    gboolean retval = FALSE;
    sidebar->drop_occured = TRUE;
    retval = get_drag_data (tree_view, context, time);
    g_signal_stop_emission_by_name (tree_view, "drag-drop");
    return retval;
}

/* Callback used when the file list's popup menu is detached */
static void
bookmarks_popup_menu_detach_cb (GtkWidget *attach_widget,
                                GtkMenu   *menu)
{
    CajaPlacesSidebar *sidebar;

    sidebar = CAJA_PLACES_SIDEBAR (attach_widget);
    g_assert (CAJA_IS_PLACES_SIDEBAR (sidebar));

    sidebar->popup_menu = NULL;
    sidebar->popup_menu_remove_item = NULL;
    sidebar->popup_menu_rename_item = NULL;
    sidebar->popup_menu_separator_item = NULL;
    sidebar->popup_menu_mount_item = NULL;
    sidebar->popup_menu_unmount_item = NULL;
    sidebar->popup_menu_eject_item = NULL;
    sidebar->popup_menu_rescan_item = NULL;
    sidebar->popup_menu_format_item = NULL;
    sidebar->popup_menu_start_item = NULL;
    sidebar->popup_menu_stop_item = NULL;
    sidebar->popup_menu_empty_trash_item = NULL;
}

static void
check_unmount_and_eject (GMount *mount,
                         GVolume *volume,
                         GDrive *drive,
                         gboolean *show_unmount,
                         gboolean *show_eject)
{
    *show_unmount = FALSE;
    *show_eject = FALSE;

    if (drive != NULL)
    {
        *show_eject = g_drive_can_eject (drive);
    }

    if (volume != NULL)
    {
        *show_eject |= g_volume_can_eject (volume);
    }
    if (mount != NULL)
    {
        *show_eject |= g_mount_can_eject (mount);
        *show_unmount = g_mount_can_unmount (mount) && !*show_eject;
    }
}

static void
check_visibility (GMount           *mount,
                  GVolume          *volume,
                  GDrive           *drive,
                  gboolean         *show_mount,
                  gboolean         *show_unmount,
                  gboolean         *show_eject,
                  gboolean         *show_rescan,
                  gboolean         *show_format,
                  gboolean         *show_start,
                  gboolean         *show_stop)
{
    *show_mount = FALSE;
    *show_format = FALSE;
    *show_rescan = FALSE;
    *show_start = FALSE;
    *show_stop = FALSE;

    check_unmount_and_eject (mount, volume, drive, show_unmount, show_eject);

    if (drive != NULL)
    {
        if (g_drive_is_media_removable (drive) &&
                !g_drive_is_media_check_automatic (drive) &&
                g_drive_can_poll_for_media (drive))
            *show_rescan = TRUE;

        *show_start = g_drive_can_start (drive) || g_drive_can_start_degraded (drive);
        *show_stop  = g_drive_can_stop (drive);

        if (*show_stop)
            *show_unmount = FALSE;
    }

    if (volume != NULL)
    {
        if (mount == NULL)
            *show_mount = g_volume_can_mount (volume);
    }
}

static void
bookmarks_check_popup_sensitivity (CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    PlaceType type;
    GDrive *drive = NULL;
    GVolume *volume = NULL;
    GMount *mount = NULL;
    gboolean show_mount;
    gboolean show_unmount;
    gboolean show_eject;
    gboolean show_rescan;
    gboolean show_format;
    gboolean show_start;
    gboolean show_stop;
    gboolean show_empty_trash;
    char *uri = NULL;

    type = PLACES_BUILT_IN;

    if (sidebar->popup_menu == NULL)
    {
        return;
    }

    if (get_selected_iter (sidebar, &iter))
    {
        gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                            PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
                            PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
                            PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
                            PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
                            PLACES_SIDEBAR_COLUMN_URI, &uri,
                            -1);
    }

    gtk_widget_show (sidebar->popup_menu_open_in_new_tab_item);

    gtk_widget_set_sensitive (sidebar->popup_menu_remove_item, (type == PLACES_BOOKMARK));
    gtk_widget_set_sensitive (sidebar->popup_menu_rename_item, (type == PLACES_BOOKMARK));
    gtk_widget_set_sensitive (sidebar->popup_menu_empty_trash_item, !caja_trash_monitor_is_empty ());

    check_visibility (mount, volume, drive,
                      &show_mount, &show_unmount, &show_eject, &show_rescan, &show_format, &show_start, &show_stop);

    /* We actually want both eject and unmount since eject will unmount all volumes.
     * TODO: hide unmount if the drive only has a single mountable volume
     */

    show_empty_trash = (uri != NULL) &&
                       (!strcmp (uri, "trash:///"));

    gtk_widget_set_visible (sidebar->popup_menu_separator_item,
                              show_mount || show_unmount || show_eject || show_format || show_empty_trash);
    gtk_widget_set_visible (sidebar->popup_menu_mount_item, show_mount);
    gtk_widget_set_visible (sidebar->popup_menu_unmount_item, show_unmount);
    gtk_widget_set_visible (sidebar->popup_menu_eject_item, show_eject);
    gtk_widget_set_visible (sidebar->popup_menu_rescan_item, show_rescan);
    gtk_widget_set_visible (sidebar->popup_menu_format_item, show_format);
    gtk_widget_set_visible (sidebar->popup_menu_start_item, show_start);
    gtk_widget_set_visible (sidebar->popup_menu_stop_item, show_stop);
    gtk_widget_set_visible (sidebar->popup_menu_empty_trash_item, show_empty_trash);

    /* Adjust start/stop items to reflect the type of the drive */
    gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Start"));
    gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Stop"));
    if ((show_start || show_stop) && drive != NULL)
    {
        switch (g_drive_get_start_stop_type (drive))
        {
        case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
            /* start() for type G_DRIVE_START_STOP_TYPE_SHUTDOWN is normally not used */
            gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Power On"));
            gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Safely Remove Drive"));
            break;
        case G_DRIVE_START_STOP_TYPE_NETWORK:
            gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Connect Drive"));
            gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Disconnect Drive"));
            break;
        case G_DRIVE_START_STOP_TYPE_MULTIDISK:
            gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Start Multi-disk Device"));
            gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Stop Multi-disk Device"));
            break;
        case G_DRIVE_START_STOP_TYPE_PASSWORD:
            /* stop() for type G_DRIVE_START_STOP_TYPE_PASSWORD is normally not used */
            gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Unlock Drive"));
            gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Lock Drive"));
            break;

        default:
        case G_DRIVE_START_STOP_TYPE_UNKNOWN:
            /* uses defaults set above */
            break;
        }
    }


    g_free (uri);
}

/* Callback used when the selection in the shortcuts tree changes */
static void
bookmarks_selection_changed_cb (GtkTreeSelection      *selection,
                                CajaPlacesSidebar *sidebar)
{
    bookmarks_check_popup_sensitivity (sidebar);
}

static void
volume_mounted_cb (GVolume *volume,
                   GObject *user_data)
{
    GMount *mount;
    CajaPlacesSidebar *sidebar;
    GFile *location;

    sidebar = CAJA_PLACES_SIDEBAR (user_data);

    sidebar->mounting = FALSE;

    mount = g_volume_get_mount (volume);
    if (mount != NULL)
    {
        location = g_mount_get_default_location (mount);

        if (sidebar->go_to_after_mount_slot != NULL)
        {
            if ((sidebar->go_to_after_mount_flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW) == 0)
            {
                caja_window_slot_info_open_location (sidebar->go_to_after_mount_slot, location,
                                                     CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
                                                     sidebar->go_to_after_mount_flags, NULL);
            }
            else
            {
                CajaWindow *cur, *new;

                cur = CAJA_WINDOW (sidebar->window);
                new = caja_application_create_navigation_window (cur->application,
                        NULL,
                        gtk_window_get_screen (GTK_WINDOW (cur)));
                caja_window_go_to (new, location);
            }
        }

        g_object_unref (G_OBJECT (location));
        g_object_unref (G_OBJECT (mount));
    }


    eel_remove_weak_pointer (&(sidebar->go_to_after_mount_slot));
}

static void
drive_start_from_bookmark_cb (GObject      *source_object,
                              GAsyncResult *res,
                              gpointer      user_data)
{
    GError *error;
    char *primary;
    char *name;

    error = NULL;
    if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
    {
        if (error->code != G_IO_ERROR_FAILED_HANDLED)
        {
            name = g_drive_get_name (G_DRIVE (source_object));
            primary = g_strdup_printf (_("Unable to start %s"), name);
            g_free (name);
            eel_show_error_dialog (primary,
                                   error->message,
                                   NULL);
            g_free (primary);
        }
        g_error_free (error);
    }
}

static void
open_selected_bookmark (CajaPlacesSidebar   *sidebar,
                        GtkTreeModel        *model,
                        GtkTreePath         *path,
                        CajaWindowOpenFlags  flags)
{
    CajaWindowSlotInfo *slot;
    GtkTreeIter iter;
    GFile *location;
    char *uri;

    if (!path)
    {
        return;
    }

    if (!gtk_tree_model_get_iter (model, &iter, path))
    {
        return;
    }

    gtk_tree_model_get (model, &iter, PLACES_SIDEBAR_COLUMN_URI, &uri, -1);

    if (uri != NULL)
    {
        caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
                        "activate from places sidebar window=%p: %s",
                        sidebar->window, uri);
        location = g_file_new_for_uri (uri);
        /* Navigate to the clicked location */
        if ((flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW) == 0)
        {
            slot = caja_window_info_get_active_slot (sidebar->window);
            caja_window_slot_info_open_location (slot, location,
                                                 CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
                                                 flags, NULL);
        }
        else
        {
            CajaWindow *cur, *new;

            cur = CAJA_WINDOW (sidebar->window);
            new = caja_application_create_navigation_window (cur->application,
                    NULL,
                    gtk_window_get_screen (GTK_WINDOW (cur)));
            caja_window_go_to (new, location);
        }
        g_object_unref (location);
        g_free (uri);

    }
    else
    {
        GDrive *drive;
        GVolume *volume;
        CajaWindowSlot *slot;

        gtk_tree_model_get (model, &iter,
                            PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
                            PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
                            -1);

        if (volume != NULL && !sidebar->mounting)
        {
            sidebar->mounting = TRUE;

            g_assert (sidebar->go_to_after_mount_slot == NULL);

            slot = caja_window_info_get_active_slot (sidebar->window);
            sidebar->go_to_after_mount_slot = slot;
            eel_add_weak_pointer (&(sidebar->go_to_after_mount_slot));

            sidebar->go_to_after_mount_flags = flags;

            caja_file_operations_mount_volume_full (NULL, volume, FALSE,
                                                    volume_mounted_cb,
                                                    G_OBJECT (sidebar));
        }
        else if (volume == NULL && drive != NULL &&
                 (g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
        {
            GMountOperation *mount_op;

            mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
            g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_from_bookmark_cb, NULL);
            g_object_unref (mount_op);
        }

        if (drive != NULL)
            g_object_unref (drive);
        if (volume != NULL)
            g_object_unref (volume);
    }
}

static void
open_shortcut_from_menu (CajaPlacesSidebar   *sidebar,
                         CajaWindowOpenFlags  flags)
{
    GtkTreeModel *model;
    GtkTreePath *path;

    model = gtk_tree_view_get_model (sidebar->tree_view);
    gtk_tree_view_get_cursor (sidebar->tree_view, &path, NULL);

    open_selected_bookmark (sidebar, model, path, flags);

    gtk_tree_path_free (path);
}

static void
open_shortcut_cb (GtkMenuItem       *item,
                  CajaPlacesSidebar *sidebar)
{
    open_shortcut_from_menu (sidebar, 0);
}

static void
open_shortcut_in_new_window_cb (GtkMenuItem       *item,
                                CajaPlacesSidebar *sidebar)
{
    open_shortcut_from_menu (sidebar, CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW);
}

static void
open_shortcut_in_new_tab_cb (GtkMenuItem       *item,
                             CajaPlacesSidebar *sidebar)
{
    open_shortcut_from_menu (sidebar, CAJA_WINDOW_OPEN_FLAG_NEW_TAB);
}

/* Rename the selected bookmark */
static void
rename_selected_bookmark (CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    GtkTreePath *path;
    GtkTreeViewColumn *column;
    GtkCellRenderer *cell;
    GList *renderers;

    if (get_selected_iter (sidebar, &iter))
    {
        path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->filter_model), &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,
                    CajaPlacesSidebar *sidebar)
{
    rename_selected_bookmark (sidebar);
}

/* Removes the selected bookmarks */
static void
remove_selected_bookmarks (CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    PlaceType type;
    int index;

    if (!get_selected_iter (sidebar, &iter))
    {
        return;
    }

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
                        -1);

    if (type != PLACES_BOOKMARK)
    {
        return;
    }

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_INDEX, &index,
                        -1);

    caja_bookmark_list_delete_item_at (sidebar->bookmarks, index);
}

static void
remove_shortcut_cb (GtkMenuItem           *item,
                    CajaPlacesSidebar *sidebar)
{
    remove_selected_bookmarks (sidebar);
}

static void
mount_shortcut_cb (GtkMenuItem           *item,
                   CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    GVolume *volume;

    if (!get_selected_iter (sidebar, &iter))
    {
        return;
    }

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
                        -1);

    if (volume != NULL)
    {
        caja_file_operations_mount_volume (NULL, volume, FALSE);
        g_object_unref (volume);
    }
}

static void
unmount_done (gpointer data)
{
    CajaWindow *window;

    window = data;
    caja_window_info_set_initiated_unmount (window, FALSE);
    g_object_unref (window);
}

static void
do_unmount (GMount *mount,
            CajaPlacesSidebar *sidebar)
{
    if (mount != NULL)
    {
        caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
        caja_file_operations_unmount_mount_full (NULL, mount, FALSE, TRUE,
                unmount_done,
                g_object_ref (sidebar->window));
    }
}

static void
do_unmount_selection (CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    GMount *mount;

    if (!get_selected_iter (sidebar, &iter))
    {
        return;
    }

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
                        -1);

    if (mount != NULL)
    {
        do_unmount (mount, sidebar);
        g_object_unref (mount);
    }
}

static void
unmount_shortcut_cb (GtkMenuItem           *item,
                     CajaPlacesSidebar *sidebar)
{
    do_unmount_selection (sidebar);
}

static void
drive_eject_cb (GObject *source_object,
                GAsyncResult *res,
                gpointer user_data)
{
    CajaWindow *window;
    GError *error;
    char *primary;
    char *name;

    window = user_data;
    caja_window_info_set_initiated_unmount (window, FALSE);
    g_object_unref (window);

    error = NULL;
    if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), res, &error))
    {
        if (error->code != G_IO_ERROR_FAILED_HANDLED)
        {
            name = g_drive_get_name (G_DRIVE (source_object));
            primary = g_strdup_printf (_("Unable to eject %s"), name);
            g_free (name);
            eel_show_error_dialog (primary,
                                   error->message,
                                   NULL);
            g_free (primary);
        }
        g_error_free (error);
    }
}

static void
volume_eject_cb (GObject *source_object,
                 GAsyncResult *res,
                 gpointer user_data)
{
    CajaWindow *window;
    GError *error;
    char *primary;
    char *name;

    window = user_data;
    caja_window_info_set_initiated_unmount (window, FALSE);
    g_object_unref (window);

    error = NULL;
    if (!g_volume_eject_with_operation_finish (G_VOLUME (source_object), res, &error))
    {
        if (error->code != G_IO_ERROR_FAILED_HANDLED)
        {
            name = g_volume_get_name (G_VOLUME (source_object));
            primary = g_strdup_printf (_("Unable to eject %s"), name);
            g_free (name);
            eel_show_error_dialog (primary,
                                   error->message,
                                   NULL);
            g_free (primary);
        }
        g_error_free (error);
    }
}

static void
mount_eject_cb (GObject *source_object,
                GAsyncResult *res,
                gpointer user_data)
{
    CajaWindow *window;
    GError *error;
    char *primary;
    char *name;

    window = user_data;
    caja_window_info_set_initiated_unmount (window, FALSE);
    g_object_unref (window);

    error = NULL;
    if (!g_mount_eject_with_operation_finish (G_MOUNT (source_object), res, &error))
    {
        if (error->code != G_IO_ERROR_FAILED_HANDLED)
        {
            name = g_mount_get_name (G_MOUNT (source_object));
            primary = g_strdup_printf (_("Unable to eject %s"), name);
            g_free (name);
            eel_show_error_dialog (primary,
                                   error->message,
                                   NULL);
            g_free (primary);
        }
        g_error_free (error);
    }
}

static void
do_eject (GMount *mount,
          GVolume *volume,
          GDrive *drive,
          CajaPlacesSidebar *sidebar)
{
    GMountOperation *mount_op;

    mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
    if (mount != NULL)
    {
        caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
        g_mount_eject_with_operation (mount, 0, mount_op, NULL, mount_eject_cb,
                                      g_object_ref (sidebar->window));
    }
    else if (volume != NULL)
    {
        caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
        g_volume_eject_with_operation (volume, 0, mount_op, NULL, volume_eject_cb,
                                       g_object_ref (sidebar->window));
    }
    else if (drive != NULL)
    {
        caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
        g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb,
                                      g_object_ref (sidebar->window));
    }
    g_object_unref (mount_op);
}

static void
eject_shortcut_cb (GtkMenuItem           *item,
                   CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    GMount *mount;
    GVolume *volume;
    GDrive *drive;

    if (!get_selected_iter (sidebar, &iter))
    {
        return;
    }

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
                        PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
                        PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
                        -1);

    do_eject (mount, volume, drive, sidebar);
}

static gboolean
eject_or_unmount_bookmark (CajaPlacesSidebar *sidebar,
                           GtkTreePath *path)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    gboolean can_unmount, can_eject;
    GMount *mount;
    GVolume *volume;
    GDrive *drive;
    gboolean ret;

    model = GTK_TREE_MODEL (sidebar->filter_model);

    if (!path)
    {
        return FALSE;
    }
    if (!gtk_tree_model_get_iter (model, &iter, path))
    {
        return FALSE;
    }

    gtk_tree_model_get (model, &iter,
                        PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
                        PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
                        PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
                        -1);

    ret = FALSE;

    check_unmount_and_eject (mount, volume, drive, &can_unmount, &can_eject);
    /* if we can eject, it has priority over unmount */
    if (can_eject)
    {
        do_eject (mount, volume, drive, sidebar);
        ret = TRUE;
    }
    else if (can_unmount)
    {
        do_unmount (mount, sidebar);
        ret = TRUE;
    }

    if (mount != NULL)
        g_object_unref (mount);
    if (volume != NULL)
        g_object_unref (volume);
    if (drive != NULL)
        g_object_unref (drive);

    return ret;
}

static gboolean
eject_or_unmount_selection (CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    GtkTreePath *path;
    gboolean ret;

    if (!get_selected_iter (sidebar, &iter)) {
        return FALSE;
    }

    path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->filter_model), &iter);
    if (path == NULL) {
        return FALSE;
    }

    ret = eject_or_unmount_bookmark (sidebar, path);

    gtk_tree_path_free (path);

    return ret;
}

static void
drive_poll_for_media_cb (GObject *source_object,
                         GAsyncResult *res,
                         gpointer user_data)
{
    GError *error;
    char *primary;
    char *name;

    error = NULL;
    if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
    {
        if (error->code != G_IO_ERROR_FAILED_HANDLED)
        {
            name = g_drive_get_name (G_DRIVE (source_object));
            primary = g_strdup_printf (_("Unable to poll %s for media changes"), name);
            g_free (name);
            eel_show_error_dialog (primary,
                                   error->message,
                                   NULL);
            g_free (primary);
        }
        g_error_free (error);
    }
}

static void
rescan_shortcut_cb (GtkMenuItem           *item,
                    CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    GDrive  *drive;

    if (!get_selected_iter (sidebar, &iter))
    {
        return;
    }

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
                        -1);

    if (drive != NULL)
    {
        g_drive_poll_for_media (drive, NULL, drive_poll_for_media_cb, NULL);
    }
    g_object_unref (drive);
}

static void
format_shortcut_cb (GtkMenuItem           *item,
                    CajaPlacesSidebar *sidebar)
{
    g_spawn_command_line_async ("gfloppy", NULL);
}

static void
drive_start_cb (GObject      *source_object,
                GAsyncResult *res,
                gpointer      user_data)
{
    GError *error;
    char *primary;
    char *name;

    error = NULL;
    if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
    {
        if (error->code != G_IO_ERROR_FAILED_HANDLED)
        {
            name = g_drive_get_name (G_DRIVE (source_object));
            primary = g_strdup_printf (_("Unable to start %s"), name);
            g_free (name);
            eel_show_error_dialog (primary,
                                   error->message,
                                   NULL);
            g_free (primary);
        }
        g_error_free (error);
    }
}

static void
start_shortcut_cb (GtkMenuItem           *item,
                   CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    GDrive  *drive;

    if (!get_selected_iter (sidebar, &iter))
    {
        return;
    }

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
                        -1);

    if (drive != NULL)
    {
        GMountOperation *mount_op;

        mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));

        g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_cb, NULL);

        g_object_unref (mount_op);
    }
    g_object_unref (drive);
}

static void
drive_stop_cb (GObject *source_object,
               GAsyncResult *res,
               gpointer user_data)
{
    CajaWindow *window;
    GError *error;
    char *primary;
    char *name;

    window = user_data;
    caja_window_info_set_initiated_unmount (window, FALSE);
    g_object_unref (window);

    error = NULL;
    if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
    {
        if (error->code != G_IO_ERROR_FAILED_HANDLED)
        {
            name = g_drive_get_name (G_DRIVE (source_object));
            primary = g_strdup_printf (_("Unable to stop %s"), name);
            g_free (name);
            eel_show_error_dialog (primary,
                                   error->message,
                                   NULL);
            g_free (primary);
        }
        g_error_free (error);
    }
}

static void
stop_shortcut_cb (GtkMenuItem           *item,
                  CajaPlacesSidebar *sidebar)
{
    GtkTreeIter iter;
    GDrive  *drive;

    if (!get_selected_iter (sidebar, &iter))
    {
        return;
    }

    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
                        -1);

    if (drive != NULL)
    {
        GMountOperation *mount_op;

        mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
        caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
        g_drive_stop (drive, G_MOUNT_UNMOUNT_NONE, mount_op, NULL, drive_stop_cb,
                      g_object_ref (sidebar->window));
        g_object_unref (mount_op);
    }
    g_object_unref (drive);
}

static void
empty_trash_cb (GtkMenuItem           *item,
                CajaPlacesSidebar *sidebar)
{
    caja_file_operations_empty_trash (GTK_WIDGET (sidebar->window));
}

/* Handler for GtkWidget::key-press-event on the shortcuts list */
static gboolean
bookmarks_key_press_event_cb (GtkWidget             *widget,
                              GdkEventKey           *event,
                              CajaPlacesSidebar *sidebar)
{
    guint modifiers;
    GtkTreeModel *model;
    GtkTreePath *path;
    CajaWindowOpenFlags flags = 0;

    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)
    {
        if ((event->state & modifiers) == GDK_SHIFT_MASK)
            flags = CAJA_WINDOW_OPEN_FLAG_NEW_TAB;
        else if ((event->state & modifiers) == GDK_CONTROL_MASK)
            flags = CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW;

        model = gtk_tree_view_get_model(sidebar->tree_view);
        gtk_tree_view_get_cursor(sidebar->tree_view, &path, NULL);

        open_selected_bookmark(sidebar, model, path, flags);

        gtk_tree_path_free(path);
        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_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;
}

/* Constructs the popup menu for the file list if needed */
static void
bookmarks_build_popup_menu (CajaPlacesSidebar *sidebar)
{
    GtkWidget *item;

    if (sidebar->popup_menu)
    {
        return;
    }

    sidebar->popup_menu = gtk_menu_new ();
    gtk_menu_attach_to_widget (GTK_MENU (sidebar->popup_menu),
                               GTK_WIDGET (sidebar),
                               bookmarks_popup_menu_detach_cb);

    item = gtk_image_menu_item_new_with_mnemonic (_("_Open"));
    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
                                   gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU));
    g_signal_connect (item, "activate",
                      G_CALLBACK (open_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab"));
    sidebar->popup_menu_open_in_new_tab_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (open_shortcut_in_new_tab_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window"));
    g_signal_connect (item, "activate",
                      G_CALLBACK (open_shortcut_in_new_window_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    eel_gtk_menu_append_separator (GTK_MENU (sidebar->popup_menu));

    item = gtk_image_menu_item_new_with_label (_("Remove"));
    sidebar->popup_menu_remove_item = item;
    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
                                   gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
    g_signal_connect (item, "activate",
                      G_CALLBACK (remove_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_label (_("Rename..."));
    sidebar->popup_menu_rename_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (rename_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    /* Mount/Unmount/Eject menu items */

    sidebar->popup_menu_separator_item =
        GTK_WIDGET (eel_gtk_menu_append_separator (GTK_MENU (sidebar->popup_menu)));

    item = gtk_menu_item_new_with_mnemonic (_("_Mount"));
    sidebar->popup_menu_mount_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (mount_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_mnemonic (_("_Unmount"));
    sidebar->popup_menu_unmount_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (unmount_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_mnemonic (_("_Eject"));
    sidebar->popup_menu_eject_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (eject_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_mnemonic (_("_Detect Media"));
    sidebar->popup_menu_rescan_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (rescan_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_mnemonic (_("_Format"));
    sidebar->popup_menu_format_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (format_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_mnemonic (_("_Start"));
    sidebar->popup_menu_start_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (start_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    item = gtk_menu_item_new_with_mnemonic (_("_Stop"));
    sidebar->popup_menu_stop_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (stop_shortcut_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    /* Empty Trash menu item */

    item = gtk_menu_item_new_with_mnemonic (_("Empty _Trash"));
    sidebar->popup_menu_empty_trash_item = item;
    g_signal_connect (item, "activate",
                      G_CALLBACK (empty_trash_cb), sidebar);
    gtk_widget_show (item);
    gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);

    bookmarks_check_popup_sensitivity (sidebar);
}

static void
bookmarks_update_popup_menu (CajaPlacesSidebar *sidebar)
{
    bookmarks_build_popup_menu (sidebar);
}

static void
bookmarks_popup_menu (CajaPlacesSidebar *sidebar,
                      GdkEventButton        *event)
{
    bookmarks_update_popup_menu (sidebar);
    eel_pop_up_context_menu (GTK_MENU(sidebar->popup_menu),
                             EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
                             EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
                             event);
}

/* Callback used for the GtkWidget::popup-menu signal of the shortcuts list */
static gboolean
bookmarks_popup_menu_cb (GtkWidget *widget,
                         CajaPlacesSidebar *sidebar)
{
    bookmarks_popup_menu (sidebar, NULL);
    return TRUE;
}

static gboolean
bookmarks_button_release_event_cb (GtkWidget *widget,
                                   GdkEventButton *event,
                                   CajaPlacesSidebar *sidebar)
{
    GtkTreePath *path;
    GtkTreeModel *model;
    GtkTreeView *tree_view;
    gboolean ret;

    path = NULL;

    if (event->type != GDK_BUTTON_RELEASE)
    {
        return TRUE;
    }

    if (clicked_eject_button (sidebar, &path))
    {
        ret = eject_or_unmount_bookmark (sidebar, path);
        gtk_tree_path_free (path);
        return ret;
    }

    tree_view = GTK_TREE_VIEW (widget);
    model = gtk_tree_view_get_model (tree_view);

    if (event->button == 1)
    {

        if (event->window != gtk_tree_view_get_bin_window (tree_view))
        {
            return FALSE;
        }

        gtk_tree_view_get_path_at_pos (tree_view, (int) event->x, (int) event->y,
                                       &path, NULL, NULL, NULL);

        open_selected_bookmark (sidebar, model, path, 0);

        gtk_tree_path_free (path);
    }

    return FALSE;
}

static void
update_eject_buttons (CajaPlacesSidebar *sidebar,
                      GtkTreePath         *path)
{
    GtkTreeIter iter;
    gboolean icon_visible, path_same;

    icon_visible = TRUE;

    if (path == NULL && sidebar->eject_highlight_path == NULL) {
        /* Both are null - highlight up to date */
        return;
    }

    path_same = (path != NULL) &&
        (sidebar->eject_highlight_path != NULL) &&
        (gtk_tree_path_compare (sidebar->eject_highlight_path, path) == 0);

    if (path_same) {
        /* Same path - highlight up to date */
        return;
    }

    if (path) {
        gtk_tree_model_get_iter (GTK_TREE_MODEL (sidebar->filter_model),
                     &iter,
                     path);

        gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model),
                    &iter,
                    PLACES_SIDEBAR_COLUMN_EJECT, &icon_visible,
                    -1);
    }

    if (!icon_visible || path == NULL || !path_same) {
        /* remove highlighting and reset the saved path, as we are leaving
         * an eject button area.
         */
        if (sidebar->eject_highlight_path) {
            gtk_tree_model_get_iter (GTK_TREE_MODEL (sidebar->store),
                         &iter,
                         sidebar->eject_highlight_path);

            gtk_list_store_set (sidebar->store,
                        &iter,
                        PLACES_SIDEBAR_COLUMN_EJECT_ICON, get_eject_icon (FALSE),
                        -1);
            gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sidebar->filter_model));

            gtk_tree_path_free (sidebar->eject_highlight_path);
            sidebar->eject_highlight_path = NULL;
        }

        if (!icon_visible) {
            return;
        }
    }

    if (path != NULL) {
        /* add highlighting to the selected path, as the icon is visible and
         * we're hovering it.
         */
        gtk_tree_model_get_iter (GTK_TREE_MODEL (sidebar->store),
                     &iter,
                     path);
        gtk_list_store_set (sidebar->store,
                    &iter,
                    PLACES_SIDEBAR_COLUMN_EJECT_ICON, get_eject_icon (TRUE),
                    -1);
        gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sidebar->filter_model));

        sidebar->eject_highlight_path = gtk_tree_path_copy (path);
    }
}

static gboolean
bookmarks_motion_event_cb (GtkWidget             *widget,
                           GdkEventMotion        *event,
                           CajaPlacesSidebar *sidebar)
{
    GtkTreePath *path;
    GtkTreeModel *model;

    model = GTK_TREE_MODEL (sidebar->filter_model);
    path = NULL;

    if (over_eject_button (sidebar, event->x, event->y, &path)) {
        update_eject_buttons (sidebar, path);
        gtk_tree_path_free (path);

        return TRUE;
    }

    update_eject_buttons (sidebar, NULL);

    return FALSE;
}

/* Callback used when a button is pressed on the shortcuts list.
 * We trap button 3 to bring up a popup menu, and button 2 to
 * open in a new tab.
 */
static gboolean
bookmarks_button_press_event_cb (GtkWidget             *widget,
                                 GdkEventButton        *event,
                                 CajaPlacesSidebar *sidebar)
{
    if (event->type != GDK_BUTTON_PRESS)
    {
        /* ignore multiple clicks */
        return TRUE;
    }

    if (event->button == 3)
    {
        bookmarks_popup_menu (sidebar, event);
    }
    else if (event->button == 2)
    {
        GtkTreeModel *model;
        GtkTreePath *path;
        GtkTreeView *tree_view;

        tree_view = GTK_TREE_VIEW (widget);
        g_assert (tree_view == sidebar->tree_view);

        model = gtk_tree_view_get_model (tree_view);

        gtk_tree_view_get_path_at_pos (tree_view, (int) event->x, (int) event->y,
                                       &path, NULL, NULL, NULL);

        open_selected_bookmark (sidebar, model, path,
                                event->state & GDK_CONTROL_MASK ?
                                CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW :
                                CAJA_WINDOW_OPEN_FLAG_NEW_TAB);

        if (path != NULL)
        {
            gtk_tree_path_free (path);
            return TRUE;
        }
    }

    return FALSE;
}


static void
bookmarks_edited (GtkCellRenderer       *cell,
                  gchar                 *path_string,
                  gchar                 *new_text,
                  CajaPlacesSidebar *sidebar)
{
    GtkTreePath *path;
    GtkTreeIter iter;
    CajaBookmark *bookmark;
    int index;

    g_object_set (cell, "editable", FALSE, NULL);

    path = gtk_tree_path_new_from_string (path_string);
    gtk_tree_model_get_iter (GTK_TREE_MODEL (sidebar->filter_model), &iter, path);
    gtk_tree_model_get (GTK_TREE_MODEL (sidebar->filter_model), &iter,
                        PLACES_SIDEBAR_COLUMN_INDEX, &index,
                        -1);
    gtk_tree_path_free (path);
    bookmark = caja_bookmark_list_item_at (sidebar->bookmarks, index);

    if (bookmark != NULL)
    {
        caja_bookmark_set_name (bookmark, new_text);
    }
}

static void
bookmarks_editing_canceled (GtkCellRenderer       *cell,
                            CajaPlacesSidebar *sidebar)
{
    g_object_set (cell, "editable", FALSE, NULL);
}

static void
trash_state_changed_cb (CajaTrashMonitor    *trash_monitor,
                        gboolean             state,
                        gpointer             data)
{
    CajaPlacesSidebar *sidebar;

    sidebar = CAJA_PLACES_SIDEBAR (data);

    /* The trash icon changed, update the sidebar */
    update_places (sidebar);

    bookmarks_check_popup_sensitivity (sidebar);
}

static 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)
{
    CajaPlacesSidebar *sidebar;
    PlaceType type;

    sidebar = user_data;

    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 void
caja_places_sidebar_init (CajaPlacesSidebar *sidebar)
{
    GtkTreeView       *tree_view;
    GtkTreeViewColumn *col;
    GtkCellRenderer   *cell;
    GtkTreeSelection  *selection;

    sidebar->volume_monitor = g_volume_monitor_get ();

    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar),
                                    GTK_POLICY_NEVER,
                                    GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
    gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar), GTK_SHADOW_IN);

    /* tree view */
    tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
    gtk_tree_view_set_headers_visible (tree_view, FALSE);

    col = GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ());

    /* initial padding */
    cell = gtk_cell_renderer_text_new ();
    sidebar->padding_cell_renderer = cell;
    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 ();
    sidebar->icon_padding_cell_renderer = cell;
    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 ();
    sidebar->icon_cell_renderer = cell;
    gtk_tree_view_column_pack_start (col, cell, FALSE);
    gtk_tree_view_column_set_attributes (col, cell,
                                         "pixbuf", PLACES_SIDEBAR_COLUMN_ICON,
                                         NULL);
    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 ();
    sidebar->eject_text_cell_renderer = cell;
    gtk_tree_view_column_pack_start (col, cell, TRUE);
    gtk_tree_view_column_set_attributes (col, cell,
                                         "text", PLACES_SIDEBAR_COLUMN_NAME,
                                         "visible", PLACES_SIDEBAR_COLUMN_EJECT,
                                         NULL);
    g_object_set (cell,
                  "ellipsize", PANGO_ELLIPSIZE_END,
                  "ellipsize-set", TRUE,
                  NULL);

    /* eject icon renderer */
    cell = gtk_cell_renderer_pixbuf_new ();
    g_object_set (cell,
                  "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
                  "stock-size", GTK_ICON_SIZE_MENU,
                  "xpad", EJECT_BUTTON_XPAD,
                  NULL);
    gtk_tree_view_column_pack_start (col, cell, FALSE);
    gtk_tree_view_column_set_attributes (col, cell,
                                         "visible", PLACES_SIDEBAR_COLUMN_EJECT,
                                         "pixbuf", PLACES_SIDEBAR_COLUMN_EJECT_ICON,
                                         NULL);

    /* 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), CAJA_ICON_SIZE_SMALLER);
    gtk_tree_view_append_column (tree_view, col);

    sidebar->store = gtk_list_store_new (PLACES_SIDEBAR_COLUMN_COUNT,
                                         G_TYPE_INT,
                                         G_TYPE_STRING,
                                         G_TYPE_DRIVE,
                                         G_TYPE_VOLUME,
                                         G_TYPE_MOUNT,
                                         G_TYPE_STRING,
                                         GDK_TYPE_PIXBUF,
                                         G_TYPE_INT,
                                         G_TYPE_BOOLEAN,
                                         G_TYPE_BOOLEAN,
                                         G_TYPE_BOOLEAN,
                                         G_TYPE_STRING,
                                         GDK_TYPE_PIXBUF,
                                         G_TYPE_INT,
                                         G_TYPE_STRING);

    gtk_tree_view_set_tooltip_column (tree_view, PLACES_SIDEBAR_COLUMN_TOOLTIP);

    sidebar->filter_model = caja_shortcuts_model_filter_new (sidebar,
                            GTK_TREE_MODEL (sidebar->store),
                            NULL);

    gtk_tree_view_set_model (tree_view, sidebar->filter_model);
    gtk_container_add (GTK_CONTAINER (sidebar), GTK_WIDGET (tree_view));
    gtk_widget_show (GTK_WIDGET (tree_view));

    gtk_widget_show (GTK_WIDGET (sidebar));
    sidebar->tree_view = tree_view;

    gtk_tree_view_set_search_column (tree_view, PLACES_SIDEBAR_COLUMN_NAME);
    selection = gtk_tree_view_get_selection (tree_view);
    gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);

    gtk_tree_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,
                                            caja_shortcuts_source_targets,
                                            G_N_ELEMENTS (caja_shortcuts_source_targets),
                                            GDK_ACTION_MOVE);
    gtk_drag_dest_set (GTK_WIDGET (tree_view),
                       0,
                       caja_shortcuts_drop_targets, G_N_ELEMENTS (caja_shortcuts_drop_targets),
                       GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);

    g_signal_connect (tree_view, "key-press-event",
                      G_CALLBACK (bookmarks_key_press_event_cb), sidebar);

    g_signal_connect (tree_view, "drag-motion",
                      G_CALLBACK (drag_motion_callback), sidebar);
    g_signal_connect (tree_view, "drag-leave",
                      G_CALLBACK (drag_leave_callback), sidebar);
    g_signal_connect (tree_view, "drag-data-received",
                      G_CALLBACK (drag_data_received_callback), sidebar);
    g_signal_connect (tree_view, "drag-drop",
                      G_CALLBACK (drag_drop_callback), sidebar);

    g_signal_connect (selection, "changed",
                      G_CALLBACK (bookmarks_selection_changed_cb), sidebar);
    g_signal_connect (tree_view, "popup-menu",
                      G_CALLBACK (bookmarks_popup_menu_cb), sidebar);
    g_signal_connect (tree_view, "button-press-event",
                      G_CALLBACK (bookmarks_button_press_event_cb), sidebar);
    g_signal_connect (tree_view, "motion-notify-event",
                      G_CALLBACK (bookmarks_motion_event_cb), sidebar);
    g_signal_connect (tree_view, "button-release-event",
                      G_CALLBACK (bookmarks_button_release_event_cb), sidebar);

    eel_gtk_tree_view_set_activate_on_single_click (sidebar->tree_view,
            TRUE);

    g_signal_connect_swapped (caja_preferences, "changed::" CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
                              G_CALLBACK(desktop_location_changed_callback),
                              sidebar);

    g_signal_connect_object (caja_trash_monitor_get (),
                             "trash_state_changed",
                             G_CALLBACK (trash_state_changed_cb),
                             sidebar, 0);
}

static void
caja_places_sidebar_dispose (GObject *object)
{
    CajaPlacesSidebar *sidebar;

    sidebar = CAJA_PLACES_SIDEBAR (object);

    sidebar->window = NULL;
    sidebar->tree_view = NULL;

    g_free (sidebar->uri);
    sidebar->uri = NULL;

    free_drag_data (sidebar);

    if (sidebar->eject_highlight_path != NULL) {
        gtk_tree_path_free (sidebar->eject_highlight_path);
        sidebar->eject_highlight_path = NULL;
    }

    g_clear_object (&sidebar->store);
    g_clear_object (&sidebar->volume_monitor);
    g_clear_object (&sidebar->bookmarks);
    g_clear_object (&sidebar->filter_model);

    eel_remove_weak_pointer (&(sidebar->go_to_after_mount_slot));

    g_signal_handlers_disconnect_by_func (caja_preferences,
                                          desktop_location_changed_callback,
                                          sidebar);

    G_OBJECT_CLASS (caja_places_sidebar_parent_class)->dispose (object);
}

static void
caja_places_sidebar_class_init (CajaPlacesSidebarClass *class)
{
    G_OBJECT_CLASS (class)->dispose = caja_places_sidebar_dispose;

    GTK_WIDGET_CLASS (class)->style_set = caja_places_sidebar_style_set;
}

static const char *
caja_places_sidebar_get_sidebar_id (CajaSidebar *sidebar)
{
    return CAJA_PLACES_SIDEBAR_ID;
}

static char *
caja_places_sidebar_get_tab_label (CajaSidebar *sidebar)
{
    return g_strdup (_("Places"));
}

static char *
caja_places_sidebar_get_tab_tooltip (CajaSidebar *sidebar)
{
    return g_strdup (_("Show Places"));
}

static GdkPixbuf *
caja_places_sidebar_get_tab_icon (CajaSidebar *sidebar)
{
    return NULL;
}

static void
caja_places_sidebar_is_visible_changed (CajaSidebar *sidebar,
                                        gboolean         is_visible)
{
    /* Do nothing */
}

static void
caja_places_sidebar_iface_init (CajaSidebarIface *iface)
{
    iface->get_sidebar_id = caja_places_sidebar_get_sidebar_id;
    iface->get_tab_label = caja_places_sidebar_get_tab_label;
    iface->get_tab_tooltip = caja_places_sidebar_get_tab_tooltip;
    iface->get_tab_icon = caja_places_sidebar_get_tab_icon;
    iface->is_visible_changed = caja_places_sidebar_is_visible_changed;
}

static void
caja_places_sidebar_set_parent_window (CajaPlacesSidebar *sidebar,
                                       CajaWindowInfo *window)
{
    CajaWindowSlotInfo *slot;

    sidebar->window = window;

    slot = caja_window_info_get_active_slot (window);

    sidebar->bookmarks = caja_bookmark_list_new ();
    sidebar->uri = caja_window_slot_info_get_current_location (slot);

    g_signal_connect_object (sidebar->bookmarks, "contents_changed",
                             G_CALLBACK (update_places),
                             sidebar, G_CONNECT_SWAPPED);

    g_signal_connect_object (window, "loading_uri",
                             G_CALLBACK (loading_uri_callback),
                             sidebar, 0);

    g_signal_connect_object (sidebar->volume_monitor, "volume_added",
                             G_CALLBACK (volume_added_callback), sidebar, 0);
    g_signal_connect_object (sidebar->volume_monitor, "volume_removed",
                             G_CALLBACK (volume_removed_callback), sidebar, 0);
    g_signal_connect_object (sidebar->volume_monitor, "volume_changed",
                             G_CALLBACK (volume_changed_callback), sidebar, 0);
    g_signal_connect_object (sidebar->volume_monitor, "mount_added",
                             G_CALLBACK (mount_added_callback), sidebar, 0);
    g_signal_connect_object (sidebar->volume_monitor, "mount_removed",
                             G_CALLBACK (mount_removed_callback), sidebar, 0);
    g_signal_connect_object (sidebar->volume_monitor, "mount_changed",
                             G_CALLBACK (mount_changed_callback), sidebar, 0);
    g_signal_connect_object (sidebar->volume_monitor, "drive_disconnected",
                             G_CALLBACK (drive_disconnected_callback), sidebar, 0);
    g_signal_connect_object (sidebar->volume_monitor, "drive_connected",
                             G_CALLBACK (drive_connected_callback), sidebar, 0);
    g_signal_connect_object (sidebar->volume_monitor, "drive_changed",
                             G_CALLBACK (drive_changed_callback), sidebar, 0);

    update_places (sidebar);
}

static void
caja_places_sidebar_style_set (GtkWidget *widget,
                               GtkStyle  *previous_style)
{
    CajaPlacesSidebar *sidebar;

    sidebar = CAJA_PLACES_SIDEBAR (widget);

    update_places (sidebar);
}

static CajaSidebar *
caja_places_sidebar_create (CajaSidebarProvider *provider,
                            CajaWindowInfo *window)
{
    CajaPlacesSidebar *sidebar;

    sidebar = g_object_new (caja_places_sidebar_get_type (), NULL);
    caja_places_sidebar_set_parent_window (sidebar, window);
    g_object_ref_sink (sidebar);

    return CAJA_SIDEBAR (sidebar);
}

static void
sidebar_provider_iface_init (CajaSidebarProviderIface *iface)
{
    iface->create = caja_places_sidebar_create;
}

static void
caja_places_sidebar_provider_init (CajaPlacesSidebarProvider *sidebar)
{
}

static void
caja_places_sidebar_provider_class_init (CajaPlacesSidebarProviderClass *class)
{
}

void
caja_places_sidebar_register (void)
{
    caja_module_add_type (caja_places_sidebar_provider_get_type ());
}

/* Drag and drop interfaces */

static void
_caja_shortcuts_model_filter_class_init (CajaShortcutsModelFilterClass *class)
{
}

static void
_caja_shortcuts_model_filter_init (CajaShortcutsModelFilter *model)
{
    model->sidebar = NULL;
}

/* GtkTreeDragSource::row_draggable implementation for the shortcuts filter model */
static gboolean
caja_shortcuts_model_filter_row_draggable (GtkTreeDragSource *drag_source,
                                           GtkTreePath       *path)
{
    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, &section_type,
                        -1);

    if (place_type != PLACES_HEADING && section_type == SECTION_BOOKMARKS)
        return TRUE;

    return FALSE;
}

/* Fill the GtkTreeDragSourceIface vtable */
static void
caja_shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface)
{
    iface->row_draggable = caja_shortcuts_model_filter_row_draggable;
}

static GtkTreeModel *
caja_shortcuts_model_filter_new (CajaPlacesSidebar *sidebar,
                                 GtkTreeModel          *child_model,
                                 GtkTreePath           *root)
{
    CajaShortcutsModelFilter *model;

    model = g_object_new (CAJA_SHORTCUTS_MODEL_FILTER_TYPE,
                          "child-model", child_model,
                          "virtual-root", root,
                          NULL);

    model->sidebar = sidebar;

    return GTK_TREE_MODEL (model);
}
