/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikamath/pikamath.h"
#include "libpikawidgets/pikawidgets.h"

#include "tools-types.h"

#include "core/pika-transform-utils.h"
#include "core/pikaimage.h"

#include "paint/pikaperspectiveclone.h"
#include "paint/pikaperspectivecloneoptions.h"

#include "widgets/pikahelp-ids.h"
#include "widgets/pikaviewablebox.h"
#include "widgets/pikawidgets-utils.h"

#include "display/pikacanvasgroup.h"
#include "display/pikadisplay.h"
#include "display/pikatooltransformgrid.h"

#include "pikaperspectiveclonetool.h"
#include "pikacloneoptions-gui.h"
#include "pikatoolcontrol.h"

#include "pika-intl.h"


/*  index into trans_info array  */
enum
{
  X0,
  Y0,
  X1,
  Y1,
  X2,
  Y2,
  X3,
  Y3,
  PIVOT_X,
  PIVOT_Y
};


static void     pika_perspective_clone_tool_constructed    (GObject          *object);

static gboolean pika_perspective_clone_tool_initialize     (PikaTool         *tool,
                                                            PikaDisplay      *display,
                                                            GError          **error);

static gboolean pika_perspective_clone_tool_has_display    (PikaTool         *tool,
                                                            PikaDisplay      *display);
static PikaDisplay *
                pika_perspective_clone_tool_has_image      (PikaTool         *tool,
                                                            PikaImage        *image);
static void     pika_perspective_clone_tool_control        (PikaTool         *tool,
                                                            PikaToolAction    action,
                                                            PikaDisplay      *display);
static void     pika_perspective_clone_tool_button_press   (PikaTool         *tool,
                                                            const PikaCoords *coords,
                                                            guint32           time,
                                                            GdkModifierType   state,
                                                            PikaButtonPressType  press_type,
                                                            PikaDisplay      *display);
static void     pika_perspective_clone_tool_button_release (PikaTool         *tool,
                                                            const PikaCoords *coords,
                                                            guint32           time,
                                                            GdkModifierType   state,
                                                            PikaButtonReleaseType  release_type,
                                                            PikaDisplay      *display);
static void     pika_perspective_clone_tool_motion         (PikaTool         *tool,
                                                            const PikaCoords *coords,
                                                            guint32           time,
                                                            GdkModifierType   state,
                                                            PikaDisplay      *display);
static void     pika_perspective_clone_tool_modifier_key   (PikaTool         *tool,
                                                            GdkModifierType   key,
                                                            gboolean          press,
                                                            GdkModifierType   state,
                                                            PikaDisplay      *display);
static void     pika_perspective_clone_tool_cursor_update  (PikaTool         *tool,
                                                            const PikaCoords *coords,
                                                            GdkModifierType   state,
                                                            PikaDisplay      *display);
static void     pika_perspective_clone_tool_oper_update    (PikaTool         *tool,
                                                            const PikaCoords *coords,
                                                            GdkModifierType   state,
                                                            gboolean          proximity,
                                                            PikaDisplay      *display);
static void     pika_perspective_clone_tool_options_notify (PikaTool         *tool,
                                                            PikaToolOptions  *options,
                                                            const GParamSpec *pspec);

static void     pika_perspective_clone_tool_draw           (PikaDrawTool             *draw_tool);

static void     pika_perspective_clone_tool_halt           (PikaPerspectiveCloneTool *clone_tool);
static void     pika_perspective_clone_tool_bounds         (PikaPerspectiveCloneTool *clone_tool,
                                                            PikaDisplay              *display);
static void     pika_perspective_clone_tool_prepare        (PikaPerspectiveCloneTool *clone_tool);
static void     pika_perspective_clone_tool_recalc_matrix  (PikaPerspectiveCloneTool *clone_tool,
                                                            PikaToolWidget           *widget);

static void     pika_perspective_clone_tool_widget_changed (PikaToolWidget           *widget,
                                                            PikaPerspectiveCloneTool *clone_tool);
static void     pika_perspective_clone_tool_widget_status  (PikaToolWidget           *widget,
                                                            const gchar              *status,
                                                            PikaPerspectiveCloneTool *clone_tool);

static GtkWidget *
                pika_perspective_clone_options_gui         (PikaToolOptions *tool_options);


G_DEFINE_TYPE (PikaPerspectiveCloneTool, pika_perspective_clone_tool,
               PIKA_TYPE_BRUSH_TOOL)

#define parent_class pika_perspective_clone_tool_parent_class


void
pika_perspective_clone_tool_register (PikaToolRegisterCallback  callback,
                                      gpointer                  data)
{
  (* callback) (PIKA_TYPE_PERSPECTIVE_CLONE_TOOL,
                PIKA_TYPE_PERSPECTIVE_CLONE_OPTIONS,
                pika_perspective_clone_options_gui,
                PIKA_PAINT_OPTIONS_CONTEXT_MASK |
                PIKA_CONTEXT_PROP_MASK_PATTERN,
                "pika-perspective-clone-tool",
                _("Perspective Clone"),
                _("Perspective Clone Tool: Clone from an image source "
                  "after applying a perspective transformation"),
                N_("_Perspective Clone"), NULL,
                NULL, PIKA_HELP_TOOL_PERSPECTIVE_CLONE,
                PIKA_ICON_TOOL_PERSPECTIVE_CLONE,
                data);
}

static void
pika_perspective_clone_tool_class_init (PikaPerspectiveCloneToolClass *klass)
{
  GObjectClass      *object_class    = G_OBJECT_CLASS (klass);
  PikaToolClass     *tool_class      = PIKA_TOOL_CLASS (klass);
  PikaDrawToolClass *draw_tool_class = PIKA_DRAW_TOOL_CLASS (klass);

  object_class->constructed  = pika_perspective_clone_tool_constructed;

  tool_class->initialize     = pika_perspective_clone_tool_initialize;
  tool_class->has_display    = pika_perspective_clone_tool_has_display;
  tool_class->has_image      = pika_perspective_clone_tool_has_image;
  tool_class->control        = pika_perspective_clone_tool_control;
  tool_class->button_press   = pika_perspective_clone_tool_button_press;
  tool_class->button_release = pika_perspective_clone_tool_button_release;
  tool_class->motion         = pika_perspective_clone_tool_motion;
  tool_class->modifier_key   = pika_perspective_clone_tool_modifier_key;
  tool_class->cursor_update  = pika_perspective_clone_tool_cursor_update;
  tool_class->oper_update    = pika_perspective_clone_tool_oper_update;
  tool_class->options_notify = pika_perspective_clone_tool_options_notify;

  draw_tool_class->draw      = pika_perspective_clone_tool_draw;
}

static void
pika_perspective_clone_tool_init (PikaPerspectiveCloneTool *clone_tool)
{
  PikaTool *tool = PIKA_TOOL (clone_tool);

  pika_tool_control_set_action_object_2 (tool->control,
                                         "context-pattern-select-set");

  pika_matrix3_identity (&clone_tool->transform);
  pika_paint_tool_enable_multi_paint (PIKA_PAINT_TOOL (clone_tool));
}

static void
pika_perspective_clone_tool_constructed (GObject *object)
{
  PikaPerspectiveCloneOptions *options;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (object);

  G_OBJECT_CLASS (parent_class)->constructed (object);

  if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_ADJUST)
    pika_paint_tool_set_active (PIKA_PAINT_TOOL (object), FALSE);
}

static gboolean
pika_perspective_clone_tool_initialize (PikaTool     *tool,
                                        PikaDisplay  *display,
                                        GError      **error)
{
  PikaPerspectiveCloneTool *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaImage                *image      = pika_display_get_image (display);
  GList                    *drawables;

  if (! PIKA_TOOL_CLASS (parent_class)->initialize (tool, display, error))
    {
      return FALSE;
    }

  drawables = pika_image_get_selected_drawables (image);
  if (g_list_length (drawables) == 0)
    {
      pika_tool_message_literal (tool, display, _("No selected drawables."));

      g_list_free (drawables);

      return FALSE;
    }

  if (display != tool->display)
    {
      PikaDisplayShell *shell = pika_display_get_shell (display);
      gint              i;

      tool->display   = display;
      tool->drawables = drawables;

      /*  Find the transform bounds initializing */
      pika_perspective_clone_tool_bounds (clone_tool, display);

      pika_perspective_clone_tool_prepare (clone_tool);

      /*  Recalculate the transform tool  */
      pika_perspective_clone_tool_recalc_matrix (clone_tool, NULL);

      clone_tool->widget =
        pika_tool_transform_grid_new (shell,
                                      &clone_tool->transform,
                                      clone_tool->x1,
                                      clone_tool->y1,
                                      clone_tool->x2,
                                      clone_tool->y2);

      g_object_set (clone_tool->widget,
                    "pivot-x",                 (clone_tool->x1 + clone_tool->x2) / 2.0,
                    "pivot-y",                 (clone_tool->y1 + clone_tool->y2) / 2.0,
                    "inside-function",         PIKA_TRANSFORM_FUNCTION_MOVE,
                    "outside-function",        PIKA_TRANSFORM_FUNCTION_ROTATE,
                    "use-corner-handles",      TRUE,
                    "use-perspective-handles", TRUE,
                    "use-side-handles",        TRUE,
                    "use-shear-handles",       TRUE,
                    "use-pivot-handle",        TRUE,
                    NULL);

      g_signal_connect (clone_tool->widget, "changed",
                        G_CALLBACK (pika_perspective_clone_tool_widget_changed),
                        clone_tool);
      g_signal_connect (clone_tool->widget, "status",
                        G_CALLBACK (pika_perspective_clone_tool_widget_status),
                        clone_tool);

      /*  start drawing the bounding box and handles...  */
      if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool)))
        pika_draw_tool_stop (PIKA_DRAW_TOOL (tool));

      pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);

      /*  Save the current transformation info  */
      for (i = 0; i < TRANS_INFO_SIZE; i++)
        clone_tool->old_trans_info[i] = clone_tool->trans_info[i];
    }
  else
    {
      g_list_free (drawables);
    }

  return TRUE;
}

static gboolean
pika_perspective_clone_tool_has_display (PikaTool    *tool,
                                         PikaDisplay *display)
{
  PikaPerspectiveCloneTool *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);

  return (display == clone_tool->src_display ||
          PIKA_TOOL_CLASS (parent_class)->has_display (tool, display));
}

static PikaDisplay *
pika_perspective_clone_tool_has_image (PikaTool  *tool,
                                       PikaImage *image)
{
  PikaPerspectiveCloneTool *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaDisplay              *display;

  display = PIKA_TOOL_CLASS (parent_class)->has_image (tool, image);

  if (! display && clone_tool->src_display)
    {
      if (image && pika_display_get_image (clone_tool->src_display) == image)
        display = clone_tool->src_display;

      /*  NULL image means any display  */
      if (! image)
        display = clone_tool->src_display;
    }

  return display;
}

static void
pika_perspective_clone_tool_control (PikaTool       *tool,
                                     PikaToolAction  action,
                                     PikaDisplay    *display)
{
  PikaPerspectiveCloneTool *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);

  switch (action)
    {
    case PIKA_TOOL_ACTION_PAUSE:
    case PIKA_TOOL_ACTION_RESUME:
      break;

    case PIKA_TOOL_ACTION_HALT:
      pika_perspective_clone_tool_halt (clone_tool);
      break;

    case PIKA_TOOL_ACTION_COMMIT:
      break;
    }

  PIKA_TOOL_CLASS (parent_class)->control (tool, action, display);
}

static void
pika_perspective_clone_tool_button_press (PikaTool            *tool,
                                          const PikaCoords    *coords,
                                          guint32              time,
                                          GdkModifierType      state,
                                          PikaButtonPressType  press_type,
                                          PikaDisplay         *display)
{
  PikaPaintTool               *paint_tool  = PIKA_PAINT_TOOL (tool);
  PikaPerspectiveCloneTool    *clone_tool  = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaPerspectiveClone        *clone       = PIKA_PERSPECTIVE_CLONE (paint_tool->core);
  PikaSourceCore              *source_core = PIKA_SOURCE_CORE (clone);
  PikaPerspectiveCloneOptions *options;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);

  if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_ADJUST)
    {
      if (clone_tool->widget)
        {
          pika_tool_widget_hover (clone_tool->widget, coords, state, TRUE);

          if (pika_tool_widget_button_press (clone_tool->widget, coords,
                                             time, state, press_type))
            {
              clone_tool->grab_widget = clone_tool->widget;
            }
        }

      pika_tool_control_activate (tool->control);
    }
  else
    {
      GdkModifierType extend_mask = pika_get_extend_selection_mask ();
      GdkModifierType toggle_mask = pika_get_toggle_behavior_mask ();
      gdouble         nnx, nny;

      pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

      if ((state & (toggle_mask | extend_mask)) == toggle_mask)
        {
          source_core->set_source = TRUE;

          clone_tool->src_display = display;
        }
      else
        {
          source_core->set_source = FALSE;
        }

      PIKA_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
                                                    press_type, display);

      /* Set the coordinates for the reference cross */
      pika_perspective_clone_get_source_point (clone,
                                               coords->x, coords->y,
                                               &nnx, &nny);

      clone_tool->src_x = floor (nnx);
      clone_tool->src_y = floor (nny);

      pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
    }
}

static void
pika_perspective_clone_tool_button_release (PikaTool              *tool,
                                            const PikaCoords      *coords,
                                            guint32                time,
                                            GdkModifierType        state,
                                            PikaButtonReleaseType  release_type,
                                            PikaDisplay           *display)
{
  PikaPerspectiveCloneTool    *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaPerspectiveCloneOptions *options;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);

  switch (options->clone_mode)
    {
    case PIKA_PERSPECTIVE_CLONE_MODE_ADJUST:
      pika_tool_control_halt (tool->control);

      if (clone_tool->grab_widget)
        {
          pika_tool_widget_button_release (clone_tool->grab_widget,
                                           coords, time, state, release_type);
          clone_tool->grab_widget = NULL;
        }
      break;

    case PIKA_PERSPECTIVE_CLONE_MODE_PAINT:
      PIKA_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
                                                      release_type, display);
      break;
    }
}

static void
pika_perspective_clone_tool_motion (PikaTool         *tool,
                                    const PikaCoords *coords,
                                    guint32           time,
                                    GdkModifierType   state,
                                    PikaDisplay      *display)
{
  PikaPerspectiveCloneTool    *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaPaintTool               *paint_tool = PIKA_PAINT_TOOL (tool);
  PikaPerspectiveClone        *clone      = PIKA_PERSPECTIVE_CLONE (paint_tool->core);
  PikaPerspectiveCloneOptions *options;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);

  if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_ADJUST)
    {
      if (clone_tool->grab_widget)
        {
          pika_tool_widget_motion (clone_tool->grab_widget, coords, time, state);
        }
    }
  else if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_PAINT)
    {
      gdouble nnx, nny;

      pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

      PIKA_TOOL_CLASS (parent_class)->motion (tool, coords, time, state,
                                              display);

      /* Set the coordinates for the reference cross */
      pika_perspective_clone_get_source_point (clone,
                                               coords->x, coords->y,
                                               &nnx, &nny);

      clone_tool->src_x = floor (nnx);
      clone_tool->src_y = floor (nny);

      pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
    }
}

static void
pika_perspective_clone_tool_modifier_key (PikaTool        *tool,
                                          GdkModifierType  key,
                                          gboolean         press,
                                          GdkModifierType  state,
                                          PikaDisplay     *display)
{
  PikaPerspectiveCloneTool    *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaPerspectiveCloneOptions *options;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);

  if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_PAINT &&
      key == pika_get_toggle_behavior_mask ())
    {
      if (press)
        {
          clone_tool->saved_precision =
            pika_tool_control_get_precision (tool->control);
          pika_tool_control_set_precision (tool->control,
                                           PIKA_CURSOR_PRECISION_PIXEL_CENTER);
        }
      else
        {
          pika_tool_control_set_precision (tool->control,
                                           clone_tool->saved_precision);
        }
    }

  PIKA_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
                                                display);
}

static void
pika_perspective_clone_tool_cursor_update (PikaTool         *tool,
                                           const PikaCoords *coords,
                                           GdkModifierType   state,
                                           PikaDisplay      *display)
{
  PikaPerspectiveCloneTool    *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaPerspectiveCloneOptions *options;
  PikaImage                   *image;
  PikaToolClass               *tool_class;
  PikaCursorType               cursor      = PIKA_CURSOR_MOUSE;
  PikaToolCursorType           tool_cursor = PIKA_TOOL_CURSOR_NONE;
  PikaCursorModifier           modifier    = PIKA_CURSOR_MODIFIER_NONE;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);

  image = pika_display_get_image (display);

  if (pika_image_coords_in_active_pickable (image, coords,
                                            FALSE, FALSE, TRUE))
    {
      cursor = PIKA_CURSOR_MOUSE;
    }

  if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_ADJUST)
    {
      if (clone_tool->widget)
        {
          if (display == tool->display)
            {
              pika_tool_widget_get_cursor (clone_tool->widget,
                                           coords, state,
                                           &cursor, &tool_cursor, &modifier);
            }
        }
    }
  else
    {
      GdkModifierType extend_mask = pika_get_extend_selection_mask ();
      GdkModifierType toggle_mask = pika_get_toggle_behavior_mask ();

      if ((state & (toggle_mask | extend_mask)) == toggle_mask)
        {
          cursor = PIKA_CURSOR_CROSSHAIR_SMALL;
        }
      else if (! PIKA_SOURCE_OPTIONS (options)->src_drawables)
        {
          modifier = PIKA_CURSOR_MODIFIER_BAD;
        }

      tool_cursor = PIKA_TOOL_CURSOR_CLONE;
    }

  pika_tool_control_set_cursor          (tool->control, cursor);
  pika_tool_control_set_tool_cursor     (tool->control, tool_cursor);
  pika_tool_control_set_cursor_modifier (tool->control, modifier);

  /*  If we are in adjust mode, skip the PikaBrushClass when chaining up.
   *  This ensures that the cursor will be set regardless of
   *  PikaBrushTool::show_cursor (see bug #354933).
   */
  if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_ADJUST)
    tool_class = PIKA_TOOL_CLASS (g_type_class_peek_parent (parent_class));
  else
    tool_class = PIKA_TOOL_CLASS (parent_class);

  tool_class->cursor_update (tool, coords, state, display);
}

static void
pika_perspective_clone_tool_oper_update (PikaTool         *tool,
                                         const PikaCoords *coords,
                                         GdkModifierType   state,
                                         gboolean          proximity,
                                         PikaDisplay      *display)
{
  PikaPerspectiveCloneTool    *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaPerspectiveCloneOptions *options;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);

  if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_ADJUST)
    {
      if (clone_tool->widget)
        {
          if (display == tool->display)
            {
              pika_tool_widget_hover (clone_tool->widget, coords, state,
                                      proximity);
            }
        }
    }
  else
    {
      PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
                                                   proximity, display);

      if (proximity)
        {
          PikaPaintCore        *core        = PIKA_PAINT_TOOL (tool)->core;
          PikaPerspectiveClone *clone       = PIKA_PERSPECTIVE_CLONE (core);
          PikaSourceCore       *source_core = PIKA_SOURCE_CORE (core);

          if (PIKA_SOURCE_OPTIONS (options)->src_drawables == NULL)
            {
              pika_tool_replace_status (tool, display,
                                        _("Ctrl-Click to set a clone source"));
            }
          else
            {
              pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

              g_object_get (options,
                            "src-x", &clone_tool->src_x,
                            "src-y", &clone_tool->src_y,
                            NULL);

              if (! source_core->first_stroke)
                {
                  if (PIKA_SOURCE_OPTIONS (options)->align_mode ==
                      PIKA_SOURCE_ALIGN_YES)
                    {
                      gdouble nnx, nny;

                      /* Set the coordinates for the reference cross */
                      pika_perspective_clone_get_source_point (clone,
                                                               coords->x,
                                                               coords->y,
                                                               &nnx, &nny);

                      clone_tool->src_x = floor (nnx);
                      clone_tool->src_y = floor (nny);
                    }
                }

              pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
            }
        }
    }
}

static void
pika_perspective_clone_tool_options_notify (PikaTool         *tool,
                                            PikaToolOptions  *options,
                                            const GParamSpec *pspec)
{
  PikaPerspectiveCloneTool    *clone_tool = PIKA_PERSPECTIVE_CLONE_TOOL (tool);
  PikaPaintTool               *paint_tool = PIKA_PAINT_TOOL (tool);
  PikaPerspectiveCloneOptions *clone_options;

  clone_options = PIKA_PERSPECTIVE_CLONE_OPTIONS (options);

  PIKA_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);

  if (! strcmp (pspec->name, "clone-mode"))
    {
      PikaPerspectiveClone *clone;

      clone = PIKA_PERSPECTIVE_CLONE (PIKA_PAINT_TOOL (tool)->core);

      pika_draw_tool_pause (PIKA_DRAW_TOOL (clone_tool));

      if (clone_options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_PAINT)
        {
          pika_perspective_clone_set_transform (clone, &clone_tool->transform);

          pika_paint_tool_set_active (paint_tool, TRUE);
        }
      else
        {
          pika_paint_tool_set_active (paint_tool, FALSE);

          pika_tool_control_set_precision (tool->control,
                                           PIKA_CURSOR_PRECISION_SUBPIXEL);

          /*  start drawing the bounding box and handles...  */
          if (tool->display &&
              ! pika_draw_tool_is_active (PIKA_DRAW_TOOL (clone_tool)))
            {
              pika_draw_tool_start (PIKA_DRAW_TOOL (clone_tool), tool->display);
            }
        }

      pika_draw_tool_resume (PIKA_DRAW_TOOL (clone_tool));
    }
}

static void
pika_perspective_clone_tool_draw (PikaDrawTool *draw_tool)
{
  PikaTool                    *tool        = PIKA_TOOL (draw_tool);
  PikaPerspectiveCloneTool    *clone_tool  = PIKA_PERSPECTIVE_CLONE_TOOL (draw_tool);
  PikaPerspectiveCloneOptions *options;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);

  if (options->clone_mode == PIKA_PERSPECTIVE_CLONE_MODE_ADJUST)
    {
      if (clone_tool->widget)
        {
          PikaCanvasItem *item = pika_tool_widget_get_item (clone_tool->widget);

          pika_draw_tool_add_item (draw_tool, item);
        }
    }
  else
    {
      PikaCanvasGroup *stroke_group;

      stroke_group = pika_draw_tool_add_stroke_group (draw_tool);

      /*  draw the bounding box  */
      pika_draw_tool_push_group (draw_tool, stroke_group);

      pika_draw_tool_add_line (draw_tool,
                               clone_tool->trans_info[X0],
                               clone_tool->trans_info[Y0],
                               clone_tool->trans_info[X1],
                               clone_tool->trans_info[Y1]);
      pika_draw_tool_add_line (draw_tool,
                               clone_tool->trans_info[X1],
                               clone_tool->trans_info[Y1],
                               clone_tool->trans_info[X3],
                               clone_tool->trans_info[Y3]);
      pika_draw_tool_add_line (draw_tool,
                               clone_tool->trans_info[X2],
                               clone_tool->trans_info[Y2],
                               clone_tool->trans_info[X3],
                               clone_tool->trans_info[Y3]);
      pika_draw_tool_add_line (draw_tool,
                               clone_tool->trans_info[X2],
                               clone_tool->trans_info[Y2],
                               clone_tool->trans_info[X0],
                               clone_tool->trans_info[Y0]);

      pika_draw_tool_pop_group (draw_tool);
    }

  if (PIKA_SOURCE_OPTIONS (options)->src_drawables && clone_tool->src_display)
    {
      PikaDisplay *tmp_display;

      tmp_display = draw_tool->display;
      draw_tool->display = clone_tool->src_display;

      pika_draw_tool_add_handle (draw_tool,
                                 PIKA_HANDLE_CROSS,
                                 clone_tool->src_x + 0.5,
                                 clone_tool->src_y + 0.5,
                                 PIKA_TOOL_HANDLE_SIZE_CROSS,
                                 PIKA_TOOL_HANDLE_SIZE_CROSS,
                                 PIKA_HANDLE_ANCHOR_CENTER);

      draw_tool->display = tmp_display;
    }

  PIKA_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
}

static void
pika_perspective_clone_tool_halt (PikaPerspectiveCloneTool *clone_tool)
{
  PikaTool                    *tool = PIKA_TOOL (clone_tool);
  PikaPerspectiveCloneOptions *options;

  options = PIKA_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);

  clone_tool->src_display = NULL;

  g_object_set (options,
                "src-drawables", NULL,
                NULL);

  if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool)))
    pika_draw_tool_stop (PIKA_DRAW_TOOL (tool));

  g_clear_object (&clone_tool->widget);

  tool->display   = NULL;
  g_list_free (tool->drawables);
  tool->drawables = NULL;
}

static void
pika_perspective_clone_tool_bounds (PikaPerspectiveCloneTool *clone_tool,
                                    PikaDisplay              *display)
{
  PikaImage *image = pika_display_get_image (display);

  clone_tool->x1 = 0;
  clone_tool->y1 = 0;
  clone_tool->x2 = pika_image_get_width  (image);
  clone_tool->y2 = pika_image_get_height (image);
}

static void
pika_perspective_clone_tool_prepare (PikaPerspectiveCloneTool *clone_tool)
{
  clone_tool->trans_info[PIVOT_X] = (gdouble) (clone_tool->x1 + clone_tool->x2) / 2.0;
  clone_tool->trans_info[PIVOT_Y] = (gdouble) (clone_tool->y1 + clone_tool->y2) / 2.0;

  clone_tool->trans_info[X0] = clone_tool->x1;
  clone_tool->trans_info[Y0] = clone_tool->y1;
  clone_tool->trans_info[X1] = clone_tool->x2;
  clone_tool->trans_info[Y1] = clone_tool->y1;
  clone_tool->trans_info[X2] = clone_tool->x1;
  clone_tool->trans_info[Y2] = clone_tool->y2;
  clone_tool->trans_info[X3] = clone_tool->x2;
  clone_tool->trans_info[Y3] = clone_tool->y2;
}

static void
pika_perspective_clone_tool_recalc_matrix (PikaPerspectiveCloneTool *clone_tool,
                                           PikaToolWidget           *widget)
{
  pika_matrix3_identity (&clone_tool->transform);
  pika_transform_matrix_perspective (&clone_tool->transform,
                                     clone_tool->x1,
                                     clone_tool->y1,
                                     clone_tool->x2 - clone_tool->x1,
                                     clone_tool->y2 - clone_tool->y1,
                                     clone_tool->trans_info[X0],
                                     clone_tool->trans_info[Y0],
                                     clone_tool->trans_info[X1],
                                     clone_tool->trans_info[Y1],
                                     clone_tool->trans_info[X2],
                                     clone_tool->trans_info[Y2],
                                     clone_tool->trans_info[X3],
                                     clone_tool->trans_info[Y3]);

  if (widget)
    g_object_set (widget,
                  "transform", &clone_tool->transform,
                  "x1",        (gdouble) clone_tool->x1,
                  "y1",        (gdouble) clone_tool->y1,
                  "x2",        (gdouble) clone_tool->x2,
                  "y2",        (gdouble) clone_tool->y2,
                  "pivot-x",   clone_tool->trans_info[PIVOT_X],
                  "pivot-y",   clone_tool->trans_info[PIVOT_Y],
                  NULL);
}

static void
pika_perspective_clone_tool_widget_changed (PikaToolWidget           *widget,
                                            PikaPerspectiveCloneTool *clone_tool)
{
  PikaMatrix3 *transform;

  g_object_get (widget,
                "transform", &transform,
                "pivot-x",   &clone_tool->trans_info[PIVOT_X],
                "pivot-y",   &clone_tool->trans_info[PIVOT_Y],
                NULL);

  pika_matrix3_transform_point (transform,
                                clone_tool->x1, clone_tool->y1,
                                &clone_tool->trans_info[X0],
                                &clone_tool->trans_info[Y0]);
  pika_matrix3_transform_point (transform,
                                clone_tool->x2, clone_tool->y1,
                                &clone_tool->trans_info[X1],
                                &clone_tool->trans_info[Y1]);
  pika_matrix3_transform_point (transform,
                                clone_tool->x1, clone_tool->y2,
                                &clone_tool->trans_info[X2],
                                &clone_tool->trans_info[Y2]);
  pika_matrix3_transform_point (transform,
                                clone_tool->x2, clone_tool->y2,
                                &clone_tool->trans_info[X3],
                                &clone_tool->trans_info[Y3]);

  g_free (transform);

  pika_perspective_clone_tool_recalc_matrix (clone_tool, NULL);
}

static void
pika_perspective_clone_tool_widget_status (PikaToolWidget           *widget,
                                           const gchar              *status,
                                           PikaPerspectiveCloneTool *clone_tool)
{
  PikaTool *tool = PIKA_TOOL (clone_tool);

  if (status)
    {
      pika_tool_replace_status (tool, tool->display, "%s", status);
    }
  else
    {
      pika_tool_pop_status (tool, tool->display);
    }
}


/*  tool options stuff  */

static GtkWidget *
pika_perspective_clone_options_gui (PikaToolOptions *tool_options)
{
  GObject   *config = G_OBJECT (tool_options);
  GtkWidget *vbox   = pika_clone_options_gui (tool_options);
  GtkWidget *mode;

  /* radio buttons to set if you are modifying the perspective plane
   * or painting
   */
  mode = pika_prop_enum_radio_box_new (config, "clone-mode", 0, 0);
  gtk_box_pack_start (GTK_BOX (vbox), mode, FALSE, FALSE, 0);
  gtk_box_reorder_child (GTK_BOX (vbox), mode, 0);

  return vbox;
}
