#include <gtk/gtk.h>

static GdkPaintable *
get_image_paintable (GtkImage *image,
                    int      *out_size)
{
  GtkIconTheme *icon_theme;
  const char *icon_name;
  int width = 48;
  GdkPaintable *paintable;
  GtkIconInfo *icon_info;

  switch (gtk_image_get_storage_type (image))
    {
    case GTK_IMAGE_PAINTABLE:
      paintable = gtk_image_get_paintable (image);
      *out_size = gdk_paintable_get_intrinsic_width (paintable);
      return g_object_ref (paintable);
    case GTK_IMAGE_ICON_NAME:
      icon_name = gtk_image_get_icon_name (image);
      icon_theme = gtk_icon_theme_get_for_display (gtk_widget_get_display (GTK_WIDGET (image)));
      *out_size = width;
      icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, width, GTK_ICON_LOOKUP_GENERIC_FALLBACK);
      paintable = GDK_PAINTABLE (gtk_icon_info_load_texture (icon_info));
      g_object_unref (icon_info);
      return paintable;
    default:
      g_warning ("Image storage type %d not handled",
                 gtk_image_get_storage_type (image));
      return NULL;
    }
}

enum {
  TOP_LEFT,
  CENTER,
  BOTTOM_RIGHT
};

static void
image_drag_begin (GtkWidget      *widget,
                  GdkDrag        *drag,
                  gpointer        data)
{
  GdkPaintable *paintable;
  gint hotspot;
  gint hot_x, hot_y;
  gint size;

  paintable = get_image_paintable (GTK_IMAGE (data), &size);
  hotspot = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (data), "hotspot"));
  switch (hotspot)
    {
    default:
    case TOP_LEFT:
      hot_x = 0;
      hot_y = 0;
      break;
    case CENTER:
      hot_x = size / 2;
      hot_y = size / 2;
      break;
    case BOTTOM_RIGHT:
      hot_x = size;
      hot_y = size;
      break;
    }
  gtk_drag_set_icon_paintable (drag, paintable, hot_x, hot_y);
  g_object_unref (paintable);
}

static void
drag_widget_destroyed (GtkWidget *image, gpointer data)
{
  GtkWidget *widget = data;

  g_print ("drag widget destroyed\n");
  g_object_unref (image);
  g_object_set_data (G_OBJECT (widget), "drag widget", NULL);
}

static void
window_drag_end (GtkWidget *widget,
                 GdkDrag   *drag,
                 gpointer   data)
{
  GtkWidget *window = data;

  gtk_widget_destroy (window);
  g_signal_handlers_disconnect_by_func (widget, window_drag_end, data);
}

static void
window_drag_begin (GtkWidget      *widget,
                   GdkDrag        *drag,
                   gpointer        data)
{
  GdkPaintable *paintable;
  GtkWidget *image;
  int hotspot;
  int size;

  hotspot = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (data), "hotspot"));

  image = g_object_get_data (G_OBJECT (widget), "drag widget");
  if (image == NULL)
    {
      g_print ("creating new drag widget\n");
      paintable = get_image_paintable (GTK_IMAGE (data), &size);
      image = gtk_image_new_from_paintable (paintable);
      g_object_unref (paintable);
      g_object_ref (image);
      g_object_set_data (G_OBJECT (widget), "drag widget", image);
      g_signal_connect (image, "destroy", G_CALLBACK (drag_widget_destroyed), widget);
    }
  else
    g_print ("reusing drag widget\n");

  gtk_drag_set_icon_widget (drag, image, 0, 0);

  if (hotspot == CENTER)
    g_signal_connect (widget, "drag-end", G_CALLBACK (window_drag_end), image);
}

static void
update_source_target_list (GtkWidget *image)
{
  GdkContentFormats *target_list;

  target_list = gdk_content_formats_new (NULL, 0);

  target_list = gtk_content_formats_add_image_targets (target_list, FALSE);
  if (gtk_image_get_storage_type (GTK_IMAGE (image)) == GTK_IMAGE_ICON_NAME)
    target_list = gtk_content_formats_add_text_targets (target_list);

  gtk_drag_source_set_target_list (image, target_list);

  gdk_content_formats_unref (target_list);
}

static void
update_dest_target_list (GtkWidget *image)
{
  GdkContentFormats *target_list;

  target_list = gdk_content_formats_new (NULL, 0);

  target_list = gtk_content_formats_add_image_targets (target_list, FALSE);
  target_list = gtk_content_formats_add_text_targets (target_list);

  gtk_drag_dest_set_target_list (image, target_list);

  gdk_content_formats_unref (target_list);
}

void
image_drag_data_get (GtkWidget        *widget,
                     GdkDrag          *drag,
                     GtkSelectionData *selection_data,
                     gpointer          data)
{
  GdkPaintable *paintable;
  const gchar *name;
  int size;

  if (gtk_selection_data_targets_include_image (selection_data, TRUE))
    {
      paintable = get_image_paintable (GTK_IMAGE (data), &size);
      if (GDK_IS_TEXTURE (paintable))
        gtk_selection_data_set_texture (selection_data, GDK_TEXTURE (paintable));
      if (paintable)
        g_object_unref (paintable);
    }
  else if (gtk_selection_data_targets_include_text (selection_data))
    {
      if (gtk_image_get_storage_type (GTK_IMAGE (data)) == GTK_IMAGE_ICON_NAME)
        name = gtk_image_get_icon_name (GTK_IMAGE (data));
      else
        name = "Boo!";
      gtk_selection_data_set_text (selection_data, name, -1);
    }
  else
    {
      g_assert_not_reached ();
    }
}

static void
image_drag_data_received (GtkWidget        *widget,
                          GdkDrop          *drop,
                          GtkSelectionData *selection_data,
                          gpointer          data)
{
  gchar *text;

  if (gtk_selection_data_get_length (selection_data) == 0)
    return;

  if (gtk_selection_data_targets_include_image (selection_data, FALSE))
    {
      GdkTexture *texture;

      texture = gtk_selection_data_get_texture (selection_data);
      gtk_image_set_from_paintable (GTK_IMAGE (data), GDK_PAINTABLE (texture));

      g_object_unref (texture);
    }
  else if (gtk_selection_data_targets_include_text (selection_data))
    {
      text = (gchar *)gtk_selection_data_get_text (selection_data);
      gtk_image_set_from_icon_name (GTK_IMAGE (data), text);
      g_free (text);
    }
  else
    {
      g_assert_not_reached ();
    }
}


GtkWidget *
make_image (const gchar *icon_name, int hotspot)
{
  GtkWidget *image;

  image = gtk_image_new_from_icon_name (icon_name);
  gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);

  gtk_drag_source_set (image, GDK_BUTTON1_MASK, NULL, GDK_ACTION_COPY);
  update_source_target_list (image);

  g_object_set_data  (G_OBJECT (image), "hotspot", GINT_TO_POINTER (hotspot));

  g_signal_connect (image, "drag-begin", G_CALLBACK (image_drag_begin), image);
  g_signal_connect (image, "drag-data-get", G_CALLBACK (image_drag_data_get), image);

  gtk_drag_dest_set (image, GTK_DEST_DEFAULT_ALL, NULL, GDK_ACTION_COPY);
  g_signal_connect (image, "drag-data-received", G_CALLBACK (image_drag_data_received), image);
  update_dest_target_list (image);

  return image;
}

GtkWidget *
make_image2 (const gchar *icon_name, int hotspot)
{
  GtkWidget *image;

  image = gtk_image_new_from_icon_name (icon_name);
  gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);

  gtk_drag_source_set (image, GDK_BUTTON1_MASK, NULL, GDK_ACTION_COPY);
  update_source_target_list (image);

  g_object_set_data  (G_OBJECT (image), "hotspot", GINT_TO_POINTER (hotspot));

  g_signal_connect (image, "drag-begin", G_CALLBACK (window_drag_begin), image);
  g_signal_connect (image, "drag-data-get", G_CALLBACK (image_drag_data_get), image);

  gtk_drag_dest_set (image, GTK_DEST_DEFAULT_ALL, NULL, GDK_ACTION_COPY);
  g_signal_connect (image, "drag-data-received", G_CALLBACK (image_drag_data_received), image);
  update_dest_target_list (image);

  return image;
}

static void
spinner_drag_begin (GtkWidget      *widget,
                    GdkDrag        *drag,
                    gpointer        data)
{
  GtkWidget *spinner;

  g_print ("GtkWidget::drag-begin\n");
  spinner = g_object_new (GTK_TYPE_SPINNER,
                          "visible", TRUE,
                          "active",  TRUE,
                          NULL);
  gtk_drag_set_icon_widget (drag, spinner, 0, 0);
  g_object_set_data (G_OBJECT (drag), "spinner", spinner);
}

static void
spinner_drag_end (GtkWidget      *widget,
                  GdkDrag        *drag,
                  gpointer        data)
{
  GtkWidget *spinner;

  g_print ("GtkWidget::drag-end\n");
  spinner = g_object_get_data (G_OBJECT (drag), "spinner");
  gtk_widget_destroy (spinner);
}

static gboolean
spinner_drag_failed (GtkWidget      *widget,
                     GdkDrag        *drag,
                     GtkDragResult   result,
                     gpointer        data)
{
  GTypeClass *class;
  GEnumValue *value;

  class = g_type_class_ref (GTK_TYPE_DRAG_RESULT);
  value = g_enum_get_value (G_ENUM_CLASS (class), result);
  g_print ("GtkWidget::drag-failed %s\n", value->value_nick);
  g_type_class_unref (class);

  return FALSE;
}

void
spinner_drag_data_get (GtkWidget        *widget,
                       GdkDrag          *drag,
                       GtkSelectionData *selection_data,
                       gpointer          data)
{
  g_print ("GtkWidget::drag-data-get\n");
  gtk_selection_data_set_text (selection_data, "ACTIVE", -1);
}

static GtkWidget *
make_spinner (void)
{
  GtkWidget *spinner;

  spinner = gtk_spinner_new ();
  gtk_spinner_start (GTK_SPINNER (spinner));

  gtk_drag_source_set (spinner, GDK_BUTTON1_MASK, NULL, GDK_ACTION_COPY);
  gtk_drag_source_add_text_targets (spinner);

  g_signal_connect (spinner, "drag-begin", G_CALLBACK (spinner_drag_begin), spinner);
  g_signal_connect (spinner, "drag-end", G_CALLBACK (spinner_drag_end), spinner);
  g_signal_connect (spinner, "drag-failed", G_CALLBACK (spinner_drag_failed), spinner);
  g_signal_connect (spinner, "drag-data-get", G_CALLBACK (spinner_drag_data_get), spinner);

  return spinner;
}

int
main (int argc, char *Argv[])
{
  GtkWidget *window;
  GtkWidget *grid;
  GtkWidget *entry;

  gtk_init ();

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Drag And Drop");
  gtk_window_set_resizable (GTK_WINDOW (window), FALSE);

  grid = gtk_grid_new ();
  g_object_set (grid,
                "margin", 20,
                "row-spacing", 20,
                "column-spacing", 20,
                NULL);
  gtk_container_add (GTK_CONTAINER (window), grid);
  gtk_grid_attach (GTK_GRID (grid), make_image ("dialog-warning", TOP_LEFT), 0, 0, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), make_image ("process-stop", BOTTOM_RIGHT), 1, 0, 1, 1);

  entry = gtk_entry_new ();
  gtk_grid_attach (GTK_GRID (grid), entry, 0, 1, 2, 1);

  gtk_grid_attach (GTK_GRID (grid), make_spinner (), 0, 2, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), make_image ("weather-clear", CENTER), 1, 2, 1, 1);

  gtk_grid_attach (GTK_GRID (grid), make_image2 ("dialog-question", TOP_LEFT), 0, 3, 1, 1);

  gtk_grid_attach (GTK_GRID (grid), make_image2 ("dialog-information", CENTER), 1, 3, 1, 1);

  gtk_widget_show (window);
  gtk_main ();

  return 0;
}
