/*                                           -*- mode: c; c-basic-offset: 2 -*-
 *
 * This is kiba, a useless but fun GNOME dock.
 */

#include <gtk/gtk.h>
#include <gtk/gtkprivate.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <gdk/gdkx.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <math.h>
#include <limits.h>
#include <gconf/gconf-client.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

#include "akamaru.h"

#define KIBA_GCONF_PATH "/apps/kiba"
#define PIXMAP_PATH "/usr/share/pixmaps"

#define ICON_SIZE 48

GQuark kiba_error_quark (void)
{
  static GQuark q = 0;

  if (q == 0)
    q = g_quark_from_static_string ("kiba-quark");

  return q;
}

#define KIBA_ERROR kiba_error_quark ()

typedef struct _KibaDock      KibaDock;
typedef struct _KibaDockClass KibaDockClass;
typedef struct _KibaLauncher  KibaLauncher;

typedef enum {
  KIBA_DOCK_POSITION_LEFT_EDGE_TOP,
  KIBA_DOCK_POSITION_LEFT_EDGE_CENTER,
  KIBA_DOCK_POSITION_LEFT_EDGE_BOTTOM,

  KIBA_DOCK_POSITION_RIGHT_EDGE_TOP,
  KIBA_DOCK_POSITION_RIGHT_EDGE_CENTER,
  KIBA_DOCK_POSITION_RIGHT_EDGE_BOTTOM,

  KIBA_DOCK_POSITION_TOP_EDGE_LEFT,
  KIBA_DOCK_POSITION_TOP_EDGE_CENTER,
  KIBA_DOCK_POSITION_TOP_EDGE_RIGHT,

  KIBA_DOCK_POSITION_BOTTOM_EDGE_LEFT,
  KIBA_DOCK_POSITION_BOTTOM_EDGE_CENTER,
  KIBA_DOCK_POSITION_BOTTOM_EDGE_RIGHT
} KibaDockPosition;

struct _KibaDock
{
  GtkWidget        widget;

  KibaDockPosition position;
  Model            model;
  Anchor          *anchor;
  Anchor          *mouse_anchor;

  GdkRectangle     geometry;
  int              spacing;
  int              num_launchers;
  GList           *launchers;
  int              drag_offset_x;
  int              drag_offset_y;
  int	           drag_distance;
  KibaLauncher    *dragging_launcher;
  int              dx, dy;
  gboolean         dragging_dock;

  GConfClient  *gconf_client;
};

struct _KibaDockClass
{
  GtkWidgetClass parent_class;
};

struct _KibaLauncher {
  GdkWindow *window;
  GdkPixbuf *pixbuf;
  KibaDock *dock;
  Object *object;
  char *name;
  char *exec;
  char *comment;
  char *icon;
  gboolean prelight;
};

#define KIBA_TYPE_DOCK             (kiba_dock_get_type ())
#define KIBA_DOCK(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), KIBA_TYPE_DOCK, KibaDock))
#define KIBA_DOCK_CLASS(obj)       (G_TYPE_CHECK_CLASS_CAST ((obj), KIBA_DOCK,  KibaDockClass))
#define KIBA_IS_DOCK(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), KIBA_TYPE_DOCK))
#define KIBA_IS_DOCK_CLASS(obj)    (G_TYPE_CHECK_CLASS_TYPE ((obj), KIBA_TYPE_DOCK))
#define KIBA_DOCK_GET_CLASS        (G_TYPE_INSTANCE_GET_CLASS ((obj), KIBA_TYPE_DOCK, KibaDockClass))

static void     kiba_dock_realize              (GtkWidget        *widget);
static void     kiba_dock_show                 (GtkWidget        *widget);
static void     kiba_dock_map                  (GtkWidget        *widget);
static gboolean kiba_dock_expose_event         (GtkWidget        *clock,
						GdkEventExpose   *event);
static gboolean kiba_dock_button_press_event   (GtkWidget        *widget,
						GdkEventButton   *event);
static gboolean kiba_dock_button_release_event (GtkWidget        *widget,
						GdkEventButton   *event);
static gboolean kiba_dock_motion_notify_event  (GtkWidget        *widget,
						GdkEventMotion   *event);
static gboolean kiba_dock_enter_notify_event   (GtkWidget        *widget,
						GdkEventCrossing *event);
static gboolean kiba_dock_leave_notify_event   (GtkWidget        *widget,
						GdkEventCrossing *event);

static void     kiba_dock_drag_data_received   (GtkWidget        *widget,
						GdkDragContext   *drag_context,
						gint              x,
						gint              y,
						GtkSelectionData *selection_data,
						guint             info,
						guint             time);

static KibaLauncher *kiba_launcher_new (KibaDock *dock,
					const char *gconf_path);
static void kiba_launcher_create_window (KibaLauncher *launcher);
static void kiba_dock_add_launcher (KibaDock *dock, KibaLauncher *launcher,
				    int x, int y);

static gint      timeout_callback (gpointer data);

G_DEFINE_TYPE (KibaDock, kiba_dock, GTK_TYPE_WIDGET)

typedef struct {
  const char     *name;
  GtkOrientation  orientation;
  int             halignment;
  int             valignment;
  GdkPoint        anchor;
} KibaDockLayoutData;

static const KibaDockLayoutData kiba_dock_layout_data[] = {
  { "left-edge-top",      GTK_ORIENTATION_VERTICAL,   0, 0, { 0, 1 } },
  { "left-edge-center",   GTK_ORIENTATION_VERTICAL,   0, 1, { 0, 2 } },
  { "left-edge-bottom",   GTK_ORIENTATION_VERTICAL,   0, 2, { 0, 3 } },
  { "right-edge-top",     GTK_ORIENTATION_VERTICAL,   2, 0, { 4, 1 } },
  { "right-edge-center",  GTK_ORIENTATION_VERTICAL,   2, 1, { 4, 2 } },
  { "right-edge-bottom",  GTK_ORIENTATION_VERTICAL,   2, 2, { 4, 3 } },
  { "top-edge-left",      GTK_ORIENTATION_HORIZONTAL, 0, 0, { 1, 0 } },
  { "top-edge-center",    GTK_ORIENTATION_HORIZONTAL, 1, 0, { 2, 0 } },
  { "top-edge-right",     GTK_ORIENTATION_HORIZONTAL, 2, 0, { 3, 0 } },
  { "bottom-edge-left",   GTK_ORIENTATION_HORIZONTAL, 0, 2, { 1, 4 } },
  { "bottom-edge-center", GTK_ORIENTATION_HORIZONTAL, 1, 2, { 2, 4 } },
  { "bottom-edge-right",  GTK_ORIENTATION_HORIZONTAL, 2, 2, { 3, 4 } }
};

static void
kiba_dock_class_init (KibaDockClass *class)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);

  widget_class->realize              = kiba_dock_realize;
  widget_class->show                 = kiba_dock_show;
  widget_class->map                  = kiba_dock_map;
  widget_class->expose_event         = kiba_dock_expose_event;
  widget_class->drag_data_received   = kiba_dock_drag_data_received;
  widget_class->button_press_event   = kiba_dock_button_press_event;
  widget_class->button_release_event = kiba_dock_button_release_event;
  widget_class->motion_notify_event  = kiba_dock_motion_notify_event;
  widget_class->enter_notify_event   = kiba_dock_enter_notify_event;
  widget_class->leave_notify_event   = kiba_dock_leave_notify_event;
}

static void
kiba_dock_init (KibaDock *dock)
{
  GdkScreen *screen;
  Object *object;
  static const GtkTargetEntry targets[] = {
    { "text/plain", 0, 0 }
  };

  GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (dock), GTK_NO_WINDOW);
  GTK_WIDGET_SET_FLAGS (GTK_WIDGET (dock), GTK_TOPLEVEL);

  GTK_PRIVATE_SET_FLAG (GTK_WIDGET (dock), GTK_ANCHORED);

  screen = gdk_screen_get_default ();
  gdk_screen_get_monitor_geometry(screen, 0, &dock->geometry);

  dock->position = KIBA_DOCK_POSITION_BOTTOM_EDGE_CENTER;
  dock->spacing = ICON_SIZE;

  model_init (&dock->model);
  dock->model.constrain_iterations = 1;

  model_add_enclosing_rectangle (&dock->model, ICON_SIZE / 2, ICON_SIZE / 2,
				 dock->geometry.width - ICON_SIZE / 2,
				 dock->geometry.height - ICON_SIZE / 2);

  object = model_add_object (&dock->model, 0, 0, 0, NULL);
  dock->anchor = model_add_anchor (&dock->model, object, 0, 0 );
  dock->mouse_anchor = model_add_anchor (&dock->model, NULL, 0, 0);

  gtk_drag_dest_set (GTK_WIDGET (dock),
		     GTK_DEST_DEFAULT_ALL,
		     targets, G_N_ELEMENTS (targets),
		     GDK_ACTION_MOVE | GDK_ACTION_COPY);
  gtk_drag_dest_add_uri_targets (GTK_WIDGET (dock));

  g_timeout_add (20, timeout_callback, dock);
}

GtkWidget*
kiba_dock_new (GConfClient *gconf_client)
{
  GtkWidget *dock = g_object_new (KIBA_TYPE_DOCK, NULL);

  KIBA_DOCK (dock)->gconf_client = gconf_client;

  return dock;
}

static KibaLauncher *
kiba_dock_get_launcher_for_window (KibaDock  *dock,
				   GdkWindow *window)
{
  GList *l;

  for (l = dock->launchers; l != NULL; l = l->next)
    {
      KibaLauncher *launcher = l->data;

      if (launcher->window == window)
	return launcher;
    }

  return NULL;
}

static void
kiba_dock_paint (KibaDock *dock)
{
  GtkWidget *widget = GTK_WIDGET (dock);
  cairo_pattern_t *gradient;
  cairo_t *cr;
  const int margin = 1, radius = 5;
  int x0, y0, x1, y1;
  const KibaDockLayoutData *d;

  d = &kiba_dock_layout_data[dock->position];
  x0 = (d->halignment == 0 ? -radius : margin);
  y0 = (d->valignment == 0 ? -radius : margin);;
  x1 = widget->allocation.width - (d->halignment == 2 ? -radius : margin);
  y1 = widget->allocation.height - (d->valignment == 2 ? -radius : margin);

  cr = gdk_cairo_create (widget->window);
  cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
  cairo_paint (cr);

  cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
  cairo_arc (cr, x0 + radius, y1 - radius, radius, M_PI / 2, M_PI);
  cairo_line_to (cr, x0, y0 + radius);
  cairo_arc (cr, x0 + radius, y0 + radius, radius, M_PI, 3 * M_PI / 2);
  cairo_line_to (cr, x1 - radius, y0);
  cairo_arc (cr, x1 - radius, y0 + radius, radius, 3 * M_PI / 2, 2 * M_PI);
  cairo_line_to (cr, x1, y1 - radius);
  cairo_arc (cr, x1 - radius, y1 - radius, radius, 0, M_PI / 2);
  cairo_close_path (cr);

  gradient = cairo_pattern_create_linear ((x0 + x1) / 2 - 1, y0,
					  (x0 + x1) / 2 + 1, y1);
  cairo_pattern_add_color_stop_rgba (gradient, 0, 1, 1, 1, 0.4);
  cairo_pattern_add_color_stop_rgba (gradient, 1, 1, 1, 1, 0.6);
  cairo_set_source (cr, gradient);
  cairo_fill_preserve (cr);

  cairo_set_source_rgba (cr, 1, 1, 1, 1);
  cairo_set_line_width (cr, 1);
  cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
  cairo_stroke (cr);

  cairo_destroy (cr);
}

static void
kiba_launcher_paint (KibaLauncher *launcher)
{
  cairo_t *cr;

  cr = gdk_cairo_create (launcher->window);
  gdk_cairo_set_source_pixbuf (cr, launcher->pixbuf, 0, 0);
  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
  cairo_paint (cr);

  if (launcher->prelight)
    {
      cairo_set_source_rgba (cr, 1, 1, 1, 0.3);
      cairo_set_operator (cr, CAIRO_OPERATOR_ATOP);
      cairo_paint (cr);
    }

  cairo_destroy (cr);
}

static gboolean
kiba_dock_expose_event (GtkWidget      *widget,
			GdkEventExpose *event)
{
  KibaDock *dock = KIBA_DOCK (widget);
  KibaLauncher *launcher;

  if (event->window == widget->window)
    kiba_dock_paint (dock);
  else
    {
      launcher = kiba_dock_get_launcher_for_window (dock, event->window);
      if (launcher != NULL)
	kiba_launcher_paint (launcher);
    }

  return TRUE;
}

static void
kiba_dock_realize (GtkWidget *widget)
{
  KibaDock *dock = KIBA_DOCK (widget);
  GdkScreen *screen;
  GdkWindowAttr attributes;
  gint attributes_mask;
  GList *l;

  g_return_if_fail (KIBA_IS_DOCK (widget));

  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);

  screen = gdk_screen_get_default ();

  attributes.window_type = GDK_WINDOW_TEMP;
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gdk_screen_get_rgba_visual (screen);
  attributes.colormap = gdk_screen_get_rgba_colormap (screen);
  attributes.event_mask = gtk_widget_get_events (widget) |
    GDK_EXPOSURE_MASK |
    GDK_BUTTON_PRESS_MASK |
    GDK_BUTTON_RELEASE_MASK |
    GDK_ENTER_NOTIFY_MASK |
    GDK_LEAVE_NOTIFY_MASK |
    GDK_POINTER_MOTION_MASK |
    GDK_POINTER_MOTION_HINT_MASK;

  attributes_mask = GDK_WA_X | GDK_WA_Y;
  if (attributes.visual != NULL && attributes.colormap != NULL)
    attributes_mask |= GDK_WA_VISUAL | GDK_WA_COLORMAP;

  widget->window = gdk_window_new (gdk_screen_get_root_window (screen),
				   &attributes, attributes_mask);
  gdk_window_set_user_data (widget->window, dock);

  for (l = dock->launchers; l != NULL; l = l->next)
    {
      KibaLauncher *launcher = l->data;
      kiba_launcher_create_window (launcher);
    }
}

static void
kiba_dock_layout (KibaDock *dock)
{
  const KibaDockLayoutData *d;
  GtkAllocation allocation;
  const int cap_size = 20;

  d = &kiba_dock_layout_data[dock->position];

  if (d->orientation == GTK_ORIENTATION_VERTICAL)
    {
      allocation.width = 2 * ICON_SIZE / 3;
      allocation.height = dock->num_launchers * dock->spacing +
	(d->valignment == 0 ? 2 * cap_size : cap_size);
    }
  else
    {
      allocation.width = dock->num_launchers * dock->spacing +
	(d->halignment == 0 ? 2 * cap_size : cap_size);
      allocation.height = 2 * ICON_SIZE / 3;
    }

  allocation.x =
    d->halignment * (dock->geometry.width - allocation.width) / 2;
  allocation.y =
    d->valignment * (dock->geometry.height - allocation.height) / 2;

  dock->anchor->x = allocation.x + allocation.width / 2;
  dock->anchor->y = allocation.y + allocation.height / 2;

  if (d->orientation == GTK_ORIENTATION_VERTICAL)
    {
      dock->model.gravity.x = (d->halignment - 1) * 50;
      dock->model.gravity.y = 0;
    }
  else
    {
      dock->model.gravity.x = 0;
      dock->model.gravity.y = (d->valignment - 1) * 50;
    }

  gtk_widget_size_allocate (GTK_WIDGET (dock), &allocation);
  gtk_widget_queue_draw (GTK_WIDGET (dock));
}

static void
kiba_dock_set_position (KibaDock *dock, KibaDockPosition position)
{
  dock->position = position;
  kiba_dock_layout (dock);
}

static void
kiba_dock_show (GtkWidget *widget)
{
  KibaDock *dock = KIBA_DOCK (widget);

  GTK_WIDGET_SET_FLAGS (widget, GTK_VISIBLE);

  if (!GTK_WIDGET_REALIZED (widget))
    {
      kiba_dock_layout (dock);
      gtk_widget_realize (widget);
    }

  gtk_widget_map (widget);
}

static void
kiba_dock_map (GtkWidget *widget)
{
  KibaDock *dock = KIBA_DOCK (widget);
  GList *l;

  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
  gdk_window_show (widget->window);

  for (l = dock->launchers; l != NULL; l = l->next)
    {
      KibaLauncher *launcher = l->data;
      gdk_window_show (launcher->window);
    }
}

static gint
timeout_callback (gpointer data)
{
  KibaDock *dock = data;
  GList *l;
  int i;

  for (l = dock->launchers; l != NULL; l = l->next)
    {
      KibaLauncher *launcher = l->data;
      gdk_window_move (launcher->window,
		       launcher->object->position.x + 0.5 - ICON_SIZE / 2,
		       launcher->object->position.y + 0.5 - ICON_SIZE / 2);
    }

  for (i = 0; i < 6; i++)
    model_step (&dock->model, 0.01);

  return TRUE;
}

typedef struct {
  KibaDock *dock;
  Object *object;
} AddSpacerClosure;

static void
add_spacer (Object *object, void *data)
{
  AddSpacerClosure *closure = data;

  /* Skip the anchor object. */
  if (object != closure->dock->anchor->object && object != closure->object)
    model_add_spacer (&closure->dock->model,
		      closure->object, object, closure->dock->spacing);
}

static void
kiba_dock_add_launcher (KibaDock *dock, KibaLauncher *launcher, int x, int y)
{
  AddSpacerClosure closure;

  closure.dock = dock;
  closure.object = model_add_object (&dock->model, x, y, 12, NULL);
  model_add_spring (&dock->model, dock->anchor->object, closure.object, 0);
  model_for_each_object (&dock->model, add_spacer, &closure);
  dock->launchers = g_list_prepend (dock->launchers, launcher);
  dock->num_launchers++;

  launcher->object = closure.object;
  launcher->dock = dock;

  if (GTK_WIDGET_REALIZED (GTK_WIDGET (dock)))
    kiba_launcher_create_window (launcher);

  if (GTK_WIDGET_MAPPED (GTK_WIDGET (dock)))
    gdk_window_show (launcher->window);

  kiba_dock_layout (dock);
}

static gboolean
kiba_dock_button_press_event (GtkWidget	     *widget,
			      GdkEventButton *event)
{
  KibaDock *dock = KIBA_DOCK (widget);
  KibaLauncher *launcher;

  dock->drag_offset_x = event->x;
  dock->drag_offset_y = event->y;
  dock->drag_distance = 0;

  if (widget->window == event->window)
    {
      dock->dragging_dock = TRUE;
      return TRUE;
    }

  launcher = kiba_dock_get_launcher_for_window (dock, event->window);
  if (launcher == NULL)
    return FALSE;

  dock->dragging_launcher    = launcher;
  dock->mouse_anchor->x      = launcher->object->position.x;
  dock->mouse_anchor->y      = launcher->object->position.y;
  dock->mouse_anchor->object = launcher->object;

  return TRUE;
}

static gboolean
kiba_launcher_exec (gpointer data)
{
  KibaLauncher *launcher = data;
  int argc;
  char **argv, **p, **q;
  GError *error = NULL;
  const KibaDockLayoutData *d;

  /* FIXME: Support drag and drop on launchers with %U and %F */
  if (!g_shell_parse_argv (launcher->exec, &argc, &argv, &error))
    {
      g_error ("failed to parse '%s': %s\n", launcher->exec, error->message);
      g_error_free (error);
      return FALSE;
    }

  for (p = argv, q = argv; *p != NULL; p++)
    {
      if (*p[0] == '%' || strlen (*p) == 2)
	switch (*p[1])
	  {
	  case 'U':
	  case 'u':
	  case 'F':
	  case 'f':
	  default:
	    g_free (*p);
	    break;
	  }
      else
	*q++ = *p;
    }
  *q++ = NULL;

  /* Bounce the icon. */
  d = &kiba_dock_layout_data[launcher->dock->position];

  if (d->orientation == GTK_ORIENTATION_VERTICAL)
    launcher->object->previous_position.x +=
      (d->halignment - 1) * g_random_double_range (5, 10);
  else
    launcher->object->previous_position.y +=
      (d->valignment - 1) * g_random_double_range (5, 10);

  g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
		 NULL, NULL, NULL, &error);
  if (error != NULL)
    g_error ("failed to launch %s: %s\n", argv[0], error->message);
  g_strfreev (argv);

  return FALSE;
}

static void
kiba_launcher_queue_draw (KibaLauncher *launcher)
{
  GdkRectangle invalid_rect;

  invalid_rect.x = 0;
  invalid_rect.y = 0;
  invalid_rect.width = gdk_pixbuf_get_width (launcher->pixbuf);
  invalid_rect.height = gdk_pixbuf_get_height (launcher->pixbuf);

  gdk_window_invalidate_rect (launcher->window, &invalid_rect, TRUE);
}

static gboolean
kiba_dock_button_release_event (GtkWidget      *widget,
				GdkEventButton *event)
{
  KibaDock *dock = KIBA_DOCK (widget);

  if (dock->dragging_dock)
    {
      dock->dragging_dock = FALSE;
      return TRUE;
    }

  if (dock->dragging_launcher && dock->drag_distance <= 1)
    g_idle_add (kiba_launcher_exec, dock->dragging_launcher);
  else
    {
      dock->dragging_launcher->object->previous_position.x -= dock->dx / 2;
      dock->dragging_launcher->object->previous_position.y -= dock->dy / 2;
    }

  dock->dragging_launcher    = NULL;
  dock->mouse_anchor->object = NULL;

  return TRUE;
}

static int
kiba_dock_find_closest_position (KibaDock *dock, int x, int y)
{
  int i, min_d, d, position;
  int dx, dy;

  min_d = INT_MAX;
  for (i = 0; i < G_N_ELEMENTS (kiba_dock_layout_data); i++)
    {
      dx = x - dock->geometry.width * kiba_dock_layout_data[i].anchor.x / 4;
      dy = y - dock->geometry.height * kiba_dock_layout_data[i].anchor.y / 4;
      d = dx * dx + dy * dy;
      if (d < min_d)
	{
	  min_d = d;
	  position = i;
	}
    }

  return position;
}

static gboolean
kiba_dock_motion_notify_event (GtkWidget      *widget,
			       GdkEventMotion *event)
{
  KibaDock *dock = KIBA_DOCK (widget);
  GdkModifierType state;
  int x, y, new_x, new_y, dx, dy, position;

  gdk_window_get_pointer (gdk_get_default_root_window(), &x, &y, &state);

  if (dock->dragging_dock)
    {
      position = kiba_dock_find_closest_position (dock, x, y);
      gconf_client_set_string  (dock->gconf_client,
				KIBA_GCONF_PATH "/options/position",
				kiba_dock_layout_data[position].name, NULL);
      return TRUE;
    }

  new_x = x - dock->drag_offset_x + ICON_SIZE / 2;
  new_y = y - dock->drag_offset_y + ICON_SIZE / 2;
  dx = new_x - dock->mouse_anchor->x;
  dy = new_y - dock->mouse_anchor->y;

  dock->dx = dx;
  dock->dy = dy;

  dock->mouse_anchor->x =
    CLAMP (new_x, ICON_SIZE / 2, dock->geometry.width - ICON_SIZE /2 );
  dock->mouse_anchor->y =
    CLAMP (new_y, ICON_SIZE / 2, dock->geometry.height - ICON_SIZE / 2);
  dock->drag_distance += sqrt (dx * dx + dy * dy);

  return TRUE;
}

static gboolean
kiba_dock_enter_notify_event (GtkWidget        *widget,
			      GdkEventCrossing *event)
{
  KibaDock *dock = KIBA_DOCK (widget);
  KibaLauncher *launcher;

  launcher = kiba_dock_get_launcher_for_window (dock, event->window);
  if (launcher == NULL)
    return FALSE;

  launcher->prelight = TRUE;
  kiba_launcher_queue_draw (launcher);

  return TRUE;
}

static gboolean
kiba_dock_leave_notify_event (GtkWidget        *widget,
			      GdkEventCrossing *event)
{
  KibaDock *dock = KIBA_DOCK (widget);
  KibaLauncher *launcher;

  launcher = kiba_dock_get_launcher_for_window (dock, event->window);
  if (launcher == NULL)
    return FALSE;

  launcher->prelight = FALSE;
  kiba_launcher_queue_draw (launcher);

  return TRUE;
}

static void
parse_desktop_file (KibaLauncher *launcher, const char *uri, GError **error)
{
  static const char file_uri_prefix[] = "file://";
  static const char desktop_file_suffix[] = ".desktop";
  static const char desktop_entry_group[] = "Desktop Entry";
  static const struct { const char *name; size_t offset; } fields[] = {
    { "Icon",    G_STRUCT_OFFSET (KibaLauncher, icon) },
    { "Exec",    G_STRUCT_OFFSET (KibaLauncher, exec) },
    { "Name",    G_STRUCT_OFFSET (KibaLauncher, name) },
    { "Comment", G_STRUCT_OFFSET (KibaLauncher, comment) },
  };

  GKeyFile *file;
  const char *filename ;
  char *s;
  int i;

  if (!g_str_has_suffix (uri, desktop_file_suffix)) {
    g_set_error (error, KIBA_ERROR, 0,
		 "%s is not a .desktop file", uri);
    return;
  }
  if (g_str_has_prefix (uri, file_uri_prefix))
    filename = uri + strlen (file_uri_prefix);
  else
    filename = uri;

  file = g_key_file_new ();
  if (!g_key_file_load_from_file (file, filename, 0, error))
    return;

  for (i = 0; i < G_N_ELEMENTS (fields); i++) {
    s = g_key_file_get_value (file, desktop_entry_group,
			      fields[i].name, error);
    if (s == NULL)
      return;

    G_STRUCT_MEMBER (char *, launcher, fields[i].offset) = s;
  }
}

static gboolean
load_icon (KibaLauncher *launcher, GError **error)
{
  char *path;
  char *suffix;

  launcher->pixbuf =
    gdk_pixbuf_new_from_file_at_size (launcher->icon, 48, -1, error);
  if (launcher->pixbuf != NULL)
    return TRUE;

  g_clear_error (error);
  path = g_strdup_printf (PIXMAP_PATH "/%s", launcher->icon);
  launcher->pixbuf = gdk_pixbuf_new_from_file_at_size (path, 48, -1, error);
  g_free (path);

  if (launcher->pixbuf != NULL)
    return TRUE;

  g_clear_error (error);
  launcher->pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
					       launcher->icon, 48, 0, error);
  if (launcher->pixbuf != NULL)
    return TRUE;

  suffix = strstr (launcher->icon, ".png");
  if (suffix == NULL)
    return FALSE;

  *suffix = '\0';
  g_clear_error (error);
  launcher->pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
					       launcher->icon, 48, 0, error);

  return launcher->pixbuf != NULL;
}

static void
kiba_launcher_create_window (KibaLauncher *launcher)
{
  GdkWindowAttr attributes;
  gint attributes_mask;
  GdkScreen *screen = gdk_screen_get_default ();

  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gdk_screen_get_rgba_visual (screen);
  attributes.colormap = gdk_screen_get_rgba_colormap (screen);
  attributes.window_type = GDK_WINDOW_TEMP;

  attributes.x = launcher->object->position.x;
  attributes.y = launcher->object->position.y;
  attributes.width = gdk_pixbuf_get_width (launcher->pixbuf);
  attributes.height = gdk_pixbuf_get_height (launcher->pixbuf);
  attributes.event_mask =
    GDK_EXPOSURE_MASK |
    GDK_BUTTON_PRESS_MASK |
    GDK_BUTTON_RELEASE_MASK |
    GDK_ENTER_NOTIFY_MASK |
    GDK_LEAVE_NOTIFY_MASK |
    GDK_POINTER_MOTION_MASK |
    GDK_POINTER_MOTION_HINT_MASK;

  attributes_mask = GDK_WA_X | GDK_WA_Y;
  if (attributes.visual != NULL && attributes.colormap != NULL)
    attributes_mask |= GDK_WA_VISUAL | GDK_WA_COLORMAP;

  launcher->window = gdk_window_new (gdk_screen_get_root_window (screen),
				     &attributes, attributes_mask);

  gdk_window_set_user_data (launcher->window, launcher->dock);
}

static KibaLauncher *
kiba_launcher_new (KibaDock *dock, const char *gconf_path)
{
  KibaLauncher *launcher;
  GError *error = NULL;
  char *desktop_file;
  char *key;

  launcher = g_new0 (KibaLauncher, 1);

  key = g_strdup_printf ("%s/file", gconf_path);
  desktop_file = gconf_client_get_string (dock->gconf_client, key, &error);
  g_free (key);
  if (error != NULL) {
    printf ("error getting desktop file path for launcher: %s\n",
	    error->message);
    g_error_free (error);
    g_free (launcher);
    return NULL;
  }

  parse_desktop_file (launcher, desktop_file, &error);
  free (desktop_file);
  if (error != NULL) {
    printf ("error parsing desktop file: %s\n", error->message);
    g_error_free (error);
    g_free (launcher);
    return NULL;
  }

  load_icon (launcher, &error);
  if (error != NULL) {
    printf ("error creating launcher window: %s\n", error->message);
    g_error_free (error);
    g_free (launcher);
    return NULL;
  }

  return launcher;
}

static void
kiba_dock_drag_data_received(GtkWidget        *widget,
			     GdkDragContext   *drag_context,
			     gint              x,
			     gint              y,
			     GtkSelectionData *selection_data,
			     guint             info,
			     guint             time)
{
  KibaDock *dock = KIBA_DOCK (widget);
  GError *error = NULL;
  char *key;

  gtk_drag_finish (drag_context, TRUE, FALSE, time);
  g_strstrip ((char *) selection_data->data);
  printf ("got desktop file: %s\n", selection_data->data);

  key = g_strdup_printf (KIBA_GCONF_PATH "/launchers/%x/file", time);
  gconf_client_set_string  (dock->gconf_client,
			    key, (char *) selection_data->data, &error);
  if (error != NULL) {
    printf ("failed to set gconf key %s: %s\n", key, error->message);
    g_error_free (error);
  }

  if (error == NULL) {
    gconf_engine_associate_schema (dock->gconf_client->engine,
				   key,
				   "/schemas" KIBA_GCONF_PATH "/launchers/file",
				   &error);
    if (error != NULL) {
      printf ("failed to set schema for %s: %s\n", key, error->message);
      g_error_free (error);
    }
  }

  g_free (key);
}

static void
set_position_from_string (KibaDock *dock, const char *string)
{
  int i;

  for (i = 0; i < G_N_ELEMENTS (kiba_dock_layout_data); i++)
    {
      if (strcmp (kiba_dock_layout_data[i].name, string) == 0)
	{
	  kiba_dock_set_position (dock, i);
	  return;
	}
    }

  printf ("invalid position\n");
}

static void
set_option (KibaDock *dock, GConfEntry *entry)
{
  const char *value;
  char *key;
  int i;
  struct { const char *name; size_t offset; } options[] = {
    { "elasticity", G_STRUCT_OFFSET (Model, elasticity) },
    { "friction", G_STRUCT_OFFSET (Model, friction) },
    { "k", G_STRUCT_OFFSET (Model, k) }
  };

  if (strcmp (entry->key, KIBA_GCONF_PATH "/options/position") == 0 &&
      entry->value != NULL &&
      entry->value->type == GCONF_VALUE_STRING)
    {
      value = gconf_value_get_string (entry->value);
      set_position_from_string (dock, value);
    }
  else for (i = 0; i < G_N_ELEMENTS (options); i++) {
    key = g_strdup_printf (KIBA_GCONF_PATH "/options/%s", options[i].name);
    if (strcmp (entry->key, key) == 0 &&
	entry->value != NULL &&
	entry->value->type == GCONF_VALUE_FLOAT)
      {
	G_STRUCT_MEMBER (double, &dock->model, options[i].offset) =
	  gconf_value_get_float (entry->value);
      }
    g_free (key);
  }
}

static void
options_changed_callback(GConfClient *client,
			 guint cnxn_id,
			 GConfEntry *entry,
			 gpointer user_data)
{
  KibaDock *dock;

  dock = KIBA_DOCK (user_data);

  set_option (dock, entry);
}

static void
launchers_changed_callback(GConfClient *client,
			   guint cnxn_id,
			   GConfEntry *entry,
			   gpointer user_data)
{
  KibaDock *dock;
  KibaLauncher *launcher;
  char *path, *last_slash;

  dock = KIBA_DOCK (user_data);

  if (!g_str_has_suffix (entry->key, "/file"))
    {
      printf ("ignoring gconf event for %s\n", entry->key);
      return;
    }

  path = g_strdup (entry->key);
  last_slash = strrchr (path, '/');
  *last_slash = '\0';

  launcher = kiba_launcher_new (dock, path);
  g_free (path);

  if (launcher == NULL)
    return;

  kiba_dock_add_launcher (dock, launcher,
			  g_random_int_range (0, dock->geometry.width),
			  g_random_int_range (0, dock->geometry.height));
}

static void
init_from_gconf (KibaDock *dock, GConfClient *gconf_client, GError **error)
{
  GSList *launchers, *options, *l;
  KibaLauncher *launcher;

  launchers = gconf_client_all_dirs (gconf_client,
				     KIBA_GCONF_PATH "/launchers",
				     error);
  if (*error != NULL)
    return;

  for (l = launchers; l != NULL; l = l->next)
    {
      char *path = l->data;
      launcher = kiba_launcher_new (dock, path);
      if (launcher == NULL)
	continue;

      kiba_dock_add_launcher (dock, launcher,
			      g_random_int_range (0, dock->geometry.width),
			      g_random_int_range (0, dock->geometry.height));
    }

  options = gconf_client_all_entries (gconf_client,
				      KIBA_GCONF_PATH "/options",
				      error);
  if (*error != NULL)
    return;

  for (l = options; l != NULL; l = l->next)
    {
      GConfEntry *entry = l->data;

      set_option (dock, entry);
    }
}

int main (int argc, char *argv[])
{
  GtkWidget *dock;
  GConfClient *gconf_client;
  GError *error = NULL;

  gtk_init (&argc, &argv);

  gconf_client = gconf_client_get_default ();

  dock = kiba_dock_new (gconf_client);

  gconf_client_add_dir (gconf_client, KIBA_GCONF_PATH,
			GCONF_CLIENT_PRELOAD_NONE, &error);

  gconf_client_notify_add(gconf_client, KIBA_GCONF_PATH "/options",
                          options_changed_callback,
                          dock,
                          NULL, &error);
  if (error != NULL)
    {
      g_error ("Failed to listen for "
	       KIBA_GCONF_PATH "/options notifications: %s",
	       error->message);
      exit (1);
    }

  gconf_client_notify_add(gconf_client, KIBA_GCONF_PATH "/launchers",
                          launchers_changed_callback,
                          dock,
                          NULL, &error);
  if (error != NULL)
    {
      g_error ("Failed to listen for "
	       KIBA_GCONF_PATH "/launchers notifications: %s",
	       error->message);
      exit (1);
    }

  init_from_gconf (KIBA_DOCK (dock), gconf_client, &error);
  if (error != NULL)
    {
      g_error ("Failed to read config from gconf: %s", error->message);
      exit (1);
    }

  gtk_widget_show (dock);

  gtk_main ();

  return 0;
}
