/*
  Copyright (C) 2008 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as 
 * published by the Free Software Foundation.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 * Authored by Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <gconf/gconf.h>
#include <gconf/gconf-client.h>

#include "launcher-sidebar.h"

#include <gio/gio.h>
#include <glib/gi18n.h>
#include <string.h>
#include <tidy/tidy.h>

#include "clutter-focus-group.h"

#include "launcher-config.h"
#include "launcher-defines.h"
#include "clutter-drag-dest.h"
#include "clutter-drag-server.h"
#include "launcher-icon.h"
#include "launcher-shortcut.h"
#include "launcher-notify.h"
#include "launcher-util.h"
#include "launcher-quit.h"

static void clutter_focus_group_iface_init (ClutterFocusGroupIface *iface);
static void on_link_clicked (ClutterActor *shortcut, gpointer null);

G_DEFINE_TYPE_WITH_CODE (LauncherSidebar, 
                         launcher_sidebar, 
                         CLUTTER_TYPE_GROUP,
                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_FOCUS_GROUP,
                                              clutter_focus_group_iface_init));


#define LAUNCHER_SIDEBAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  LAUNCHER_TYPE_SIDEBAR, \
  LauncherSidebarPrivate))

#define NAUTILUS  "xdg-open \"%s\""

struct _LauncherSidebarPrivate
{
  GVolumeMonitor *monitor;
  GFile          *bookfile;
  GFileMonitor   *bookfile_mon;

  ClutterActor   *bg;
  ClutterActor   *clip;
  ClutterActor   *grid;
  ClutterActor   *scroll;
  TidyAdjustment *adjust;

  GList *shortcuts;
  GList *links;
  GList *places;

  GSList *excluded_volumes;

  ClutterActor *focused;

  ClutterTimeline *timeline;
  ClutterEffectTemplate *temp;

  gboolean first_run;
};

enum
{
  SHORTCUTS_CHANGED,
  
  LAST_SIGNAL
};
static guint32 _sidebar_signals[LAST_SIGNAL] = { 0 };

GList *
launcher_sidebar_get_places (LauncherSidebar *sidebar)
{
  LauncherSidebarPrivate *priv;
  GList *places = NULL;

  g_return_val_if_fail (LAUNCHER_IS_SIDEBAR (sidebar), NULL);
  priv = sidebar->priv;

  places = g_list_concat (places, g_list_copy (priv->shortcuts));
  places = g_list_concat (places, g_list_copy (priv->places));

  return places;
}

gboolean
launcher_sidebar_scroll_event (ClutterActor       *actor,
                              ClutterScrollEvent *event,
                              LauncherSidebar     *bar)
{
  TidyAdjustment *adjust;
  LauncherConfig *cfg = launcher_config_get_default ();
  gdouble diff = cfg->shortcut_height;
  gdouble lower, upper;

  if (!LAUNCHER_IS_SIDEBAR (bar)) return FALSE;
  adjust = bar->priv->adjust;

  if (!TIDY_IS_ADJUSTMENT (adjust))
    return TRUE;

  if (event->direction == CLUTTER_SCROLL_UP)
    diff = -cfg->icon_height;

  diff += tidy_adjustment_get_value (adjust);

  tidy_adjustment_get_values (adjust, NULL, &lower, &upper, NULL, NULL, NULL);

  diff = CLAMP (diff, lower, upper);

  tidy_adjustment_set_value (adjust, diff);

  return TRUE;
}
static void
ensure_layout (LauncherSidebar *bar)
{
  LauncherSidebarPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  TidyAdjustment *adjustment;
  ClutterActor *scroll, *tex, *frame;
  GList *s;
  gint bar_width = cfg->bar_width;
  gint bar_height = cfg->shortcut_height;
  gint offset = 0;
  gint i = 0;

  g_return_if_fail (LAUNCHER_IS_SIDEBAR (bar));
  priv = bar->priv;

  for (s = priv->shortcuts; s; s = s->next)
  {
    ClutterActor *actor = s->data;

    if (!CLUTTER_IS_ACTOR (actor))
      continue;
    
    clutter_actor_set_position (actor, 0, offset);

    offset += bar_height;
    i++;
  }

  for (s = priv->places; s; s = s->next)
  {
    ClutterActor *actor = s->data;

    if (!CLUTTER_IS_ACTOR (actor))
      continue;

    clutter_actor_set_position (actor, 0, offset);

    offset += bar_height;
    i++;
  }
  
  clutter_actor_set_size (priv->grid, bar_width, i * bar_height);

  if (i > N_CATS-2)
  {
    tidy_scrollable_get_adjustments (TIDY_SCROLLABLE (priv->grid), 
                                     NULL, &adjustment);
    priv->adjust = adjustment;
    tidy_adjustment_set_values (adjustment, 
                                0,
                                0, i * bar_height,
                                bar_height,
                                1 * bar_width,
                                (N_CATS-2) * bar_height);

    if (priv->scroll)
    {
      tidy_scroll_bar_set_adjustment (TIDY_SCROLL_BAR (priv->scroll), 
                                      adjustment);
      return;
    }
    tex = launcher_util_texture_new_from_file (PKGDATADIR"/scrollbar.svg");
    clutter_actor_set_opacity (tex, 0);
    clutter_container_add_actor (CLUTTER_CONTAINER (priv->grid), tex);
    
    frame = tidy_texture_frame_new (CLUTTER_TEXTURE (tex), 5, 5, 5, 5);
    clutter_actor_set_reactive (CLUTTER_ACTOR (frame), TRUE);
    g_signal_connect (frame, "scroll-event",
                      G_CALLBACK (launcher_sidebar_scroll_event), adjustment); 
        
    scroll = priv->scroll = tidy_scroll_bar_new_with_handle (priv->adjust, 
                                                             frame);
    
    frame = tidy_texture_frame_new (CLUTTER_TEXTURE (tex), 5, 5, 5, 5);
    clutter_actor_set_reactive (CLUTTER_ACTOR (frame), TRUE);
    g_signal_connect (frame, "scroll-event",
                      G_CALLBACK (launcher_sidebar_scroll_event), adjustment);
    clutter_actor_set_reactive (CLUTTER_ACTOR (scroll), TRUE);
    g_signal_connect (scroll, "scroll-event",
                      G_CALLBACK (launcher_sidebar_scroll_event), adjustment);
    clutter_actor_set_opacity (frame, 100);
    tidy_scroll_bar_set_texture (TIDY_SCROLL_BAR (scroll), frame);

    clutter_container_add_actor (CLUTTER_CONTAINER (priv->clip), scroll);
    clutter_actor_set_size (scroll, bar_height * (N_CATS-2) -2, 5);
    clutter_actor_set_anchor_point_from_gravity (scroll, 
                                                 CLUTTER_GRAVITY_CENTER);
    clutter_actor_set_position (scroll, 
                                bar_width- 10/2,
                                ((bar_height*(N_CATS-2))/2) + 2);
    clutter_actor_set_opacity (scroll, 100);
    clutter_actor_set_rotation (scroll, CLUTTER_Z_AXIS, 90, 0, 0, 0);
    clutter_actor_show (scroll);
  }  
  else if (CLUTTER_IS_ACTOR (priv->scroll))
  {
    clutter_actor_set_opacity (priv->scroll, 0);
  }

  g_signal_emit (bar, _sidebar_signals[SHORTCUTS_CHANGED], 0);
}


/*
 * PLACES 
 */
static void
on_places_mount (GFile *file, GAsyncResult *res, LauncherShortcut *shortcut)
{
  ClutterTexture *temp;
  GError *error = NULL;
  gboolean success;
  
  success = g_file_mount_enclosing_volume_finish (file, res, &error);

  if (success)
  {
    g_app_info_launch_default_for_uri (g_file_get_uri (file), NULL, &error);
    temp = launcher_shortcut_get_texture (LAUNCHER_SHORTCUT (shortcut));
    launcher_notify_popup (launcher_notify_get_default (),
                    launcher_shortcut_get_label (LAUNCHER_SHORTCUT (shortcut)),
                           temp, 
                           -1);

  }
  else
  { 
    GtkWidget *dialog;
    gchar *uri;

    uri = g_file_get_uri (file);

    dialog = gtk_message_dialog_new_with_markup (NULL, 
                                     0,
                                     GTK_MESSAGE_ERROR,
                                     GTK_BUTTONS_CLOSE,
                                     _("<b>Unable to open %s:</b>\n%s"),
                                     uri, 
                                     error ? error->message : _("Unknown"));
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
    g_error_free (error);
    g_free (uri);
  }
 
  g_object_unref (file);
}

static void
on_place_clicked (ClutterActor *shortcut, gpointer null)
{
  GVfs *vfs;
  GFile *file;
  const gchar *uri;
  ClutterTexture *temp;
  GError *error = NULL;

  launcher_shortcut_set_active (LAUNCHER_SHORTCUT (shortcut), FALSE);

  uri = clutter_actor_get_name (shortcut);

  if (!uri)
    return;

  //g_app_info_launch_default_for_uri (uri, NULL, &error);
  g_spawn_command_line_async (g_strdup_printf ("nautilus %s", uri), NULL);
  
  if (error && !g_strstr_len (uri, -1, "file"))
  {
    g_warning ("%s\n", error->message);
    g_error_free (error);

    vfs = g_vfs_get_default ();
    file = g_vfs_get_file_for_uri (vfs, uri);
    g_file_mount_enclosing_volume (file, 0, NULL, NULL, 
                                   (GAsyncReadyCallback)on_places_mount,
                                   shortcut);

    return;
  }
  else if (error)
  {
    GtkWidget *dialog;
    gint res;
    
    dialog = gtk_message_dialog_new_with_markup (NULL, 
                                     0,
                                     GTK_MESSAGE_ERROR,
                                     GTK_BUTTONS_CLOSE,
                                     _("<b>Unable to open %s:</b>\n%s"),
                                     uri, 
                                     error->message);
    res = gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
    g_error_free (error);
  }

  temp = launcher_shortcut_get_texture (LAUNCHER_SHORTCUT (shortcut));
  launcher_notify_popup (launcher_notify_get_default (),
                  launcher_shortcut_get_label (LAUNCHER_SHORTCUT (shortcut)),
                         temp, 
                         -1);
}

static ClutterActor * 
add_place (LauncherSidebar *sidebar, 
           const gchar *icon_name, 
           const gchar *name,
           const gchar *command)
{
  LauncherSidebarPrivate *priv = sidebar->priv;
  ClutterActor *shortcut;
  gchar *filename;

  /* Check to make sure the place exists */
  filename = g_filename_from_uri (command, NULL, NULL);
  if (g_strstr_len (command, -1, "file:") 
      && ( !filename || !g_file_test (filename, G_FILE_TEST_EXISTS)))
  {
    g_debug ("PLACES: Excluding %s, does not exist", filename);
    g_free (filename);
    return NULL;
  }
  g_free (filename);

  shortcut = launcher_shortcut_new (icon_name, name, FALSE);
  clutter_container_add_actor (CLUTTER_CONTAINER (priv->grid), shortcut);
  clutter_actor_set_name (shortcut, command);
  clutter_actor_show_all(shortcut);

  priv->places = g_list_append (priv->places, shortcut);
  g_signal_connect (shortcut, "clicked", G_CALLBACK (on_place_clicked), NULL);
  
  return shortcut;
}

static gchar *
get_icon_name_for_place (const gchar *uri)
{
  GFile *file;
  GFileInfo *info;
  GIcon *icon = NULL;
  const gchar * const *names;
  GtkIconTheme *icon_theme;
  int i;

  file = g_file_new_for_uri (uri);
  info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_ICON, 
                            0, NULL, NULL);
  if (info)
    icon = (GIcon*)g_file_info_get_attribute_object (info, 
                                             G_FILE_ATTRIBUTE_STANDARD_ICON);

  if (!G_IS_THEMED_ICON (icon))
  {
    g_object_unref (file);
    if (info)
      g_object_unref (info);

    return g_strdup ("folder");
  }

  names = g_themed_icon_get_names (G_THEMED_ICON (icon));
  icon_theme = gtk_icon_theme_get_default ();

  for (i = 0; names[i] != NULL; i++)
  {
    if (gtk_icon_theme_has_icon (icon_theme, names[i]))
    {
      gchar *ret = g_strdup (names[i]);
      
      g_object_unref (file);
      g_object_unref (info);
      
      return ret;
    }
  }

  g_object_unref (file);
  g_object_unref (info);

  return NULL;
}

static void
load_places (LauncherSidebar *bar)
{
  LauncherSidebarPrivate *priv = bar->priv;
  gchar *path, *contents;

  path = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL);

  /* Remove the old places */
  g_list_foreach (priv->places, (GFunc)clutter_actor_destroy, NULL);
  g_list_free (priv->places);
  priv->places = NULL;

  if (!g_file_test (path, G_FILE_TEST_EXISTS))
  {
    g_debug ("PLACES: No gtk-bookmarks file. Cannot load places");
    g_free (path);
    return;
  }
  
  if (!g_file_get_contents (path, &contents, NULL, NULL))
  {
    g_debug ("PLACES: Unable to read gtk-bookmarks file");
    g_free (path);
    return;
  }

  if (contents)
  {
    gchar **bookmarks;
    gint i;

    bookmarks = g_strsplit (contents, "\n", -1);

    for (i = 0; bookmarks[i]; i++)
    {
      gchar *icon_name;
      gchar *label;

      label = strchr (bookmarks[i], ' ');
      if (label)
      {
        *label = '\0';
        label++;
      }
      else
      {
        label = strrchr (bookmarks[i], '/');
        if (label)
          label++;
      }
      if ((!label || !label[0]) 
           && !g_ascii_strcasecmp (bookmarks[i], "network:///"))
      {
        label = _("Network");
      }
      
      if (label)
      {
        icon_name = get_icon_name_for_place (bookmarks[i]);
        add_place (bar, icon_name, label, bookmarks[i]);
        g_free (icon_name);
      }
    }
    g_strfreev (bookmarks);
    g_free (contents);
  }

  ensure_layout (bar);
}

static void
on_places_changed (GFileMonitor      *monitor,
                   GFile             *file,
                   GFile             *old_file,
                   GFileMonitorEvent  event,
                   LauncherSidebar   *bar)
{
  g_return_if_fail (LAUNCHER_IS_SIDEBAR (bar));

  load_places (bar);
}

/*
 * VOLUMES
 */
static gboolean
volume_is_excluded (LauncherSidebar *bar, GVolume *volume)
{
  LauncherSidebarPrivate *priv = bar->priv;
  GSList *v;
  gchar *uid;

  uid = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_HAL_UDI);

  if (!uid)
    return FALSE;

  for (v = priv->excluded_volumes; v; v = v->next)
  {
    if (v->data && strstr (uid, v->data))
    {
      g_free (uid);
      return TRUE;
    }
  }

  g_free (uid);
  return FALSE;
}


static gboolean
can_eject (GVolume *volume)
{

  if (!G_IS_VOLUME (volume))
  {
    return FALSE;
  }
  else if (g_volume_can_eject (volume))
  {
    return TRUE;
  }
  else if (g_volume_get_mount (volume))
  {
    return TRUE;
  }
  return FALSE;
}

static void
on_volume_mounted (GVolume          *volume,
                   GAsyncResult     *res,
                   LauncherShortcut *shortcut)
{
  gboolean success;
  GMount *mount;
  GFile *root;
  gchar *path, *command;
  
  g_return_if_fail (G_IS_VOLUME (volume));

  success = g_volume_mount_finish (volume, res, NULL);
  
  if (!success)
  {
    g_debug ("PLACES: Unsuccessful mount operation");
    return;
  }

  if (shortcut)
    launcher_shortcut_set_active (LAUNCHER_SHORTCUT (shortcut), FALSE);

  mount = g_volume_get_mount (volume);
  if (!G_IS_MOUNT (mount))
  {
    g_debug ("PLACES: Mount attempt failed");
    return;
  }
  g_object_set_data (G_OBJECT (shortcut), "automount", GINT_TO_POINTER (0));
  root = g_mount_get_root (mount);
  path = g_file_get_uri (root);
  command = g_strdup_printf (NAUTILUS, path);

  gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
                                    command, NULL);
  g_debug ("PLACES: Volume mounted: %s", command);
  g_free (command);
  g_free (path);
}

static void
on_volume_clicked (ClutterActor *shortcut, GVolume *volume)
{
  GMount *mount;
  GFile *root;
  gchar *path, *command;
  ClutterTexture *temp;
  
  g_return_if_fail (G_IS_VOLUME (volume));

  mount = g_volume_get_mount (volume);
  if (!G_IS_MOUNT (mount))
  {
    if (!g_volume_can_mount (volume))
      return;

    g_volume_mount (volume, 0, NULL, NULL, 
                    (GAsyncReadyCallback)on_volume_mounted, shortcut);

    return;
  }

  root = g_mount_get_root (mount);
  path = g_file_get_uri (root);
  command = g_strdup_printf (NAUTILUS, path);

  gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
                                    command, NULL);

  /* Notify the user */
  temp = launcher_shortcut_get_texture (LAUNCHER_SHORTCUT (shortcut));
  launcher_notify_popup (launcher_notify_get_default (),
                  launcher_shortcut_get_label (LAUNCHER_SHORTCUT (shortcut)),
                         CLUTTER_TEXTURE (temp), 
                         -1);

  g_free (command);
  g_free (path);
}

void  
launcher_sidebar_launch_volume (LauncherSidebar *sidebar,
                                GVolume         *volume)
{
  GMount *mount;
  GFile *root;
  gchar *path, *command;
    
  g_return_if_fail (G_IS_VOLUME (volume));

  mount = g_volume_get_mount (volume);
  if (!G_IS_MOUNT (mount))
  {
    if (!g_volume_can_mount (volume))
      return;

    g_volume_mount (volume, 0, NULL, NULL, 
                    (GAsyncReadyCallback)on_volume_mounted, NULL);

    return;
  }

  root = g_mount_get_root (mount);
  path = g_file_get_uri (root);
  command = g_strdup_printf (NAUTILUS, path);

  gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
                                    command, NULL);

  g_free (command);
  g_free (path);
}

static void
on_volume_ejected (GVolume          *volume,
                   GAsyncResult     *res,
                   LauncherShortcut *shortcut)
{
  g_volume_eject_finish (volume, res, NULL);
  clutter_actor_set_opacity (CLUTTER_ACTOR (shortcut), 175);
}

static void
on_mount_unmounted (GMount           *mount,
                    GAsyncResult     *res,
                    LauncherShortcut *shortcut)
{
  g_mount_unmount_finish (mount, res, NULL);
  clutter_actor_set_opacity (CLUTTER_ACTOR (shortcut), 175);

}

static void
on_eject_clicked (ClutterActor *shortcut, GVolume *volume)
{
  g_return_if_fail (G_IS_VOLUME (volume));

  if (g_volume_can_eject (volume))
  {
    g_volume_eject (volume, 0, NULL,
                    (GAsyncReadyCallback)on_volume_ejected, shortcut);
  }
  else
  {
    GMount *mount = g_volume_get_mount (volume);

    if (!mount)
      return;
    g_mount_unmount (mount, 0, NULL,
                     (GAsyncReadyCallback)on_mount_unmounted, shortcut);
  }
}

static void
on_link_clicked (ClutterActor *shortcut, gpointer null)
{
  const gchar *command;
  ClutterTexture *temp;

  launcher_shortcut_set_active (LAUNCHER_SHORTCUT (shortcut), FALSE);

  command = clutter_actor_get_name (shortcut);

  if (command)
    gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
                                      command, NULL);

  /* Notify the user */
  temp = launcher_shortcut_get_texture (LAUNCHER_SHORTCUT (shortcut));
  launcher_notify_popup (launcher_notify_get_default (),
                  launcher_shortcut_get_label (LAUNCHER_SHORTCUT (shortcut)),
                         temp, 
                         -1);
}

/* Taken from nautilus::panel-util.c, with some modifications */
static gchar *
get_icon_name (GVolume *volume)
{
  GIcon *icon;
  const gchar * const *names;
  GtkIconTheme *icon_theme;
  int i;

  icon = g_volume_get_icon (volume);

  if (!G_IS_THEMED_ICON (icon))
    return g_strdup ("gnome-fs-folder");

  names = g_themed_icon_get_names (G_THEMED_ICON (icon));
  icon_theme = gtk_icon_theme_get_default ();

  for (i = 0; names[i] != NULL; i++)
  {
    if (gtk_icon_theme_has_icon (icon_theme, names[i]))
          return g_strdup (names[i]);
  }

  return NULL;
}

static void
on_volume_removed (GVolume *volume, LauncherShortcut *shortcut)
{
  LauncherSidebar *bar = g_object_get_data (G_OBJECT (shortcut), "bar");
  
  g_return_if_fail (LAUNCHER_IS_SIDEBAR (bar));

  g_debug ("PLACES: Volume removed: %s", g_volume_get_name (volume));

  bar->priv->shortcuts = g_list_remove (bar->priv->shortcuts, shortcut);
  clutter_actor_destroy (CLUTTER_ACTOR (shortcut));

  ensure_layout (bar);
}

static void
on_volume_changed (GVolume *volume, LauncherShortcut *shortcut)
{
  GMount *mount;

  g_return_if_fail (LAUNCHER_IS_SHORTCUT (shortcut));
  
  launcher_shortcut_set_can_eject (shortcut, can_eject (volume));

  mount = g_volume_get_mount (volume);
  if (G_IS_MOUNT (mount))
  {
    clutter_actor_set_opacity (CLUTTER_ACTOR (shortcut), 255);
    if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (shortcut), "automount")))
    {
      g_object_set_data (G_OBJECT (shortcut), "automount", GINT_TO_POINTER (0));
      launcher_sidebar_launch_volume (NULL, volume);
    }
  }
  else
    clutter_actor_set_opacity (CLUTTER_ACTOR (shortcut), 175);
}

static gboolean
is_running (gchar *process_name)
{
    gchar    *argv[] = { "pgrep", "-lfu", NULL, process_name, NULL };
    gchar    *output = NULL;
    gchar    *error = NULL;
    gint      exit_status = 0;
    
    argv[2] = (gchar*)g_get_user_name ();

    g_spawn_sync (NULL, argv, NULL, 
                  G_SPAWN_SEARCH_PATH, NULL, NULL, 
                  &output, &error, &exit_status, NULL);
    if (output)
    {
      if (g_strstr_len (output, strlen (output), process_name))
        return TRUE;
    }

    return FALSE;

}

static void
on_volume_added (GVolumeMonitor *monitor,
                 GVolume *volume, 
                 LauncherSidebar *sidebar)
{ 
  LauncherSidebarPrivate *priv;
  GMount *mount;
  ClutterActor *shortcut;
  gchar *icon_name = NULL;

  g_return_if_fail (LAUNCHER_IS_SIDEBAR (sidebar));
  priv = sidebar->priv;

  g_debug ("PLACES: Volume Added: %s %s", 
           g_volume_get_name (volume),
           g_volume_get_identifier (volume, 
                                    G_VOLUME_IDENTIFIER_KIND_HAL_UDI));

  icon_name = get_icon_name (volume);
  shortcut = launcher_shortcut_new (icon_name, 
                                    g_volume_get_name (volume),
                                    can_eject (volume));
  
  clutter_container_add_actor (CLUTTER_CONTAINER (priv->grid), shortcut);
  g_object_set_data (G_OBJECT (shortcut), "volume", volume);
  g_object_set_data (G_OBJECT (shortcut), "bar", sidebar);
  clutter_actor_show_all (shortcut);

  mount = g_volume_get_mount (volume);
  if (!G_IS_MOUNT (mount))
    clutter_actor_set_opacity (shortcut, 175);

  g_signal_connect (shortcut, "clicked", 
                    G_CALLBACK (on_volume_clicked), volume);
  g_signal_connect (shortcut, "eject", 
                    G_CALLBACK (on_eject_clicked), volume);
  
  g_signal_connect (volume, "changed", 
                    G_CALLBACK (on_volume_changed), shortcut);
  g_signal_connect (volume, "removed", 
                    G_CALLBACK (on_volume_removed), shortcut);
  
  priv->shortcuts = g_list_append (priv->shortcuts, shortcut);

  ensure_layout (sidebar);

  /* Try and automount IF nautilus and gnome-volume-manager
     are not already running */
  if (!G_IS_MOUNT (mount) && !priv->first_run && 
      !is_running ("gnome-volume-manager") && !is_running ("nautilus"))
  {
    g_object_set_data (G_OBJECT (shortcut), "automount", 
                       GINT_TO_POINTER (TRUE ? 1:0));

    g_debug ("PLACES: Automounting volume");
    on_volume_clicked (shortcut, volume);
  }
  else
    g_object_set_data (G_OBJECT (shortcut), "automount", 
                       GINT_TO_POINTER (FALSE ? 1:0));

  g_free (icon_name);
}
/*
 * /VOLUMES
 */

/*
 * Remove favourite stuff 
 */

void
launcher_sidebar_show_trash (LauncherSidebar *sidebar,
                             gboolean         show)
{
  /* null op */
}

/*
 * FOCUS STUFF
 */
static void
launcher_sidebar_set_focus (ClutterFocusGroup *group, gboolean has_focus)
{
  LauncherSidebarPrivate *priv = LAUNCHER_SIDEBAR (group)->priv;

  if (has_focus)
  {
    priv->focused = priv->shortcuts->data;
    launcher_shortcut_set_active (LAUNCHER_SHORTCUT (priv->focused), TRUE);
  }
  else
    launcher_shortcut_set_active (LAUNCHER_SHORTCUT (priv->focused), FALSE);
}

static gboolean
launcher_sidebar_direction_event (ClutterFocusGroup     *group,
                                 ClutterFocusDirection  dir)
{
  LauncherSidebarPrivate *priv = LAUNCHER_SIDEBAR (group)->priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  LauncherShortcut *new;
  GList *children, *c;
  gint bar_height = cfg->shortcut_height;
  gint current;
  gint next;

  switch (dir)
  {
    case CLUTTER_DIRECTION_RIGHT:
    case CLUTTER_DIRECTION_LEFT:
      return TRUE;
    default:
      ;
  }
  /* Move to the next or previous category, 
   *  if category is first or last, return FALSE
   *  page up and page down takes you to top and bottom
   */
  children = g_list_copy (priv->shortcuts);
  children = g_list_concat (children, g_list_copy (priv->places));
  children = g_list_concat (children, g_list_copy (priv->links));
  current = next = g_list_index (children, priv->focused);

  switch (dir)
  {
    case CLUTTER_DIRECTION_UP:
      next -= 1;
      break;
    case CLUTTER_DIRECTION_DOWN:
      next += 1;
      break;
    case CLUTTER_DIRECTION_PAGE_UP:
      next = 0;
      break;
    case CLUTTER_DIRECTION_PAGE_DOWN:
      next = g_list_length (children) -1;
      break;
    default:
      break;
  }
  if (next < 0) next = 0;
  next = CLAMP (next, 0, g_list_length (children)-1);
  new = g_list_nth_data (children, next);
  priv->focused = CLUTTER_ACTOR (new);
  launcher_shortcut_set_active (new, TRUE);

  for (c = children; c; c= c->next)
  {
    LauncherShortcut *shortcut = c->data;

    if (!LAUNCHER_IS_SHORTCUT (shortcut))
      continue;

    if (shortcut != new)
      launcher_shortcut_set_active (shortcut, FALSE);
  }

  if (TIDY_IS_ADJUSTMENT (priv->adjust))
  {
    gint y, pos, page;

    y = tidy_adjustment_get_value (priv->adjust);
    pos = bar_height * (next);
    page = (N_CATS-2) * bar_height;

    if (pos >= (y + page))
    {
      tidy_adjustment_set_value (priv->adjust, pos);
    }
    else if (pos < y)
    {
      tidy_adjustment_set_value (priv->adjust, pos);
    }
  }

  g_list_free (children);

  return TRUE;
}

static gboolean
launcher_sidebar_action_event (ClutterFocusGroup *group)
{
  LauncherSidebarPrivate *priv = LAUNCHER_SIDEBAR (group)->priv;

  launcher_shortcut_clicked (LAUNCHER_SHORTCUT (priv->focused));

  return TRUE;
}

static gboolean
launcher_sidebar_key_event (ClutterFocusGroup *group, const gchar *string)
{
  return TRUE;
}                        

static void
clutter_focus_group_iface_init (ClutterFocusGroupIface *iface)
{
  iface->set_focus       = launcher_sidebar_set_focus;
  iface->direction_event = launcher_sidebar_direction_event;
  iface->key_event       = launcher_sidebar_key_event;
  iface->action_event    = launcher_sidebar_action_event;
}

/*
 * /FOCUS STUFF
 */
 

/* GObject stuff */
static void
launcher_sidebar_finalize (GObject *object)
{
  LauncherSidebarPrivate *priv;

  g_return_if_fail (LAUNCHER_IS_SIDEBAR (object));
  priv = LAUNCHER_SIDEBAR (object)->priv;

  g_object_unref (priv->monitor);
  g_object_unref (priv->bookfile_mon);
  g_object_unref (priv->bookfile);

  g_list_free (priv->shortcuts);
  g_list_free (priv->links);
  g_list_free (priv->places);

  g_slist_free (priv->excluded_volumes);

  G_OBJECT_CLASS (launcher_sidebar_parent_class)->finalize (object);
}

static void
launcher_sidebar_class_init (LauncherSidebarClass *klass)
{
  GObjectClass        *obj_class = G_OBJECT_CLASS (klass);
  
  obj_class->finalize = launcher_sidebar_finalize;

	_sidebar_signals[SHORTCUTS_CHANGED] =
		g_signal_new ("shortcuts-changed",
			      G_OBJECT_CLASS_TYPE (obj_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (LauncherSidebarClass, shortcuts_changed),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID, 
			      G_TYPE_NONE, 0);

  g_type_class_add_private (obj_class, sizeof (LauncherSidebarPrivate));
}

static ClutterActor * 
add_shortcut (LauncherSidebar *sidebar, 
              const gchar *icon_name, 
              const gchar *name,
              const gchar *command)
{
  LauncherSidebarPrivate *priv = sidebar->priv;
  ClutterActor *shortcut;
   
  shortcut = launcher_shortcut_new (icon_name, name, FALSE);
  clutter_container_add_actor (CLUTTER_CONTAINER (priv->grid), shortcut);
  clutter_actor_set_name (shortcut, command);
  clutter_actor_show_all (shortcut);
  priv->shortcuts = g_list_append (priv->shortcuts, shortcut);
  g_signal_connect (shortcut, "clicked", G_CALLBACK (on_link_clicked), NULL);
  
  return shortcut;
}

static void 
on_quit_clicked (ClutterActor *shortcut, gpointer null)
{
  GtkWidget *quit;

  quit = launcher_quit_new ();

  gtk_dialog_run (GTK_DIALOG (quit));

  gtk_widget_destroy (quit);
}

static void
load_volumes (LauncherSidebar *sidebar)
{
  LauncherSidebarPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  ClutterActor *shortcut;
  GList *volumes, *vol;
  gint bar_height = cfg->shortcut_height;
  gchar *home, *desktop,*network;

  g_return_if_fail (LAUNCHER_IS_SIDEBAR (sidebar));
  priv = sidebar->priv;

  home = g_strdup_printf (NAUTILUS, g_get_home_dir ());
  shortcut = add_shortcut (sidebar, "gnome-fs-home", _("Home"), home);
  g_free (home);
  desktop = g_strdup_printf (NAUTILUS, launcher_util_get_desktop_dir ());
  shortcut = add_shortcut (sidebar, "gnome-fs-desktop", _("Desktop"), desktop);
  g_free (desktop);
  network = g_strdup_printf (NAUTILUS, "network:///");
  shortcut = add_shortcut (sidebar, "gnome-fs-network", _("Network"), network);
  g_free (network);

  priv->first_run = TRUE;
  volumes = g_volume_monitor_get_volumes (priv->monitor);
  for (vol = volumes; vol; vol = vol->next)
  {
    GVolume *volume;
     
    volume = vol->data;

    if (volume_is_excluded (sidebar, volume))
    {
      g_object_unref (volume);
      continue;
    }
    on_volume_added (priv->monitor, volume, sidebar);
    
    g_object_unref (volume);
  }
  g_list_free (volumes);
  priv->first_run = FALSE;

  ensure_layout (sidebar);

  shortcut = launcher_shortcut_new ("gnome-logout",
                                    _("Quit..."),
                                    FALSE);
  clutter_container_add_actor (CLUTTER_CONTAINER (sidebar), shortcut);
  clutter_actor_set_position (shortcut, 0, 
                              cfg->win_height - (bar_height)-(PANEL_HEIGHT*2));
  clutter_actor_set_name (shortcut, "gnome-session-save --gui --kill");
  clutter_actor_show_all (shortcut);
  priv->links = g_list_append (priv->links, shortcut);
  g_signal_connect (shortcut, "clicked", G_CALLBACK (on_quit_clicked), NULL);
}

static void
launcher_sidebar_init (LauncherSidebar *sidebar)
{
  LauncherSidebarPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  GConfClient *client = gconf_client_get_default ();
  gchar *path;
  
  priv = sidebar->priv = LAUNCHER_SIDEBAR_GET_PRIVATE (sidebar);

  priv->monitor = g_volume_monitor_get ();
  priv->shortcuts = NULL;

  priv->clip = clutter_group_new ();
  clutter_container_add_actor (CLUTTER_CONTAINER (sidebar), priv->clip);
  clutter_actor_set_position (priv->clip, 0, 0);
  clutter_actor_set_clip (priv->clip, 0, 2, cfg->bar_width, 
                          cfg->shortcut_height * (N_CATS-2) +2);
  clutter_actor_show (priv->clip);

  priv->grid = tidy_viewport_new ();
  clutter_container_add_actor (CLUTTER_CONTAINER (priv->clip), priv->grid);
  clutter_actor_set_position (priv->grid, 0, 0);
  clutter_actor_show (priv->grid);

  priv->timeline = clutter_timeline_new_for_duration (SLOW_TIME);
  priv->temp = clutter_effect_template_new (priv->timeline,
                                            clutter_sine_inc_func);

  priv->excluded_volumes = gconf_client_get_list (client, 
                                  "/apps/netbook-launcher/volume_exclude_list",
                                  GCONF_VALUE_STRING,
                                  NULL);

  load_volumes (sidebar);
  load_places (sidebar);

  /* Places monitor */
  path = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL);
  priv->bookfile = g_file_new_for_path (path);
  priv->bookfile_mon = g_file_monitor_file (priv->bookfile, 0, NULL, NULL);
  g_signal_connect (priv->bookfile_mon, "changed",
                    G_CALLBACK (on_places_changed), sidebar);
  g_free (path);
  
  g_signal_connect (priv->monitor, "volume-added", 
                    G_CALLBACK (on_volume_added), sidebar);


  clutter_actor_set_reactive (CLUTTER_ACTOR (sidebar), TRUE);
  g_signal_connect (sidebar, "scroll-event",
                    G_CALLBACK (launcher_sidebar_scroll_event), sidebar);
  g_signal_connect (priv->grid, "scroll-event",
                    G_CALLBACK (launcher_sidebar_scroll_event), sidebar);

  g_object_unref (client);
}

ClutterActor *
launcher_sidebar_get_default (void)

{
  static ClutterActor *sidebar = NULL;

  if (!sidebar)
    sidebar = g_object_new (LAUNCHER_TYPE_SIDEBAR, 
                       NULL);

  return sidebar;
}
