/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
 * Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
 * Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
 * Copyright (c) 2012      Nick Schermer <nick@xfce.org>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif

#include "thunar/thunar-device-monitor.h"
#include "thunar/thunar-file.h"
#include "thunar/thunar-preferences.h"
#include "thunar/thunar-private.h"
#include "thunar/thunar-shortcuts-model.h"
#include "thunar/thunar-util.h"

#include <glib.h>
#include <glib/gstdio.h>
#include <libxfce4util/libxfce4util.h>

#define SPINNER_CYCLE_DURATION 1000
#define SPINNER_NUM_STEPS 12



#define THUNAR_SHORTCUT(obj) ((ThunarShortcut *) (obj))



typedef struct _ThunarShortcut ThunarShortcut;



enum
{
  PROP_0,
  PROP_HIDDEN_BOOKMARKS,
  PROP_FILE_SIZE_BINARY
};



static void
thunar_shortcuts_model_tree_model_init (GtkTreeModelIface *iface);
static void
thunar_shortcuts_model_drag_source_init (GtkTreeDragSourceIface *iface);
static void
thunar_shortcuts_model_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec);
static void
thunar_shortcuts_model_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec);
static void
thunar_shortcuts_model_finalize (GObject *object);
static GtkTreeModelFlags
thunar_shortcuts_model_get_flags (GtkTreeModel *tree_model);
static gint
thunar_shortcuts_model_get_n_columns (GtkTreeModel *tree_model);
static GType
thunar_shortcuts_model_get_column_type (GtkTreeModel *tree_model,
                                        gint          idx);
static gboolean
thunar_shortcuts_model_get_iter (GtkTreeModel *tree_model,
                                 GtkTreeIter  *iter,
                                 GtkTreePath  *path);
static GtkTreePath *
thunar_shortcuts_model_get_path (GtkTreeModel *tree_model,
                                 GtkTreeIter  *iter);
static void
thunar_shortcuts_model_get_value (GtkTreeModel *tree_model,
                                  GtkTreeIter  *iter,
                                  gint          column,
                                  GValue       *value);
static gboolean
thunar_shortcuts_model_iter_next (GtkTreeModel *tree_model,
                                  GtkTreeIter  *iter);
static gboolean
thunar_shortcuts_model_iter_children (GtkTreeModel *tree_model,
                                      GtkTreeIter  *iter,
                                      GtkTreeIter  *parent);
static gboolean
thunar_shortcuts_model_iter_has_child (GtkTreeModel *tree_model,
                                       GtkTreeIter  *iter);
static gint
thunar_shortcuts_model_iter_n_children (GtkTreeModel *tree_model,
                                        GtkTreeIter  *iter);
static gboolean
thunar_shortcuts_model_iter_nth_child (GtkTreeModel *tree_model,
                                       GtkTreeIter  *iter,
                                       GtkTreeIter  *parent,
                                       gint          n);
static gboolean
thunar_shortcuts_model_iter_parent (GtkTreeModel *tree_model,
                                    GtkTreeIter  *iter,
                                    GtkTreeIter  *child);
static gboolean
thunar_shortcuts_model_row_draggable (GtkTreeDragSource *source,
                                      GtkTreePath       *path);
static gboolean
thunar_shortcuts_model_drag_data_get (GtkTreeDragSource *source,
                                      GtkTreePath       *path,
                                      GtkSelectionData  *selection_data);
static gboolean
thunar_shortcuts_model_drag_data_delete (GtkTreeDragSource *source,
                                         GtkTreePath       *path);
static void
thunar_shortcuts_model_header_visibility (ThunarShortcutsModel *model);
static void
thunar_shortcuts_model_shortcut_devices (ThunarShortcutsModel *model);
static void
thunar_shortcuts_model_shortcut_places (ThunarShortcutsModel *model);
static void
thunar_shortcuts_model_shortcut_network (ThunarShortcutsModel *model);
static gboolean
thunar_shortcuts_model_get_hidden (ThunarShortcutsModel *model,
                                   ThunarShortcut       *shortcut);
static void
thunar_shortcuts_model_add_shortcut (ThunarShortcutsModel *model,
                                     ThunarShortcut       *shortcut);
static void
thunar_shortcuts_model_remove_shortcut (ThunarShortcut       *shortcut,
                                        ThunarShortcutsModel *model);
static gboolean
thunar_shortcuts_model_load_bookmarks (gpointer data);
static void
thunar_shortcuts_model_save_bookmarks (ThunarShortcutsModel *model);
static void
thunar_shortcuts_model_monitor_bookmarks_file (GFileMonitor     *monitor,
                                               GFile            *file,
                                               GFile            *other_file,
                                               GFileMonitorEvent event_type,
                                               gpointer          user_data);
static void
thunar_shortcuts_model_device_added (ThunarDeviceMonitor  *device_monitor,
                                     ThunarDevice         *device,
                                     ThunarShortcutsModel *model);
static void
thunar_shortcuts_model_device_removed (ThunarDeviceMonitor  *device_monitor,
                                       ThunarDevice         *device,
                                       ThunarShortcutsModel *model);
static void
thunar_shortcuts_model_device_changed (ThunarDeviceMonitor  *device_monitor,
                                       ThunarDevice         *device,
                                       ThunarShortcutsModel *model);
static void
thunar_shortcuts_model_file_changed (ThunarFile           *file,
                                     ThunarShortcutsModel *model);
static void
thunar_shortcuts_model_file_destroyed (ThunarFile           *file,
                                       ThunarShortcutsModel *model);

static GFile *
thunar_shortcut_get_file (ThunarShortcut *shortcut);
static ThunarShortcut *
thunar_shortcut_new (void);
static void
thunar_shortcut_free (ThunarShortcut       *shortcut,
                      ThunarShortcutsModel *model);



struct _ThunarShortcutsModelClass
{
  GObjectClass __parent__;
};

struct _ThunarShortcutsModel
{
  GObject __parent__;

  /* the model stamp is only used when debugging is
   * enabled, to make sure we don't accept iterators
   * generated by another model.
   */
#ifndef NDEBUG
  gint stamp;
#endif

  GList *shortcuts;

  ThunarPreferences *preferences;
  gchar            **hidden_bookmarks;
  gboolean           file_size_binary;

  ThunarDeviceMonitor *device_monitor;

  gint64        bookmarks_time;
  GFile        *bookmarks_file;
  GFileMonitor *bookmarks_monitor;
  guint         bookmarks_idle_id;

  guint busy_timeout_id;
};

struct _ThunarShortcut
{
  ThunarShortcutGroup group;

  gchar *name;
  GIcon *gicon;
  gchar *tooltip;
  gint   sort_id;

  guint busy : 1;
  guint busy_pulse;

  GFile        *location;
  ThunarFile   *file;
  ThunarDevice *device;

  guint hidden : 1;
};

typedef struct
{
  ThunarShortcutsModel *model;
  ThunarShortcut       *shortcut;
  gboolean              update_visibility;
} ThunarShortcutFileGetData;



G_DEFINE_TYPE_WITH_CODE (ThunarShortcutsModel, thunar_shortcuts_model, G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, thunar_shortcuts_model_tree_model_init)
    G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, thunar_shortcuts_model_drag_source_init))



static void
thunar_shortcuts_model_class_init (ThunarShortcutsModelClass *klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->finalize = thunar_shortcuts_model_finalize;
  gobject_class->get_property = thunar_shortcuts_model_get_property;
  gobject_class->set_property = thunar_shortcuts_model_set_property;

  g_object_class_install_property (gobject_class,
                                   PROP_HIDDEN_BOOKMARKS,
                                   g_param_spec_boxed ("hidden-bookmarks",
                                                       NULL,
                                                       NULL,
                                                       G_TYPE_STRV,
                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * ThunarPropertiesDialog:file_size_binary:
   *
   * Whether the file size should be shown in binary or decimal.
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_FILE_SIZE_BINARY,
                                   g_param_spec_boolean ("file-size-binary",
                                                         "FileSizeBinary",
                                                         NULL,
                                                         TRUE,
                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}



static void
thunar_shortcuts_model_tree_model_init (GtkTreeModelIface *iface)
{
  iface->get_flags = thunar_shortcuts_model_get_flags;
  iface->get_n_columns = thunar_shortcuts_model_get_n_columns;
  iface->get_column_type = thunar_shortcuts_model_get_column_type;
  iface->get_iter = thunar_shortcuts_model_get_iter;
  iface->get_path = thunar_shortcuts_model_get_path;
  iface->get_value = thunar_shortcuts_model_get_value;
  iface->iter_next = thunar_shortcuts_model_iter_next;
  iface->iter_children = thunar_shortcuts_model_iter_children;
  iface->iter_has_child = thunar_shortcuts_model_iter_has_child;
  iface->iter_n_children = thunar_shortcuts_model_iter_n_children;
  iface->iter_nth_child = thunar_shortcuts_model_iter_nth_child;
  iface->iter_parent = thunar_shortcuts_model_iter_parent;
}



static void
thunar_shortcuts_model_drag_source_init (GtkTreeDragSourceIface *iface)
{
  iface->row_draggable = thunar_shortcuts_model_row_draggable;
  iface->drag_data_get = thunar_shortcuts_model_drag_data_get;
  iface->drag_data_delete = thunar_shortcuts_model_drag_data_delete;
}



static void
thunar_shortcuts_model_init (ThunarShortcutsModel *model)
{
#ifndef NDEBUG
  model->stamp = g_random_int ();
#endif

  /* hidden bookmarks */
  model->preferences = thunar_preferences_get ();
  g_object_bind_property (model->preferences, "hidden-bookmarks",
                          model, "hidden-bookmarks",
                          G_BINDING_SYNC_CREATE);

  /* binary file size */
  g_object_bind_property (model->preferences, "misc-file-size-binary",
                          model, "file-size-binary",
                          G_BINDING_SYNC_CREATE);

  /* load volumes */
  thunar_shortcuts_model_shortcut_devices (model);

  /* add network */
  thunar_shortcuts_model_shortcut_network (model);

  /* add bookmarks */
  thunar_shortcuts_model_shortcut_places (model);
}



static void
thunar_shortcuts_model_finalize (GObject *object)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (object);

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));

  /* stop the busy timeout */
  if (model->busy_timeout_id != 0)
    g_source_remove (model->busy_timeout_id);

  /* stop bookmark load idle */
  if (model->bookmarks_idle_id != 0)
    g_source_remove (model->bookmarks_idle_id);

  /* free all shortcuts */
  g_list_foreach (model->shortcuts, (GFunc) (void (*) (void)) thunar_shortcut_free, model);
  g_list_free (model->shortcuts);

  /* disconnect from the preferences */
  g_object_unref (model->preferences);

  /* free hidden list */
  g_strfreev (model->hidden_bookmarks);

  /* detach from the file monitor */
  if (model->bookmarks_monitor != NULL)
    {
      g_file_monitor_cancel (model->bookmarks_monitor);
      g_object_unref (model->bookmarks_monitor);
    }

  if (model->bookmarks_file != NULL)
    g_object_unref (model->bookmarks_file);

  /* unlink from the device monitor */
  g_signal_handlers_disconnect_by_data (model->device_monitor, model);
  g_object_unref (model->device_monitor);

  (*G_OBJECT_CLASS (thunar_shortcuts_model_parent_class)->finalize) (object);
}



static void
thunar_shortcuts_model_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (object);

  switch (prop_id)
    {
    case PROP_HIDDEN_BOOKMARKS:
      g_value_set_boxed (value, model->hidden_bookmarks);
      break;

    case PROP_FILE_SIZE_BINARY:
      g_value_set_boolean (value, model->file_size_binary);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



static void
thunar_shortcuts_model_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (object);
  GList                *lp;
  ThunarShortcut       *shortcut;
  gboolean              hidden;
  GtkTreeIter           iter;
  GtkTreePath          *path;
  guint                 idx;

  switch (prop_id)
    {
    case PROP_HIDDEN_BOOKMARKS:
      g_strfreev (model->hidden_bookmarks);
      model->hidden_bookmarks = g_value_dup_boxed (value);

      /* update shortcuts */
      for (lp = model->shortcuts, idx = 0; lp != NULL; lp = lp->next, idx++)
        {
          shortcut = lp->data;

          /* skip devices and headers*/
          if (shortcut->device != NULL
              || (shortcut->file == NULL && shortcut->location == NULL))
            continue;

          /* update state if required */
          hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
          if (shortcut->hidden != hidden)
            {
              shortcut->hidden = hidden;

              GTK_TREE_ITER_INIT (iter, model->stamp, lp);

              path = gtk_tree_path_new_from_indices (idx, -1);
              gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
              gtk_tree_path_free (path);
            }
        }

      /* update header visibility */
      thunar_shortcuts_model_header_visibility (model);
      break;

    case PROP_FILE_SIZE_BINARY:
      model->file_size_binary = g_value_get_boolean (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



static GtkTreeModelFlags
thunar_shortcuts_model_get_flags (GtkTreeModel *tree_model)
{
  return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
}



static gint
thunar_shortcuts_model_get_n_columns (GtkTreeModel *tree_model)
{
  return THUNAR_SHORTCUTS_MODEL_N_COLUMNS;
}



static GType
thunar_shortcuts_model_get_column_type (GtkTreeModel *tree_model,
                                        gint          idx)
{
  switch (idx)
    {
    case THUNAR_SHORTCUTS_MODEL_COLUMN_IS_HEADER:
      return G_TYPE_BOOLEAN;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_IS_ITEM:
      return G_TYPE_BOOLEAN;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_VISIBLE:
      return G_TYPE_BOOLEAN;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_NAME:
      return G_TYPE_STRING;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_TOOLTIP:
      return G_TYPE_STRING;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_FILE:
      return THUNAR_TYPE_FILE;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_LOCATION:
      return G_TYPE_FILE;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_GICON:
      return G_TYPE_ICON;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_DEVICE:
      return THUNAR_TYPE_DEVICE;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_MUTABLE:
      return G_TYPE_BOOLEAN;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_CAN_EJECT:
      return G_TYPE_BOOLEAN;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_GROUP:
      return G_TYPE_UINT;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_BUSY:
      return G_TYPE_BOOLEAN;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_BUSY_PULSE:
      return G_TYPE_UINT;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_HIDDEN:
      return G_TYPE_BOOLEAN;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_DISK_SPACE_USAGE_PERCENT:
      return G_TYPE_INT;
    }

  _thunar_assert_not_reached ();
  return G_TYPE_INVALID;
}



static gboolean
thunar_shortcuts_model_get_iter (GtkTreeModel *tree_model,
                                 GtkTreeIter  *iter,
                                 GtkTreePath  *path)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (tree_model);
  GList                *lp;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);
  _thunar_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);

  /* determine the list item for the path */
  lp = g_list_nth (model->shortcuts, gtk_tree_path_get_indices (path)[0]);
  if (G_LIKELY (lp != NULL))
    {
      GTK_TREE_ITER_INIT (*iter, model->stamp, lp);
      return TRUE;
    }

  return FALSE;
}



static GtkTreePath *
thunar_shortcuts_model_get_path (GtkTreeModel *tree_model,
                                 GtkTreeIter  *iter)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (tree_model);
  gint                  idx;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), NULL);
  _thunar_return_val_if_fail (iter->stamp == model->stamp, NULL);

  /* lookup the list item in the shortcuts list */
  idx = g_list_position (model->shortcuts, iter->user_data);
  if (G_LIKELY (idx >= 0))
    return gtk_tree_path_new_from_indices (idx, -1);

  return NULL;
}



static void
thunar_shortcuts_model_get_value (GtkTreeModel *tree_model,
                                  GtkTreeIter  *iter,
                                  gint          column,
                                  GValue       *value)
{
  ThunarShortcut *shortcut;
  GFile          *file;
  gboolean        can_eject;
  gboolean        file_size_binary;
  gchar          *disk_usage;
  gchar          *device_name;
  gchar          *device_id;
  gchar          *location;
  gchar          *tooltip;
  guint32         trash_items;
  gchar          *parse_name;

  _thunar_return_if_fail (iter->stamp == THUNAR_SHORTCUTS_MODEL (tree_model)->stamp);
  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (tree_model));

  /* determine the shortcut for the list item */
  shortcut = THUNAR_SHORTCUT (((GList *) iter->user_data)->data);
  if (shortcut == NULL)
    return;

  switch (column)
    {
    case THUNAR_SHORTCUTS_MODEL_COLUMN_IS_HEADER:
      g_value_init (value, G_TYPE_BOOLEAN);
      g_value_set_boolean (value, (shortcut->group & THUNAR_SHORTCUT_GROUP_HEADER) != 0);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_IS_ITEM:
      g_value_init (value, G_TYPE_BOOLEAN);
      g_value_set_boolean (value, (shortcut->group & THUNAR_SHORTCUT_GROUP_HEADER) == 0);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_VISIBLE:
      g_value_init (value, G_TYPE_BOOLEAN);
      g_value_set_boolean (value, !shortcut->hidden);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_NAME:
      g_value_init (value, G_TYPE_STRING);
      if (G_UNLIKELY (shortcut->device != NULL))
        g_value_take_string (value, thunar_device_get_name (shortcut->device));
      else if (shortcut->name != NULL)
        g_value_set_static_string (value, shortcut->name);
      else if (shortcut->file != NULL)
        g_value_set_static_string (value, thunar_file_get_display_name (shortcut->file));
      else if (shortcut->location != NULL)
        g_value_take_string (value, thunar_g_file_get_display_name_remote (shortcut->location));
      else
        g_value_set_static_string (value, "");
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_TOOLTIP:
      g_value_init (value, G_TYPE_STRING);
      if ((shortcut->group & THUNAR_SHORTCUT_GROUP_DEVICES) != 0)
        {
          /* get Unix device path */
          if (shortcut->device != NULL)
            device_id = thunar_device_get_identifier_unix (shortcut->device);
          else
            device_id = NULL;

          /* get device root directory */
          file = thunar_shortcut_get_file (shortcut);

          if (file != NULL)
            {
              ThunarFilesystemSpaceInfo fs_size_info;

              if (g_file_has_uri_scheme (file, "file") && thunar_g_file_is_root (file))
                location = g_strdup (_("Browse the file system"));
              else if (!g_file_has_uri_scheme (file, "file"))
                location = g_file_get_uri (file);
              else
                location = thunar_g_file_get_location (file);

              if (device_id != NULL)
                {
                  gchar *tmp = g_strdup_printf ("%s (%s)", location, device_id);
                  g_free (location);
                  g_free (device_id);
                  location = tmp;
                }

              file_size_binary = THUNAR_SHORTCUTS_MODEL (tree_model)->file_size_binary;
              thunar_g_file_get_fs_space (file, &fs_size_info);
              disk_usage = thunar_g_file_get_free_space_string (&fs_size_info, file_size_binary);

              if (disk_usage != NULL)
                tooltip = g_strdup_printf ("%s\n%s", location, disk_usage);
              else
                tooltip = g_strdup_printf ("%s", location);

              g_value_take_string (value, tooltip);

              g_free (location);
              g_free (disk_usage);

              g_object_unref (file);
            }
          else
            {
              if (device_id != NULL)
                {
                  device_name = thunar_device_get_name (shortcut->device);
                  tooltip = g_strdup_printf (_("Mount and open %s (%s)"), device_name, device_id);
                  g_value_take_string (value, tooltip);
                  g_free (device_name);
                  g_free (device_id);
                }
            }
          break;
        }
      else if ((shortcut->group & THUNAR_SHORTCUT_GROUP_PLACES_TRASH) != 0)
        {
          trash_items = thunar_file_get_trash_item_count (shortcut->file);
          if (trash_items == 0)
            {
              g_value_set_static_string (value, _("Trash is empty"));
            }
          else
            {
              tooltip = g_strdup_printf (ngettext ("Trash contains %d file",
                                                   "Trash contains %d files",
                                                   trash_items),
                                         trash_items);
              g_value_take_string (value, tooltip);
            }
          break;
        }
      else if (shortcut->tooltip == NULL)
        {
          if (shortcut->device != NULL)
            {
              file = thunar_device_get_root (shortcut->device);
              if (G_LIKELY (file != NULL))
                {
                  shortcut->tooltip = g_file_get_uri (file);
                  g_object_unref (file);
                }
            }
          else
            {
              if (shortcut->file != NULL)
                file = thunar_file_get_file (shortcut->file);
              else if (shortcut->location != NULL)
                file = shortcut->location;
              else
                file = NULL;

              if (G_LIKELY (file != NULL))
                {
                  parse_name = g_file_get_parse_name (file);
                  shortcut->tooltip = g_markup_escape_text (parse_name, -1);
                  g_free (parse_name);
                }
            }
        }

      g_value_set_static_string (value, shortcut->tooltip);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_FILE:
      g_value_init (value, THUNAR_TYPE_FILE);
      g_value_set_object (value, shortcut->file);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_GICON:
      g_value_init (value, G_TYPE_ICON);
      g_value_set_object (value, shortcut->gicon);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_DEVICE:
      g_value_init (value, THUNAR_TYPE_DEVICE);
      g_value_set_object (value, shortcut->device);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_LOCATION:
      g_value_init (value, G_TYPE_FILE);
      if (shortcut->location != NULL)
        g_value_set_object (value, shortcut->location);
      else if (shortcut->file != NULL)
        g_value_set_object (value, thunar_file_get_file (shortcut->file));
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_MUTABLE:
      g_value_init (value, G_TYPE_BOOLEAN);
      g_value_set_boolean (value,
                           shortcut->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_CAN_EJECT:
      if (shortcut->device != NULL)
        can_eject = thunar_device_can_eject (shortcut->device);
      else
        can_eject = FALSE;

      g_value_init (value, G_TYPE_BOOLEAN);
      g_value_set_boolean (value, can_eject);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_GROUP:
      g_value_init (value, G_TYPE_UINT);
      g_value_set_uint (value, shortcut->group);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_BUSY:
      g_value_init (value, G_TYPE_BOOLEAN);
      g_value_set_boolean (value, shortcut->busy);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_BUSY_PULSE:
      g_value_init (value, G_TYPE_UINT);
      g_value_set_uint (value, shortcut->busy_pulse);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_HIDDEN:
      g_value_init (value, G_TYPE_BOOLEAN);
      g_value_set_boolean (value, FALSE);
      break;

    case THUNAR_SHORTCUTS_MODEL_COLUMN_DISK_SPACE_USAGE_PERCENT:
      /* This property can take values (-1, 0..100), where -1 indicates no value. */
      g_value_init (value, G_TYPE_INT);
      g_value_set_int (value, -1);
      if ((shortcut->group & THUNAR_SHORTCUT_GROUP_DEVICES) != 0)
        {
          file = thunar_shortcut_get_file (shortcut);
          if (file != NULL)
            {
              ThunarFilesystemSpaceInfo fs_size_info;

              /* If the file system size is 0, assume that it could not be retrieved. */
              thunar_g_file_get_fs_space (file, &fs_size_info);
              if (fs_size_info.fs_size_usable_read_ok && fs_size_info.fs_usable_space > 0)
                g_value_set_int (value, (gint) ((gdouble) (fs_size_info.fs_used_space) / ((gdouble) fs_size_info.fs_usable_space / 100.0)));

              g_object_unref (file);
            }
        }
      break;

    default:
      _thunar_assert_not_reached ();
    }
}



static gboolean
thunar_shortcuts_model_iter_next (GtkTreeModel *tree_model,
                                  GtkTreeIter  *iter)
{
  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (tree_model), FALSE);
  _thunar_return_val_if_fail (iter->stamp == THUNAR_SHORTCUTS_MODEL (tree_model)->stamp, FALSE);

  iter->user_data = g_list_next (iter->user_data);
  return (iter->user_data != NULL);
}



static gboolean
thunar_shortcuts_model_iter_children (GtkTreeModel *tree_model,
                                      GtkTreeIter  *iter,
                                      GtkTreeIter  *parent)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (tree_model);

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);

  if (G_LIKELY (parent == NULL && model->shortcuts != NULL))
    {
      GTK_TREE_ITER_INIT (*iter, model->stamp, model->shortcuts);
      return TRUE;
    }

  return FALSE;
}



static gboolean
thunar_shortcuts_model_iter_has_child (GtkTreeModel *tree_model,
                                       GtkTreeIter  *iter)
{
  return FALSE;
}



static gint
thunar_shortcuts_model_iter_n_children (GtkTreeModel *tree_model,
                                        GtkTreeIter  *iter)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (tree_model);

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), 0);

  return (iter == NULL) ? g_list_length (model->shortcuts) : 0;
}



static gboolean
thunar_shortcuts_model_iter_nth_child (GtkTreeModel *tree_model,
                                       GtkTreeIter  *iter,
                                       GtkTreeIter  *parent,
                                       gint          n)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (tree_model);

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);

  if (G_LIKELY (parent == NULL))
    {
      GTK_TREE_ITER_INIT (*iter, model->stamp, g_list_nth (model->shortcuts, n));
      return (iter->user_data != NULL);
    }

  return FALSE;
}



static gboolean
thunar_shortcuts_model_iter_parent (GtkTreeModel *tree_model,
                                    GtkTreeIter  *iter,
                                    GtkTreeIter  *child)
{
  return FALSE;
}



static gboolean
thunar_shortcuts_model_row_draggable (GtkTreeDragSource *source,
                                      GtkTreePath       *path)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (source);
  ThunarShortcut       *shortcut;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);
  _thunar_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);

  /* lookup the ThunarShortcut for the path */
  shortcut = g_list_nth_data (model->shortcuts, gtk_tree_path_get_indices (path)[0]);

  /* special shortcuts cannot be reordered */
  return (shortcut != NULL && shortcut->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS);
}



static gboolean
thunar_shortcuts_model_drag_data_get (GtkTreeDragSource *source,
                                      GtkTreePath       *path,
                                      GtkSelectionData  *selection_data)
{
  /* we simply return FALSE here, as the drag handling is done in
   * the ThunarShortcutsView class.
   */
  return FALSE;
}



static gboolean
thunar_shortcuts_model_drag_data_delete (GtkTreeDragSource *source,
                                         GtkTreePath       *path)
{
  /* we simply return FALSE here, as this function can only be
   * called if the user is re-arranging shortcuts within the
   * model, which will be handle by the exchange method.
   */
  return FALSE;
}



static void
thunar_shortcuts_model_header_visibility (ThunarShortcutsModel *model)
{
  GList          *lp;
  ThunarShortcut *shortcut;
  ThunarShortcut *header;
  guint           n_children = 0;
  GList          *lp_header = NULL;
  gint            idx_header = 0;
  gint            i;
  GtkTreePath    *path;
  GtkTreeIter     iter;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));

  for (lp = model->shortcuts, i = 0; lp != NULL; lp = lp->next, i++)
    {
      shortcut = lp->data;

      if ((shortcut->group & THUNAR_SHORTCUT_GROUP_HEADER) != 0)
        {
          if (lp_header != NULL)
            {
update_header:

              header = lp_header->data;
              if (header->hidden != (n_children == 0))
                {
                  /* set new visibility */
                  header->hidden = (n_children == 0);

                  /* notify view */
                  path = gtk_tree_path_new_from_indices (idx_header, -1);
                  GTK_TREE_ITER_INIT (iter, model->stamp, lp_header);
                  gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
                  gtk_tree_path_free (path);
                }

              if (lp == NULL)
                return;
            }

          /* reset for new */
          idx_header = i;
          lp_header = lp;
          n_children = 0;
        }
      else if (!shortcut->hidden)
        {
          n_children++;
        }
    }

  _thunar_assert (lp == NULL);
  if (lp_header != NULL)
    goto update_header;
}



static void
thunar_shortcuts_model_shortcut_devices (ThunarShortcutsModel *model)
{
  ThunarShortcut *shortcut;
  GList          *devices;
  GList          *lp;

  /* add the devices heading */
  shortcut = thunar_shortcut_new ();
  shortcut->group = THUNAR_SHORTCUT_GROUP_DEVICES_HEADER;
  shortcut->name = g_strdup (_("Devices"));
  thunar_shortcuts_model_add_shortcut (model, shortcut);

  /* the filesystem entry */
  shortcut = thunar_shortcut_new ();
  shortcut->group = THUNAR_SHORTCUT_GROUP_DEVICES_FILESYSTEM;
  shortcut->name = g_strdup (_("File System"));
  shortcut->tooltip = g_strdup (_("Browse the file system"));
  shortcut->file = thunar_file_get_for_uri ("file:///", NULL);
  shortcut->gicon = g_themed_icon_new ("drive-harddisk");
  shortcut->hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
  thunar_shortcuts_model_add_shortcut (model, shortcut);

  /* connect to the device monitor */
  model->device_monitor = thunar_device_monitor_get ();

  /* get a list of all devices available */
  devices = thunar_device_monitor_get_devices (model->device_monitor);
  for (lp = devices; lp != NULL; lp = lp->next)
    {
      thunar_shortcuts_model_device_added (NULL, lp->data, model);
      g_object_unref (G_OBJECT (lp->data));
    }
  g_list_free (devices);

  /* monitor for changes */
  g_signal_connect (model->device_monitor, "device-added", G_CALLBACK (thunar_shortcuts_model_device_added), model);
  g_signal_connect (model->device_monitor, "device-removed", G_CALLBACK (thunar_shortcuts_model_device_removed), model);
  g_signal_connect (model->device_monitor, "device-changed", G_CALLBACK (thunar_shortcuts_model_device_changed), model);

  thunar_shortcuts_model_header_visibility (model);
}



static void
thunar_shortcuts_model_shortcut_network (ThunarShortcutsModel *model)
{
  ThunarShortcut *shortcut;

  /* add the network heading */
  shortcut = thunar_shortcut_new ();
  shortcut->group = THUNAR_SHORTCUT_GROUP_NETWORK_HEADER;
  shortcut->name = g_strdup (_("Network"));
  thunar_shortcuts_model_add_shortcut (model, shortcut);

  /* append the browse network entry if it is supported */
  if (thunar_g_vfs_is_uri_scheme_supported ("network"))
    {
      shortcut = thunar_shortcut_new ();
      shortcut->group = THUNAR_SHORTCUT_GROUP_NETWORK_DEFAULT;
      shortcut->name = g_strdup (_("Browse Network"));
      shortcut->tooltip = g_strdup (_("Browse local network connections"));
      shortcut->location = thunar_g_file_new_for_network ();
      shortcut->gicon = g_themed_icon_new ("network-workgroup");
      shortcut->hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
      thunar_shortcuts_model_add_shortcut (model, shortcut);
    }
}



static void
thunar_shortcuts_model_shortcut_places (ThunarShortcutsModel *model)
{
  ThunarShortcut *shortcut;
  GFile          *home;
  GFile          *desktop;
  GFile          *trash;
  ThunarFile     *file;

  /* add the places heading */
  shortcut = thunar_shortcut_new ();
  shortcut->group = THUNAR_SHORTCUT_GROUP_PLACES_HEADER;
  shortcut->name = g_strdup (_("Places"));
  thunar_shortcuts_model_add_shortcut (model, shortcut);

  /* get home path */
  home = thunar_g_file_new_for_home ();

  /* add home entry */
  file = thunar_file_get (home, NULL);
  if (file != NULL)
    {
      shortcut = thunar_shortcut_new ();
      shortcut->group = THUNAR_SHORTCUT_GROUP_PLACES_DEFAULT;
      shortcut->tooltip = g_strdup (_("Open the home folder"));
      shortcut->file = file;
      shortcut->gicon = g_themed_icon_new ("go-home");
      shortcut->sort_id = 0;
      shortcut->hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
      thunar_shortcuts_model_add_shortcut (model, shortcut);
    }

  /* add desktop entry */
  desktop = thunar_g_file_new_for_desktop ();
  if (!g_file_equal (desktop, home))
    {
      file = thunar_file_get (desktop, NULL);
      if (file != NULL)
        {
          shortcut = thunar_shortcut_new ();
          shortcut->group = THUNAR_SHORTCUT_GROUP_PLACES_DEFAULT;
          shortcut->tooltip = g_strdup (_("Open the desktop folder"));
          shortcut->file = file;
          shortcut->sort_id = 1;
          shortcut->hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
          thunar_shortcuts_model_add_shortcut (model, shortcut);
        }
    }
  g_object_unref (desktop);
  g_object_unref (home);

  /* append the trash icon if the trash is supported */
  if (thunar_g_vfs_is_uri_scheme_supported ("trash"))
    {
      trash = thunar_g_file_new_for_trash ();
      file = thunar_file_get (trash, NULL);
      g_object_unref (trash);

      if (file != NULL)
        {
          shortcut = thunar_shortcut_new ();
          shortcut->group = THUNAR_SHORTCUT_GROUP_PLACES_TRASH;
          shortcut->name = g_strdup (_("Trash"));
          shortcut->file = file;
          shortcut->hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
          thunar_shortcuts_model_add_shortcut (model, shortcut);
        }
    }

  /* determine the URI to the Gtk+ bookmarks file */
  model->bookmarks_file = thunar_g_file_new_for_bookmarks ();

  /* register with the alteration monitor for the bookmarks file */
  model->bookmarks_monitor = g_file_monitor_file (model->bookmarks_file, G_FILE_MONITOR_NONE, NULL, NULL);
  if (G_LIKELY (model->bookmarks_monitor != NULL))
    {
      g_signal_connect (model->bookmarks_monitor, "changed",
                        G_CALLBACK (thunar_shortcuts_model_monitor_bookmarks_file), model);
    }

  /* read the Gtk+ bookmarks file */
  if (model->bookmarks_idle_id == 0)
    model->bookmarks_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT, thunar_shortcuts_model_load_bookmarks, model, NULL);

  /* append the computer icon if browsing the computer is supported */
  if (thunar_g_vfs_is_uri_scheme_supported ("computer"))
    {
      shortcut = thunar_shortcut_new ();
      shortcut->group = THUNAR_SHORTCUT_GROUP_PLACES_COMPUTER;
      shortcut->name = g_strdup (_("Computer"));
      shortcut->tooltip = g_strdup (_("Browse the computer"));
      shortcut->location = thunar_g_file_new_for_computer ();
      shortcut->gicon = g_themed_icon_new ("computer");
      shortcut->hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
      thunar_shortcuts_model_add_shortcut (model, shortcut);
    }

  /* append the recent icon if browsing recent is supported */
  if (thunar_g_vfs_is_uri_scheme_supported ("recent"))
    {
      shortcut = thunar_shortcut_new ();
      shortcut->group = THUNAR_SHORTCUT_GROUP_PLACES_RECENT;
      shortcut->name = g_strdup (_("Recent"));
      shortcut->tooltip = g_strdup (_("Browse recently used files"));
      shortcut->location = thunar_g_file_new_for_recent ();
      shortcut->gicon = g_themed_icon_new ("document-open-recent");
      shortcut->hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
      thunar_shortcuts_model_add_shortcut (model, shortcut);
    }
}



static gboolean
thunar_shortcuts_model_get_hidden (ThunarShortcutsModel *model,
                                   ThunarShortcut       *shortcut)
{
  gchar   *uri;
  guint    n;
  gboolean hidden = FALSE;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);
  _thunar_return_val_if_fail (shortcut->device == NULL, FALSE);

  if (model->hidden_bookmarks == NULL)
    return FALSE;

  /* get the uri */
  if (shortcut->file != NULL)
    uri = thunar_file_dup_uri (shortcut->file);
  else if (shortcut->location != NULL)
    uri = g_file_get_uri (shortcut->location);
  else
    {
      g_warn_if_reached ();
      return FALSE;
    }

  if (uri == NULL)
    return FALSE;

  for (n = 0; !hidden && model->hidden_bookmarks[n] != NULL; n++)
    hidden = (g_strcmp0 (model->hidden_bookmarks[n], uri) == 0);

  g_free (uri);

  return hidden;
}



static gint
thunar_shortcuts_model_sort_func (gconstpointer shortcut_a,
                                  gconstpointer shortcut_b)
{
  const ThunarShortcut *a = shortcut_a;
  const ThunarShortcut *b = shortcut_b;

  /* sort groups */
  if (a->group != b->group)
    return a->group - b->group;

  /* use sort order */
  if (a->sort_id != b->sort_id)
    return a->sort_id > b->sort_id ? 1 : -1;

  if (a->device != NULL && b->device != NULL)
    return thunar_device_compare_by_name (a->device, b->device);

  return g_strcmp0 (a->name, b->name);
}



static void
thunar_shortcuts_model_add_shortcut_with_path (ThunarShortcutsModel *model,
                                               ThunarShortcut       *shortcut,
                                               GtkTreePath          *path)
{
  GtkTreeIter  iter;
  GtkTreePath *sorted_path = NULL;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (shortcut->file == NULL || THUNAR_IS_FILE (shortcut->file));

  if (G_LIKELY (shortcut->file != NULL))
    {
      /* enable monitoring for the file */
      thunar_file_watch (shortcut->file);

      /* subscribe to relevant signals */
      g_signal_connect (G_OBJECT (shortcut->file), "destroy", G_CALLBACK (thunar_shortcuts_model_file_destroyed), model);
      g_signal_connect (G_OBJECT (shortcut->file), "changed", G_CALLBACK (thunar_shortcuts_model_file_changed), model);
    }

  if (path == NULL)
    {
      /* insert the new shortcut to the shortcuts list */
      model->shortcuts = g_list_insert_sorted (model->shortcuts, shortcut, thunar_shortcuts_model_sort_func);
      sorted_path = gtk_tree_path_new_from_indices (g_list_index (model->shortcuts, shortcut), -1);
      path = sorted_path;
    }
  else
    {
      model->shortcuts = g_list_insert (model->shortcuts, shortcut, gtk_tree_path_get_indices (path)[0]);
    }

  /* tell everybody that we have a new shortcut */
  gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
  gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);

  if (sorted_path)
    gtk_tree_path_free (sorted_path);
}



static void
thunar_shortcuts_model_add_shortcut (ThunarShortcutsModel *model,
                                     ThunarShortcut       *shortcut)
{
  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (shortcut->file == NULL || THUNAR_IS_FILE (shortcut->file));

  thunar_shortcuts_model_add_shortcut_with_path (model, shortcut, NULL);
}



static void
thunar_shortcuts_model_remove_shortcut (ThunarShortcut       *shortcut,
                                        ThunarShortcutsModel *model)
{
  GtkTreePath *path;
  gint         idx;

  /* determine the index of the shortcut */
  idx = g_list_index (model->shortcuts, shortcut);
  if (G_LIKELY (idx >= 0))
    {
      /* unlink the shortcut from the model */
      model->shortcuts = g_list_remove (model->shortcuts, shortcut);

      /* tell everybody that we have lost a shortcut */
      path = gtk_tree_path_new_from_indices (idx, -1);
      gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
      gtk_tree_path_free (path);

      /* actually free the shortcut */
      thunar_shortcut_free (shortcut, model);

      /* update header visibility */
      thunar_shortcuts_model_header_visibility (model);
    }
}



static gboolean
thunar_shortcuts_model_local_file (GFile *gfile)
{
  _thunar_return_val_if_fail (G_IS_FILE (gfile), FALSE);

  /* schemes we'd like to have as ThunarFiles in the model */
  if (g_file_has_uri_scheme (gfile, "file")
      || g_file_has_uri_scheme (gfile, "computer")
      || g_file_has_uri_scheme (gfile, "recent"))
    return TRUE;

  return FALSE;
}



static void
thunar_shortcuts_model_load_line (GFile       *file_path,
                                  const gchar *name,
                                  gint         row_num,
                                  gpointer     user_data)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (user_data);
  ThunarShortcut       *shortcut;
  ThunarFile           *file;

  _thunar_return_if_fail (G_IS_FILE (file_path));
  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (name == NULL || g_utf8_validate (name, -1, NULL));

  shortcut = thunar_shortcut_new ();
  shortcut->group = THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS;
  shortcut->location = g_object_ref (file_path);

  /* handle local and remote files differently */
  /* If we dont have a thunar-file, we need to set the gicon manually */
  if (thunar_shortcuts_model_local_file (file_path))
    {
      /* try to open the file corresponding to the uri */
      file = thunar_file_get (file_path, NULL);
      if (G_UNLIKELY (file == NULL))
        shortcut->gicon = g_themed_icon_new ("folder");
      else
        shortcut->file = file;
    }
  else
    {
      shortcut->gicon = g_themed_icon_new ("folder-remote");
    }

  shortcut->sort_id = row_num;
  shortcut->hidden = thunar_shortcuts_model_get_hidden (model, shortcut);
  shortcut->name = g_strdup (name);

  /* append the shortcut to the list */
  thunar_shortcuts_model_add_shortcut (model, shortcut);
}



static gboolean
thunar_shortcuts_model_load_bookmarks (gpointer data)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (data);

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);

  /* parse the bookmarks */
  thunar_util_load_bookmarks (model->bookmarks_file,
                              thunar_shortcuts_model_load_line,
                              model);

  /* update the visibility */
  thunar_shortcuts_model_header_visibility (model);

  model->bookmarks_idle_id = 0;

  return FALSE;
}



static gboolean
thunar_shortcuts_model_reload_bookmarks (gpointer data)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (data);
  GList                *lp;
  GList                *places_bookmarks = NULL;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);

  /* Find all user-defined shortcuts (bookmarks) */
  for (lp = model->shortcuts; lp != NULL; lp = lp->next)
    {
      ThunarShortcut *shortcut = THUNAR_SHORTCUT (lp->data);
      if (shortcut->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS)
        places_bookmarks = g_list_append (places_bookmarks, shortcut);
    }

  /* drop them from the model */
  g_list_foreach (places_bookmarks, (GFunc) (void (*) (void)) thunar_shortcuts_model_remove_shortcut, model);
  g_list_free (places_bookmarks);

  /* reload them from the bookmarks file */
  thunar_shortcuts_model_load_bookmarks (model);

  /* reset the idle ID */
  model->bookmarks_idle_id = 0;

  return FALSE;
}



static void
thunar_shortcuts_model_monitor_bookmarks_file (GFileMonitor     *monitor,
                                               GFile            *file,
                                               GFile            *other_file,
                                               GFileMonitorEvent event_type,
                                               gpointer          user_data)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (user_data);

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (model->bookmarks_monitor == monitor);

  /* leave if we saved less than 2 seconds ago */
  if (model->bookmarks_time + 2 * G_USEC_PER_SEC > g_get_real_time ())
    return;

  /* reload the bookmarks when idle */
  if (model->bookmarks_idle_id == 0)
    model->bookmarks_idle_id = g_idle_add (thunar_shortcuts_model_reload_bookmarks, model);
}



static void
thunar_shortcuts_model_save_bookmarks (ThunarShortcutsModel *model)
{
  GString        *contents;
  ThunarShortcut *shortcut;
  gchar          *bookmarks_path;
  gchar          *uri;
  GList          *lp;
  GError         *err = NULL;
  GFile          *parent = NULL;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));

  contents = g_string_new (NULL);

  for (lp = model->shortcuts; lp != NULL; lp = lp->next)
    {
      shortcut = THUNAR_SHORTCUT (lp->data);
      if (shortcut->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS)
        {
          if (shortcut->file != NULL)
            uri = thunar_file_dup_uri (shortcut->file);
          else if (shortcut->location != NULL)
            uri = g_file_get_uri (shortcut->location);
          else
            continue;

          if (G_LIKELY (shortcut->name != NULL))
            g_string_append_printf (contents, "%s %s\n", uri, shortcut->name);
          else
            g_string_append_printf (contents, "%s\n", uri);
          g_free (uri);
        }
    }

  /* create folder if it does not exist */
  parent = g_file_get_parent (model->bookmarks_file);
  if (!g_file_make_directory_with_parents (parent, NULL, &err))
    {
      if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_EXISTS))
        {
          g_clear_error (&err);
        }
      else
        {
          g_warning ("Failed to create bookmarks folder: %s", err->message);
          g_error_free (err);
        }
    }
  g_clear_object (&parent);

  /* write data to the disk */
  bookmarks_path = g_file_get_path (model->bookmarks_file);
  if (!g_file_set_contents (bookmarks_path, contents->str, contents->len, &err))
    {
      g_warning ("Failed to write \"%s\": %s", bookmarks_path, err->message);
      g_error_free (err);
    }
  g_free (bookmarks_path);
  g_string_free (contents, TRUE);

  /* store the save time */
  model->bookmarks_time = g_get_real_time ();
}



static void
thunar_shortcuts_model_device_added_finish (GFile      *location,
                                            ThunarFile *file,
                                            GError     *error,
                                            gpointer    user_data)
{
  ThunarShortcutFileGetData *data = user_data;
  ThunarShortcut            *shortcut = data->shortcut;
  ThunarShortcutsModel      *model = data->model;

  if (error == NULL)
    {
      shortcut->file = g_object_ref (file);

      /* insert in the model */
      thunar_shortcuts_model_add_shortcut (model, shortcut);

      if (data->update_visibility)
        thunar_shortcuts_model_header_visibility (model);
    }

  g_object_unref (location);
  g_object_unref (model);
  g_slice_free (ThunarShortcutFileGetData, data);
}



static void
thunar_shortcuts_model_device_added (ThunarDeviceMonitor  *device_monitor,
                                     ThunarDevice         *device,
                                     ThunarShortcutsModel *model)
{
  ThunarShortcut            *shortcut;
  ThunarShortcutFileGetData *data;
  GFile                     *mount_point;

  _thunar_return_if_fail (device_monitor == NULL || THUNAR_DEVICE_MONITOR (device_monitor));
  _thunar_return_if_fail (device_monitor == NULL || model->device_monitor == device_monitor);
  _thunar_return_if_fail (THUNAR_IS_DEVICE (device));
  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));

  /* allocate a new shortcut */
  shortcut = thunar_shortcut_new ();
  shortcut->device = g_object_ref (device);
  shortcut->hidden = thunar_device_get_hidden (device);

  switch (thunar_device_get_kind (device))
    {
    case THUNAR_DEVICE_KIND_VOLUME:
      shortcut->group = THUNAR_SHORTCUT_GROUP_DEVICES_VOLUMES;
      break;

    case THUNAR_DEVICE_KIND_MOUNT_LOCAL:
      shortcut->group = THUNAR_SHORTCUT_GROUP_DEVICES_MOUNTS;
      break;

    case THUNAR_DEVICE_KIND_MOUNT_REMOTE:
      shortcut->group = THUNAR_SHORTCUT_GROUP_NETWORK_MOUNTS;
      break;
    }

  /* header visibility if call is from monitor */
  gboolean update_visibility = (device_monitor != NULL && !shortcut->hidden);

  mount_point = thunar_device_get_root (device);
  if (mount_point != NULL)
    {
      data = g_slice_new0 (ThunarShortcutFileGetData);
      data->shortcut = shortcut;
      data->model = g_object_ref (model);
      data->update_visibility = update_visibility;

      thunar_file_get_async (mount_point, NULL, &thunar_shortcuts_model_device_added_finish, data);
    }
  else /* some devices (e.g. usb flashdrives) do not have a mountpoint */
    {
      /* insert in the model */
      thunar_shortcuts_model_add_shortcut (model, shortcut);

      if (update_visibility)
        thunar_shortcuts_model_header_visibility (model);
    }
}



static void
thunar_shortcuts_model_device_removed (ThunarDeviceMonitor  *device_monitor,
                                       ThunarDevice         *device,
                                       ThunarShortcutsModel *model)
{
  GList *lp;

  _thunar_return_if_fail (THUNAR_DEVICE_MONITOR (device_monitor));
  _thunar_return_if_fail (model->device_monitor == device_monitor);
  _thunar_return_if_fail (THUNAR_IS_DEVICE (device));
  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));

  /* find the shortcut */
  for (lp = model->shortcuts; lp != NULL; lp = lp->next)
    if (THUNAR_SHORTCUT (lp->data)->device == device)
      break;

  /* something is broken if we don't have a shortcut here */
  if (lp == NULL)
    {
      gchar *device_name = thunar_device_get_name (device);
      g_warning ("No shortcut found for device '%s'", device_name);
      g_free (device_name);
      return;
    }

  _thunar_assert (THUNAR_SHORTCUT (lp->data)->device == device);

  /* drop the shortcut from the model */
  if (G_LIKELY (lp != NULL))
    thunar_shortcuts_model_remove_shortcut (lp->data, model);
}



static void
thunar_shortcuts_model_device_changed (ThunarDeviceMonitor  *device_monitor,
                                       ThunarDevice         *device,
                                       ThunarShortcutsModel *model)
{
  GtkTreeIter     iter;
  GList          *lp;
  gint            idx;
  GtkTreePath    *path;
  ThunarShortcut *shortcut;
  GFile          *mount_point;
  gboolean        update_header = FALSE;

  _thunar_return_if_fail (THUNAR_DEVICE_MONITOR (device_monitor));
  _thunar_return_if_fail (model->device_monitor == device_monitor);
  _thunar_return_if_fail (THUNAR_IS_DEVICE (device));
  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));

  /* find the shortcut */
  for (lp = model->shortcuts, idx = 0; lp != NULL; lp = lp->next, idx++)
    if (THUNAR_SHORTCUT (lp->data)->device == device)
      break;

  /* something is broken if we don't have a shortcut here */
  if (lp == NULL)
    {
      gchar *device_name = thunar_device_get_name (device);
      g_warning ("No shortcut found for device '%s'", device_name);
      g_free (device_name);
      return;
    }

  _thunar_assert (THUNAR_SHORTCUT (lp->data)->device == device);

  if (G_LIKELY (lp != NULL))
    {
      shortcut = lp->data;

      /* cleanup tooltip */
      g_free (shortcut->tooltip);
      shortcut->tooltip = NULL;

      if (shortcut->file == NULL)
        {
          mount_point = thunar_device_get_root (device);
          if (mount_point != NULL)
            {
              shortcut->file = thunar_file_get (mount_point, NULL);

              /* enable monitoring for the file */
              if (shortcut->file != NULL)
                thunar_file_watch (shortcut->file);

              g_object_unref (mount_point);
            }
        }

      /* hidden state */
      if (shortcut->hidden != thunar_device_get_hidden (device))
        {
          shortcut->hidden = thunar_device_get_hidden (device);
          update_header = TRUE;
        }

      /* generate an iterator for the path */
      GTK_TREE_ITER_INIT (iter, model->stamp, lp);

      /* tell the view that the volume has changed in some way */
      path = gtk_tree_path_new_from_indices (idx, -1);
      gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
      gtk_tree_path_free (path);
    }

  /* header visibility */
  if (update_header)
    thunar_shortcuts_model_header_visibility (model);
}



static void
thunar_shortcuts_model_file_destroyed (ThunarFile           *file,
                                       ThunarShortcutsModel *model)
{
  GtkTreeIter  iter;
  GList       *lp;
  gint         idx;
  GtkTreePath *path;

  /* find the shortcut */
  for (lp = model->shortcuts, idx = 0; lp != NULL; lp = lp->next, idx++)
    {
      ThunarShortcut *shortcut = THUNAR_SHORTCUT (lp->data);
      if (shortcut->file == file)
        {
          /* Disconnect from relevant signals */
          thunar_file_unwatch (file);
          g_signal_handlers_disconnect_by_data (shortcut->file, model);
          g_object_unref (shortcut->file);
          shortcut->file = NULL;

          /* remove the thunar file from the list and set the g_file instead */
          if (shortcut->location == NULL)
            {
              shortcut->location = g_object_ref (thunar_file_get_file (file)) ;
            }

          if (shortcut->gicon == NULL)
            {
              if (thunar_shortcuts_model_local_file (shortcut->location))
                shortcut->gicon = g_themed_icon_new ("folder");
              else
                shortcut->gicon = g_themed_icon_new ("folder-remote");
            }

          /* generate an iterator for the path */
          GTK_TREE_ITER_INIT (iter, model->stamp, lp);

          /* tell the view that the file has changed in some way */
          path = gtk_tree_path_new_from_indices (idx, -1);
          gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
          gtk_tree_path_free (path);

          break;
        }
    }
}



static void
thunar_shortcuts_model_file_changed (ThunarFile           *file,
                                     ThunarShortcutsModel *model)
{
  GtkTreeIter  iter;
  GList       *lp;
  gint         idx;
  GtkTreePath *path;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (THUNAR_IS_FILE (file));


  for (lp = model->shortcuts, idx = 0; lp != NULL; lp = lp->next, idx++)
    {
      ThunarShortcut *shortcut = THUNAR_SHORTCUT (lp->data);

      if (shortcut->file != file)
        continue;

      GFile *g_file = thunar_file_get_file (file);

      /* Only update the shortcut if trash has changed or the related g_file has changed (rename) */
      if (thunar_file_is_trash (file) || (shortcut->location != NULL && g_file_equal (g_file, shortcut->location) == FALSE))
        {
          /* generate an iterator for the path */
          GTK_TREE_ITER_INIT (iter, model->stamp, lp);

          /* tell the view that the file has changed in some way */
          path = gtk_tree_path_new_from_indices (idx, -1);
          gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
          gtk_tree_path_free (path);

          /* If the changed shortcut is a bookmark, save the changes */
          if (shortcut->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS)
            thunar_shortcuts_model_save_bookmarks (model);
        }
      break;
    }
}



static GFile *
thunar_shortcut_get_file (ThunarShortcut *shortcut)
{
  if (shortcut->device != NULL)
    return thunar_device_get_root (shortcut->device);
  else if (shortcut->file != NULL)
    return g_object_ref (thunar_file_get_file (shortcut->file));

  return NULL;
}



static ThunarShortcut *
thunar_shortcut_new (void)
{
  ThunarShortcut *shortcut;

  shortcut = g_slice_new0 (ThunarShortcut);

  shortcut->group = 0;

  shortcut->name = NULL;
  shortcut->gicon = NULL;
  shortcut->tooltip = NULL;
  shortcut->sort_id = 0;

  shortcut->busy = 0;
  shortcut->busy_pulse = 0;

  shortcut->location = NULL;
  shortcut->file = NULL;
  shortcut->device = NULL;
  shortcut->hidden = 0;

  return shortcut;
}



static void
thunar_shortcut_free (ThunarShortcut       *shortcut,
                      ThunarShortcutsModel *model)
{
  if (G_LIKELY (shortcut->file != NULL))
    {
      /* disable monitoring for the file */
      thunar_file_unwatch (shortcut->file);
      g_signal_handlers_disconnect_by_data (shortcut->file, model);
      g_object_unref (shortcut->file);
      shortcut->file = NULL;
    }

  if (G_LIKELY (shortcut->device != NULL))
    g_object_unref (shortcut->device);

  if (G_LIKELY (shortcut->gicon != NULL))
    g_object_unref (shortcut->gicon);

  if (G_LIKELY (shortcut->location != NULL))
    g_object_unref (shortcut->location);

  g_free (shortcut->name);
  g_free (shortcut->tooltip);

  /* release the shortcut itself */
  g_slice_free (ThunarShortcut, shortcut);
}



static gboolean
thunar_shortcuts_model_busy_timeout (gpointer data)
{
  ThunarShortcutsModel *model = THUNAR_SHORTCUTS_MODEL (data);
  gboolean              keep_running = FALSE;
  ThunarShortcut       *shortcut;
  GtkTreePath          *path;
  guint                 idx;
  GtkTreeIter           iter;
  GList                *lp;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (data), FALSE);

  for (lp = model->shortcuts, idx = 0; lp != NULL; lp = lp->next, idx++)
    {
      shortcut = lp->data;
      if (!shortcut->busy)
        continue;

      /* loop the pulse of the shortcut */
      shortcut->busy_pulse++;
      if (shortcut->busy_pulse >= SPINNER_NUM_STEPS)
        shortcut->busy_pulse = 0;

      /* generate an iterator for the path */
      GTK_TREE_ITER_INIT (iter, model->stamp, lp);

      /* notify the views about the change */
      path = gtk_tree_path_new_from_indices (idx, -1);
      gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
      gtk_tree_path_free (path);

      /* keep the timeout running */
      keep_running = TRUE;
    }

  return keep_running;
}



static void
thunar_shortcuts_model_busy_timeout_destroyed (gpointer data)
{
  THUNAR_SHORTCUTS_MODEL (data)->busy_timeout_id = 0;
}



/**
 * thunar_shortcuts_model_get_default:
 *
 * Returns the default #ThunarShortcutsModel instance shared by
 * all #ThunarShortcutsView instances.
 *
 * Call #g_object_unref() on the returned object when you
 * don't need it any longer.
 *
 * Return value: the default #ThunarShortcutsModel instance.
 **/
ThunarShortcutsModel *
thunar_shortcuts_model_get_default (void)
{
  static ThunarShortcutsModel *model = NULL;

  if (G_UNLIKELY (model == NULL))
    {
      model = g_object_new (THUNAR_TYPE_SHORTCUTS_MODEL, NULL);
      g_object_add_weak_pointer (G_OBJECT (model), (gpointer) &model);
    }
  else
    {
      g_object_ref (G_OBJECT (model));
    }

  return model;
}



/**
 * thunar_shortcuts_model_has_bookmark:
 * @model : a #ThunarShortcutsModel instance.
 * @file  : a #ThuanrFile instance.
 *
 * Returns %TRUE if there is a bookmark (not a mount or volume) with
 * @file as destination.
 *
 * Return value: %TRUE if @file was found, else %FALSE.
 **/
gboolean
thunar_shortcuts_model_has_bookmark (ThunarShortcutsModel *model,
                                     GFile                *file)
{
  GList          *lp;
  ThunarShortcut *shortcut;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);
  _thunar_return_val_if_fail (G_IS_FILE (file), FALSE);

  for (lp = model->shortcuts; lp != NULL; lp = lp->next)
    {
      shortcut = lp->data;

      /* only check bookmarks */
      if (shortcut->group != THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS)
        continue;

      if (shortcut->file != NULL
          && g_file_equal (thunar_file_get_file (shortcut->file), file))
        return TRUE;

      if (shortcut->location != NULL
          && g_file_equal (shortcut->location, file))
        return TRUE;
    }

  return FALSE;
}



/**
 * thunar_shortcuts_model_iter_for_file:
 * @model : a #ThunarShortcutsModel instance.
 * @file  : a #ThuanrFile instance.
 * @iter  : pointer to a #GtkTreeIter.
 *
 * Tries to lookup the #GtkTreeIter, that belongs to a shortcut, which
 * refers to @file and stores it to @iter. If no such #GtkTreeIter was
 * found, %FALSE will be returned and @iter won't be changed. Else
 * %TRUE will be returned and @iter will be set appropriately.
 *
 * Return value: %TRUE if @file was found, else %FALSE.
 **/
gboolean
thunar_shortcuts_model_iter_for_file (ThunarShortcutsModel *model,
                                      ThunarFile           *file,
                                      GtkTreeIter          *iter)
{
  GFile          *mount_point;
  GList          *lp;
  ThunarShortcut *shortcut;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);
  _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
  _thunar_return_val_if_fail (iter != NULL, FALSE);

  for (lp = model->shortcuts; lp != NULL; lp = lp->next)
    {
      shortcut = lp->data;

      /* check if we have a location that matches */
      if (shortcut->file == file)
        {
          GTK_TREE_ITER_INIT (*iter, model->stamp, lp);
          return TRUE;
        }

      if (shortcut->location != NULL
          && g_file_equal (shortcut->location, thunar_file_get_file (file)))
        {
          GTK_TREE_ITER_INIT (*iter, model->stamp, lp);
          return TRUE;
        }

      /* but maybe we have a mounted(!) volume with a matching mount point */
      if (shortcut->device != NULL
          && !thunar_device_get_hidden (shortcut->device))
        {
          mount_point = thunar_device_get_root (shortcut->device);
          if (mount_point != NULL)
            {
              if (g_file_equal (mount_point, thunar_file_get_file (file)))
                {
                  GTK_TREE_ITER_INIT (*iter, model->stamp, lp);
                  g_object_unref (mount_point);
                  return TRUE;
                }

              g_object_unref (mount_point);
            }
        }
    }

  return FALSE;
}



/**
 * thunar_shortcuts_model_drop_possible:
 * @model : a #ThunarShortcutstModel.
 * @path  : a #GtkTreePath.
 *
 * Determines whether a drop is possible before the given @path, at the same depth
 * as @path. I.e., can we drop data at that location. @path does not have to exist;
 * the return value will almost certainly be FALSE if the parent of @path doesn't
 * exist, though.
 *
 * Return value: %TRUE if it's possible to drop data before @path, else %FALSE.
 **/
gboolean
thunar_shortcuts_model_drop_possible (ThunarShortcutsModel *model,
                                      GtkTreePath          *path)
{
  ThunarShortcut *shortcut;

  _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model), FALSE);
  _thunar_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);

  /* determine the list item for the path */
  shortcut = g_list_nth_data (model->shortcuts, gtk_tree_path_get_indices (path)[0]);

  /* append to the list is not possible */
  if (G_LIKELY (shortcut == NULL))
    return FALSE;

  /* cannot drop before special shortcuts! */
  if (shortcut->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS)
    return TRUE;

  /* we can drop at the end of the bookmarks (before devices header) */
  if (shortcut->group == THUNAR_SHORTCUT_GROUP_DEVICES_HEADER)
    return TRUE;

  return FALSE;
}



/**
 * thunar_shortcuts_model_add:
 * @model    : a #ThunarShortcutsModel.
 * @dst_path : the destination path.
 * @file     : the #ThunarFile that should be added to the shortcuts list.
 *
 * Adds the shortcut @file to the @model at @dst_path, unless @file is
 * already present in @model in which case no action is performed.
 **/
void
thunar_shortcuts_model_add (ThunarShortcutsModel *model,
                            GtkTreePath          *dst_path,
                            gpointer              file)
{
  ThunarShortcut *shortcut;
  GFile          *location;
  GList          *lp;
  guint           position = 0;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (dst_path == NULL || gtk_tree_path_get_depth (dst_path) > 0);
  _thunar_return_if_fail (dst_path == NULL || gtk_tree_path_get_indices (dst_path)[0] >= 0);
  _thunar_return_if_fail (dst_path == NULL || gtk_tree_path_get_indices (dst_path)[0] <= (gint) g_list_length (model->shortcuts));
  _thunar_return_if_fail (THUNAR_IS_FILE (file) || G_IS_FILE (file));

  location = G_IS_FILE (file) ? file : thunar_file_get_file (file);

  /* verify that the file is not already in use as shortcut */
  if (thunar_shortcuts_model_has_bookmark (model, location))
    return;

  /* create the new shortcut that will be inserted */
  shortcut = thunar_shortcut_new ();
  shortcut->group = THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS;
  shortcut->location = G_FILE (g_object_ref (G_OBJECT (location)));

  if (thunar_shortcuts_model_local_file (location))
    shortcut->file = thunar_file_get (location, NULL);
  else
    shortcut->gicon = g_themed_icon_new ("folder-remote");

  /* if no position was given, place the shortcut at the bottom */
  if (dst_path == NULL)
    {
      for (lp = model->shortcuts; lp != NULL; lp = lp->next)
        if (THUNAR_SHORTCUT (lp->data)->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS)
          position++;

      shortcut->sort_id = ++position;
    }

  /* add the shortcut to the list at the given position */
  thunar_shortcuts_model_add_shortcut_with_path (model, shortcut, dst_path);

  /* the shortcuts list was changed, so write the gtk bookmarks file */
  thunar_shortcuts_model_save_bookmarks (model);
}



/**
 * thunar_shortcuts_model_move:
 * @model    : a #ThunarShortcutsModel.
 * @src_path : the source path.
 * @dst_path : the destination path.
 *
 * Moves the shortcut at @src_path to @dst_path, adjusting other
 * shortcut's positions as required.
 **/
void
thunar_shortcuts_model_move (ThunarShortcutsModel *model,
                             GtkTreePath          *src_path,
                             GtkTreePath          *dst_path)
{
  ThunarShortcut *shortcut;
  GtkTreePath    *path;
  GList          *lp;
  gint           *order;
  gint            index_src;
  gint            index_dst;
  gint            idx;
  gint            n_shortcuts;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (gtk_tree_path_get_depth (src_path) > 0);
  _thunar_return_if_fail (gtk_tree_path_get_depth (dst_path) > 0);
  _thunar_return_if_fail (gtk_tree_path_get_indices (src_path)[0] >= 0);
  _thunar_return_if_fail (gtk_tree_path_get_indices (src_path)[0] < (gint) g_list_length (model->shortcuts));
  _thunar_return_if_fail (gtk_tree_path_get_indices (dst_path)[0] > 0);

  index_src = gtk_tree_path_get_indices (src_path)[0];
  index_dst = gtk_tree_path_get_indices (dst_path)[0];

  if (G_UNLIKELY (index_src == index_dst))
    return;

  /* generate the order for the rows prior the dst/src rows */
  n_shortcuts = g_list_length (model->shortcuts);
  order = g_newa (gint, n_shortcuts);
  for (idx = 0, lp = model->shortcuts; idx < index_src && idx < index_dst; ++idx, lp = lp->next)
    order[idx] = idx;

  if (idx == index_src)
    {
      shortcut = THUNAR_SHORTCUT (lp->data);

      for (; idx < index_dst; ++idx, lp = lp->next)
        {
          if (lp->next == NULL)
            break;
          lp->data = lp->next->data;
          order[idx] = idx + 1;
        }

      lp->data = shortcut;
      order[idx++] = index_src;
    }
  else
    {
      for (; idx < index_src; ++idx, lp = lp->next)
        ;

      _thunar_assert (idx == index_src);

      shortcut = THUNAR_SHORTCUT (lp->data);

      for (; idx > index_dst; --idx, lp = lp->prev)
        {
          lp->data = lp->prev->data;
          order[idx] = idx - 1;
        }

      _thunar_assert (idx == index_dst);

      lp->data = shortcut;
      order[idx] = index_src;
      idx = index_src + 1;
    }

  /* generate the remaining order */
  for (; idx < n_shortcuts; ++idx)
    order[idx] = idx;

  /* tell all listeners about the reordering just performed */
  path = gtk_tree_path_new_first ();
  gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), path, NULL, order);
  gtk_tree_path_free (path);

  /* the shortcuts list was changed, so write the gtk bookmarks file */
  thunar_shortcuts_model_save_bookmarks (model);
}



/**
 * thunar_shortcuts_model_remove:
 * @model : a #ThunarShortcutsModel.
 * @path  : the #GtkTreePath of the shortcut to remove.
 *
 * Removes the shortcut at @path from the @model and syncs to
 * on-disk storage. @path must refer to a valid, user-defined
 * shortcut, as you cannot remove system-defined entities (they
 * are managed internally).
 **/
void
thunar_shortcuts_model_remove (ThunarShortcutsModel *model,
                               GtkTreePath          *path)
{
  ThunarShortcut *shortcut;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (gtk_tree_path_get_depth (path) > 0);
  _thunar_return_if_fail (gtk_tree_path_get_indices (path)[0] >= 0);
  _thunar_return_if_fail (gtk_tree_path_get_indices (path)[0] < (gint) g_list_length (model->shortcuts));

  /* lookup the shortcut for the given path */
  shortcut = g_list_nth_data (model->shortcuts, gtk_tree_path_get_indices (path)[0]);

  /* verify that the shortcut is removable */
  _thunar_assert (shortcut->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS);

  /* remove the shortcut (using the file destroy handler) */
  thunar_shortcuts_model_remove_shortcut (shortcut, model);

  /* the bookmark list was changed, so write the gtk bookmarks file */
  thunar_shortcuts_model_save_bookmarks (model);
}



/**
 * thunar_shortcuts_model_rename:
 * @model : a #ThunarShortcutsModel.
 * @iter  : the #GtkTreeIter which refers to the shortcut that
 *          should be renamed to @name.
 * @name  : the new name for the shortcut at @path or %NULL to
 *          return to the default name.
 *
 * Renames the shortcut at @iter to the new @name in @model.
 *
 * @name may be %NULL or an empty to reset the shortcut to
 * its default name.
 **/
void
thunar_shortcuts_model_rename (ThunarShortcutsModel *model,
                               GtkTreeIter          *iter,
                               const gchar          *name)
{
  ThunarShortcut *shortcut;
  GtkTreePath    *path;

  _thunar_return_if_fail (name == NULL || g_utf8_validate (name, -1, NULL));
  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (iter->stamp == model->stamp);
  _thunar_return_if_fail (iter->user_data != NULL);

  /* lookup the shortcut for the given path */
  shortcut = THUNAR_SHORTCUT (((GList *) iter->user_data)->data);

  /* verify the shortcut */
  _thunar_assert (shortcut->group == THUNAR_SHORTCUT_GROUP_PLACES_BOOKMARKS);

  /* perform the rename */
  if (G_UNLIKELY (shortcut->name != NULL))
    g_free (shortcut->name);
  if (G_UNLIKELY (name == NULL || *name == '\0'))
    shortcut->name = NULL;
  else
    shortcut->name = g_strdup (name);

  /* notify the views about the change */
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, iter);
  gtk_tree_path_free (path);

  /* save the changes to the model */
  thunar_shortcuts_model_save_bookmarks (model);
}



void
thunar_shortcuts_model_set_busy (ThunarShortcutsModel *model,
                                 ThunarDevice         *device,
                                 gboolean              busy)
{
  ThunarShortcut *shortcut;
  GList          *lp;
  guint           idx;
  GtkTreeIter     iter;
  GtkTreePath    *path;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (THUNAR_IS_DEVICE (device));

  /* get the device */
  for (lp = model->shortcuts, idx = 0; lp != NULL; lp = lp->next, idx++)
    if (THUNAR_SHORTCUT (lp->data)->device == device)
      break;

  if (lp == NULL)
    return;

  shortcut = lp->data;
  _thunar_assert (shortcut->device == device);

  if (G_LIKELY (shortcut->busy != busy))
    {
      shortcut->busy = busy;

      if (busy && model->busy_timeout_id == 0)
        {
          /* start the global cycle timeout */
          model->busy_timeout_id =
          gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT,
                                        SPINNER_CYCLE_DURATION / SPINNER_NUM_STEPS,
                                        thunar_shortcuts_model_busy_timeout, model,
                                        thunar_shortcuts_model_busy_timeout_destroyed);
        }
      else if (!busy)
        {
          /* generate an iterator for the path */
          GTK_TREE_ITER_INIT (iter, model->stamp, lp);

          /* notify the views about the change */
          path = gtk_tree_path_new_from_indices (idx, -1);
          gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
          gtk_tree_path_free (path);
        }
    }
}



void
thunar_shortcuts_model_set_hidden (ThunarShortcutsModel *model,
                                   GtkTreePath          *path,
                                   gboolean              hidden)
{
  ThunarShortcut *shortcut;
  guint           length;
  gchar         **bookmarks;
  guint           pos;
  gchar          *uri;
  guint           n;

  _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model));
  _thunar_return_if_fail (gtk_tree_path_get_depth (path) > 0);
  _thunar_return_if_fail (gtk_tree_path_get_indices (path)[0] >= 0);
  _thunar_return_if_fail (gtk_tree_path_get_indices (path)[0] < (gint) g_list_length (model->shortcuts));

  /* lookup the shortcut for the given path */
  shortcut = g_list_nth_data (model->shortcuts, gtk_tree_path_get_indices (path)[0]);
  _thunar_return_if_fail (shortcut != NULL);

  if (shortcut->device != NULL)
    {
      /* if this is a device, store in the device monitor */
      thunar_device_monitor_set_hidden (model->device_monitor, shortcut->device, hidden);
      return;
    }

  /* get the uri */
  if (shortcut->file != NULL)
    uri = thunar_file_dup_uri (shortcut->file);
  else if (shortcut->location != NULL)
    uri = g_file_get_uri (shortcut->location);
  else
    {
      g_warn_if_reached ();
      return;
    }

  /* prepare array */
  length = model->hidden_bookmarks != NULL ? g_strv_length (model->hidden_bookmarks) : 0;
  bookmarks = g_new0 (gchar *, length + 2);
  pos = 0;

  /* copy other uuid in the new list */
  if (model->hidden_bookmarks != NULL)
    {
      for (n = 0; model->hidden_bookmarks[n] != NULL; n++)
        if (g_strcmp0 (model->hidden_bookmarks[n], uri) != 0)
          bookmarks[pos++] = g_strdup (model->hidden_bookmarks[n]);
    }

  /* add the new uri if it should hide */
  if (hidden)
    bookmarks[pos++] = uri;
  else
    g_free (uri);

  /* store new list */
  g_object_set (G_OBJECT (model->preferences), "hidden-bookmarks", bookmarks, NULL);
  g_strfreev (bookmarks);

  /* header visibility */
  thunar_shortcuts_model_header_visibility (model);
}



void
thunar_shortcuts_model_reload (ThunarShortcutsModel *model)
{
  GList          *l;
  ThunarShortcut *shortcut;

  for (l = model->shortcuts; l != NULL; l = l->next)
    {
      shortcut = l->data;

      if (shortcut->device != NULL)
        thunar_device_reload_file (shortcut->device);
    }

  thunar_shortcuts_model_reload_bookmarks (model);
}
