/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpcontainertreeview-dnd.c
 * Copyright (C) 2003 Michael Natterer <mitch@gimp.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.
 */

#include "config.h"

#include <gtk/gtk.h>

#include "widgets-types.h"

#include "core/gimpcontainer.h"
#include "core/gimpviewable.h"

#include "gimpcontainertreeview.h"
#include "gimpcontainertreeview-dnd.h"
#include "gimpcontainerview.h"
#include "gimpdnd.h"
#include "gimpviewrenderer.h"
#include "gimpselectiondata.h"


static gboolean
gimp_container_tree_view_drop_status (GimpContainerTreeView    *tree_view,
                                      GdkDragContext           *context,
                                      gint                      x,
                                      gint                      y,
                                      guint                     time,
                                      GtkTreePath             **return_path,
                                      GdkAtom                  *return_atom,
                                      GimpDndType              *return_src_type,
                                      GimpViewable            **return_src,
                                      GimpViewable            **return_dest,
                                      GtkTreeViewDropPosition  *return_pos)
{
  GimpViewable  *src_viewable = NULL;
  GtkTreePath   *path;
  GtkTargetList *target_list;
  GdkAtom        target_atom;
  GimpDndType    src_type;

  if (! gimp_container_view_get_reorderable (GIMP_CONTAINER_VIEW (tree_view)))
    goto drop_impossible;

  target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (tree_view->view));
  target_atom = gtk_drag_dest_find_target (GTK_WIDGET (tree_view->view),
                                           context, target_list);
  if (! gtk_target_list_find (target_list, target_atom, &src_type))
    goto drop_impossible;

  switch (src_type)
    {
    case GIMP_DND_TYPE_URI_LIST:
    case GIMP_DND_TYPE_TEXT_PLAIN:
    case GIMP_DND_TYPE_NETSCAPE_URL:
    case GIMP_DND_TYPE_COLOR:
    case GIMP_DND_TYPE_SVG:
    case GIMP_DND_TYPE_SVG_XML:
      break;

    default:
      {
        GtkWidget *src_widget;

        src_widget = gtk_drag_get_source_widget (context);

        if (! src_widget)
          goto drop_impossible;

        src_viewable = gimp_dnd_get_drag_data (src_widget);

        if (! GIMP_IS_VIEWABLE (src_viewable))
          goto drop_impossible;
      }
      break;
    }

  if (gtk_tree_view_get_path_at_pos (tree_view->view, x, y,
                                     &path, NULL, NULL, NULL))
    {
      GimpViewRenderer        *renderer;
      GimpViewable            *dest_viewable;
      GtkTreeIter              iter;
      GdkRectangle             cell_area;
      GtkTreeViewDropPosition  drop_pos;
      GdkDragAction            drag_action;

      gtk_tree_model_get_iter (tree_view->model, &iter, path);

      gtk_tree_model_get (tree_view->model, &iter,
                          tree_view->model_column_renderer, &renderer,
                          -1);

      dest_viewable = renderer->viewable;

      g_object_unref (renderer);

      gtk_tree_view_get_cell_area (tree_view->view, path, NULL, &cell_area);

      if (y >= (cell_area.y + cell_area.height / 2))
        {
          drop_pos = GTK_TREE_VIEW_DROP_AFTER;
        }
      else
        {
          drop_pos = GTK_TREE_VIEW_DROP_BEFORE;
        }

      if (GIMP_CONTAINER_TREE_VIEW_GET_CLASS (tree_view)->drop_possible (tree_view,
                                                                         src_type,
                                                                         src_viewable,
                                                                         dest_viewable,
                                                                         drop_pos,
                                                                         &drop_pos,
                                                                         &drag_action))
        {
          gdk_drag_status (context, drag_action, time);

          if (return_path)
            *return_path = path;
          else
            gtk_tree_path_free (path);

          if (return_atom)
            *return_atom = target_atom;

          if (return_src)
            *return_src = src_viewable;

          if (return_dest)
            *return_dest = dest_viewable;

          if (return_pos)
            *return_pos = drop_pos;

          return TRUE;
        }

      gtk_tree_path_free (path);
    }

 drop_impossible:

  gdk_drag_status (context, 0, time);

  return FALSE;
}

#define SCROLL_DISTANCE 30
#define SCROLL_STEP     10
#define SCROLL_INTERVAL  5
/* #define SCROLL_DEBUG 1 */

static gboolean
gimp_container_tree_view_scroll_timeout (gpointer data)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data);
  GtkAdjustment         *adj;
  gdouble                new_value;

  adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (tree_view->view));

#ifdef SCROLL_DEBUG
  g_print ("scroll_timeout: scrolling by %d\n", SCROLL_STEP);
#endif

  if (tree_view->scroll_dir == GDK_SCROLL_UP)
    new_value = adj->value - SCROLL_STEP;
  else
    new_value = adj->value + SCROLL_STEP;

  new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);

  gtk_adjustment_set_value (adj, new_value);

  if (tree_view->scroll_timeout_id)
    {
      g_source_remove (tree_view->scroll_timeout_id);

      tree_view->scroll_timeout_id =
        g_timeout_add (tree_view->scroll_timeout_interval,
                       gimp_container_tree_view_scroll_timeout,
                       tree_view);
    }

  return FALSE;
}

void
gimp_container_tree_view_drag_leave (GtkWidget             *widget,
                                     GdkDragContext        *context,
                                     guint                  time,
                                     GimpContainerTreeView *tree_view)
{
  if (tree_view->scroll_timeout_id)
    {
      g_source_remove (tree_view->scroll_timeout_id);
      tree_view->scroll_timeout_id = 0;
    }

  gtk_tree_view_unset_rows_drag_dest (tree_view->view);
}

gboolean
gimp_container_tree_view_drag_motion (GtkWidget             *widget,
                                      GdkDragContext        *context,
                                      gint                   x,
                                      gint                   y,
                                      guint                  time,
                                      GimpContainerTreeView *tree_view)
{
  GtkTreePath             *path;
  GtkTreeViewDropPosition  drop_pos;

  if (y < SCROLL_DISTANCE || y > (widget->allocation.height - SCROLL_DISTANCE))
    {
      gint distance;

      if (y < SCROLL_DISTANCE)
        {
          tree_view->scroll_dir = GDK_SCROLL_UP;
          distance = MIN (-y, -1);
        }
      else
        {
          tree_view->scroll_dir = GDK_SCROLL_DOWN;
          distance = MAX (widget->allocation.height - y, 1);
        }

      tree_view->scroll_timeout_interval = SCROLL_INTERVAL * ABS (distance);

#ifdef SCROLL_DEBUG
      g_print ("drag_motion: scroll_distance = %d  scroll_interval = %d\n",
               distance, tree_view->scroll_timeout_interval);
#endif

      if (! tree_view->scroll_timeout_id)
        tree_view->scroll_timeout_id =
          g_timeout_add (tree_view->scroll_timeout_interval,
                         gimp_container_tree_view_scroll_timeout,
                         tree_view);
    }
  else if (tree_view->scroll_timeout_id)
    {
      g_source_remove (tree_view->scroll_timeout_id);
      tree_view->scroll_timeout_id = 0;
    }

  if (gimp_container_tree_view_drop_status (tree_view,
                                            context, x, y, time,
                                            &path, NULL, NULL, NULL, NULL,
                                            &drop_pos))
    {
      gtk_tree_view_set_drag_dest_row (tree_view->view, path, drop_pos);
      gtk_tree_path_free (path);
    }
  else
    {
      gtk_tree_view_unset_rows_drag_dest (tree_view->view);
    }

  /*  always return TRUE so drag_leave() is called  */
  return TRUE;
}

gboolean
gimp_container_tree_view_drag_drop (GtkWidget             *widget,
                                    GdkDragContext        *context,
                                    gint                   x,
                                    gint                   y,
                                    guint                  time,
                                    GimpContainerTreeView *tree_view)
{
  GimpDndType              src_type;
  GimpViewable            *src_viewable;
  GimpViewable            *dest_viewable;
  GdkAtom                  target;
  GtkTreeViewDropPosition  drop_pos;

  if (tree_view->scroll_timeout_id)
    {
      g_source_remove (tree_view->scroll_timeout_id);
      tree_view->scroll_timeout_id = 0;
    }

  if (gimp_container_tree_view_drop_status (tree_view,
                                            context, x, y, time,
                                            NULL, &target, &src_type,
                                            &src_viewable,
                                            &dest_viewable, &drop_pos))
    {
      GimpContainerTreeViewClass *tree_view_class;

      tree_view_class = GIMP_CONTAINER_TREE_VIEW_GET_CLASS (tree_view);

      if (src_viewable)
        tree_view_class->drop_viewable (tree_view, src_viewable,
                                        dest_viewable, drop_pos);
      else
        gtk_drag_get_data (widget, context, target, time);

      return TRUE;
    }

  return FALSE;
}

void
gimp_container_tree_view_drag_data_received (GtkWidget             *widget,
                                             GdkDragContext        *context,
                                             gint                   x,
                                             gint                   y,
                                             GtkSelectionData      *selection_data,
                                             guint                  info,
                                             guint                  time,
                                             GimpContainerTreeView *tree_view)
{
  GimpViewable            *dest_viewable;
  GtkTreeViewDropPosition  drop_pos;
  gboolean                 success = FALSE;

  if (gimp_container_tree_view_drop_status (tree_view,
                                            context, x, y, time,
                                            NULL, NULL, NULL, NULL,
                                            &dest_viewable, &drop_pos))
    {
      GimpContainerTreeViewClass *tree_view_class;

      tree_view_class = GIMP_CONTAINER_TREE_VIEW_GET_CLASS (tree_view);

      switch (info)
        {
        case GIMP_DND_TYPE_URI_LIST:
        case GIMP_DND_TYPE_TEXT_PLAIN:
        case GIMP_DND_TYPE_NETSCAPE_URL:
          if (tree_view_class->drop_uri_list)
            {
              GList *uri_list;

              uri_list = gimp_selection_data_get_uri_list (selection_data);

              if (uri_list)
                {
                  tree_view_class->drop_uri_list (tree_view, uri_list,
                                                  dest_viewable, drop_pos);

                  g_list_foreach (uri_list, (GFunc) g_free, NULL);
                  g_list_free (uri_list);

                  success = TRUE;
                }
            }
          break;

        case GIMP_DND_TYPE_COLOR:
          if (tree_view_class->drop_color)
            {
              GimpRGB color;

              if (gimp_selection_data_get_color (selection_data, &color))
                {
                  tree_view_class->drop_color (tree_view, &color,
                                               dest_viewable, drop_pos);

                  success = TRUE;
                }
            }
          break;

        case GIMP_DND_TYPE_SVG:
        case GIMP_DND_TYPE_SVG_XML:
          if (tree_view_class->drop_svg)
            {
              const guchar *stream;
              gsize         stream_length;

              stream = gimp_selection_data_get_stream (selection_data,
                                                       &stream_length);

              if (stream)
                {
                  tree_view_class->drop_svg (tree_view,
                                             (const gchar *) stream,
                                             stream_length,
                                             dest_viewable, drop_pos);

                  success = TRUE;
                }
            }
          break;

        default:
          break;
        }
    }

  gtk_drag_finish (context, success, FALSE, time);
}

gboolean
gimp_container_tree_view_real_drop_possible (GimpContainerTreeView   *tree_view,
                                             GimpDndType              src_type,
                                             GimpViewable            *src_viewable,
                                             GimpViewable            *dest_viewable,
                                             GtkTreeViewDropPosition  drop_pos,
                                             GtkTreeViewDropPosition *return_drop_pos,
                                             GdkDragAction           *return_drag_action)
{
  GimpContainerView *view       = GIMP_CONTAINER_VIEW (tree_view);
  GimpContainer     *container  = gimp_container_view_get_container (view);
  gint               src_index  = -1;
  gint               dest_index = -1;

  if (src_viewable && g_type_is_a (G_TYPE_FROM_INSTANCE (src_viewable),
                                   container->children_type))
    {
      if (src_viewable == dest_viewable)
        return FALSE;

      src_index  = gimp_container_get_child_index (container,
                                                   GIMP_OBJECT (src_viewable));
      dest_index = gimp_container_get_child_index (container,
                                                   GIMP_OBJECT (dest_viewable));

      if (src_index == -1 || dest_index == -1)
        return FALSE;
    }

  if (drop_pos == GTK_TREE_VIEW_DROP_BEFORE)
    {
      if (dest_index == (src_index + 1))
        return FALSE;
    }
  else
    {
      if (dest_index == (src_index - 1))
        return FALSE;
    }

  if (return_drop_pos)
    *return_drop_pos = drop_pos;

  if (return_drag_action)
    {
      if (src_viewable && g_type_is_a (G_TYPE_FROM_INSTANCE (src_viewable),
                                       container->children_type))
        *return_drag_action = GDK_ACTION_MOVE;
      else
        *return_drag_action = GDK_ACTION_COPY;
    }

  return TRUE;
}

void
gimp_container_tree_view_real_drop_viewable (GimpContainerTreeView   *tree_view,
                                             GimpViewable            *src_viewable,
                                             GimpViewable            *dest_viewable,
                                             GtkTreeViewDropPosition  drop_pos)
{
  GimpContainerView *view      = GIMP_CONTAINER_VIEW (tree_view);
  GimpContainer     *container = gimp_container_view_get_container (view);
  gint               src_index;
  gint               dest_index;

  src_index  = gimp_container_get_child_index (container,
                                               GIMP_OBJECT (src_viewable));
  dest_index = gimp_container_get_child_index (container,
                                               GIMP_OBJECT (dest_viewable));

  if (drop_pos == GTK_TREE_VIEW_DROP_AFTER && src_index > dest_index)
    {
      dest_index++;
    }
  else if (drop_pos == GTK_TREE_VIEW_DROP_BEFORE && src_index < dest_index)
    {
      dest_index--;
    }

  gimp_container_reorder (container, GIMP_OBJECT (src_viewable), dest_index);
}
