/* 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 <gdk/gdkkeysyms.h>

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

#include "tools-types.h"

#include "config/pikadisplayconfig.h"

#include "core/pika.h"
#include "core/pikaguide.h"
#include "core/pikaimage.h"
#include "core/pikaimage-arrange.h"
#include "core/pikaimage-pick-item.h"
#include "core/pikaimage-undo.h"
#include "core/pikalayer.h"
#include "core/pikapickable.h"
#include "core/pikapickable-auto-shrink.h"

#include "vectors/pikavectors.h"

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

#include "display/pikacanvasitem.h"
#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikadisplayshell-appearance.h"

#include "pikaalignoptions.h"
#include "pikaaligntool.h"
#include "pikatoolcontrol.h"

#include "pika-intl.h"


#define EPSILON  3   /* move distance after which we do a box-select */


/*  local function prototypes  */

static void     pika_align_tool_constructed    (GObject               *object);

static void     pika_align_tool_control        (PikaTool              *tool,
                                                PikaToolAction         action,
                                                PikaDisplay           *display);
static void     pika_align_tool_button_press   (PikaTool              *tool,
                                                const PikaCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state,
                                                PikaButtonPressType    press_type,
                                                PikaDisplay           *display);
static void     pika_align_tool_button_release (PikaTool              *tool,
                                                const PikaCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state,
                                                PikaButtonReleaseType  release_type,
                                                PikaDisplay           *display);
static void     pika_align_tool_motion         (PikaTool              *tool,
                                                const PikaCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state,
                                                PikaDisplay           *display);
static gboolean pika_align_tool_key_press      (PikaTool              *tool,
                                                GdkEventKey           *kevent,
                                                PikaDisplay           *display);
static void     pika_align_tool_oper_update    (PikaTool              *tool,
                                                const PikaCoords      *coords,
                                                GdkModifierType        state,
                                                gboolean               proximity,
                                                PikaDisplay           *display);
static void     pika_align_tool_status_update  (PikaTool              *tool,
                                                PikaDisplay           *display,
                                                GdkModifierType        state,
                                                gboolean               proximity);
static void     pika_align_tool_cursor_update  (PikaTool              *tool,
                                                const PikaCoords      *coords,
                                                GdkModifierType        state,
                                                PikaDisplay           *display);
const gchar *   pika_align_tool_can_undo       (PikaTool              *tool,
                                                PikaDisplay           *display);
static gboolean pika_align_tool_undo           (PikaTool              *tool,
                                                PikaDisplay           *display);

static void     pika_align_tool_draw           (PikaDrawTool          *draw_tool);
static void     pika_align_tool_redraw         (PikaAlignTool         *align_tool);

static void     pika_align_tool_align          (PikaAlignTool         *align_tool,
                                                PikaAlignmentType      align_type);

static gint     pika_align_tool_undo_idle      (gpointer               data);
static void    pika_align_tool_display_changed (PikaContext           *context,
                                                PikaDisplay           *display,
                                                PikaAlignTool         *align_tool);


G_DEFINE_TYPE (PikaAlignTool, pika_align_tool, PIKA_TYPE_DRAW_TOOL)

#define parent_class pika_align_tool_parent_class


void
pika_align_tool_register (PikaToolRegisterCallback  callback,
                          gpointer                  data)
{
  (* callback) (PIKA_TYPE_ALIGN_TOOL,
                PIKA_TYPE_ALIGN_OPTIONS,
                pika_align_options_gui,
                0,
                "pika-align-tool",
                _("Align and Distribute"),
                _("Alignment Tool: Align or arrange layers and other objects"),
                N_("_Align and Distribute"), "Q",
                NULL, PIKA_HELP_TOOL_ALIGN,
                PIKA_ICON_TOOL_ALIGN,
                data);
}

static void
pika_align_tool_class_init (PikaAlignToolClass *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_align_tool_constructed;

  tool_class->control        = pika_align_tool_control;
  tool_class->button_press   = pika_align_tool_button_press;
  tool_class->button_release = pika_align_tool_button_release;
  tool_class->motion         = pika_align_tool_motion;
  tool_class->key_press      = pika_align_tool_key_press;
  tool_class->oper_update    = pika_align_tool_oper_update;
  tool_class->cursor_update  = pika_align_tool_cursor_update;
  tool_class->can_undo       = pika_align_tool_can_undo;
  tool_class->undo           = pika_align_tool_undo;

  draw_tool_class->draw      = pika_align_tool_draw;
}

static void
pika_align_tool_init (PikaAlignTool *align_tool)
{
  PikaTool *tool = PIKA_TOOL (align_tool);

  pika_tool_control_set_snap_to     (tool->control, FALSE);
  pika_tool_control_set_precision   (tool->control,
                                     PIKA_CURSOR_PRECISION_PIXEL_BORDER);
  pika_tool_control_set_tool_cursor (tool->control, PIKA_TOOL_CURSOR_MOVE);

  align_tool->function = ALIGN_TOOL_REF_IDLE;
}

static void
pika_align_tool_constructed (GObject *object)
{
  PikaAlignTool    *align_tool = PIKA_ALIGN_TOOL (object);
  PikaAlignOptions *options;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  options = PIKA_ALIGN_TOOL_GET_OPTIONS (align_tool);

  g_signal_connect_object (options, "align-button-clicked",
                           G_CALLBACK (pika_align_tool_align),
                           align_tool, G_CONNECT_SWAPPED);
  g_signal_connect_object (options, "notify::align-reference",
                           G_CALLBACK (pika_align_tool_redraw),
                           align_tool, G_CONNECT_SWAPPED);
  g_signal_connect_object (options, "notify::align-contents",
                           G_CALLBACK (pika_align_tool_redraw),
                           align_tool, G_CONNECT_SWAPPED);
  g_signal_connect_object (pika_get_user_context (PIKA_CONTEXT (options)->pika),
                           "display-changed",
                           G_CALLBACK (pika_align_tool_display_changed),
                           align_tool, 0);
}

static void
pika_align_tool_control (PikaTool       *tool,
                         PikaToolAction  action,
                         PikaDisplay    *display)
{
  switch (action)
    {
    case PIKA_TOOL_ACTION_PAUSE:
    case PIKA_TOOL_ACTION_RESUME:
      break;

    case PIKA_TOOL_ACTION_HALT:
      break;

    case PIKA_TOOL_ACTION_COMMIT:
      break;
    }

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

static void
pika_align_tool_button_press (PikaTool            *tool,
                              const PikaCoords    *coords,
                              guint32              time,
                              GdkModifierType      state,
                              PikaButtonPressType  press_type,
                              PikaDisplay         *display)
{
  PikaAlignTool *align_tool  = PIKA_ALIGN_TOOL (tool);

  pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

  /*  If the tool was being used in another image... reset it  */
  if (display != tool->display)
    pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);

  tool->display = display;

  pika_tool_control_activate (tool->control);

  align_tool->x2 = align_tool->x1 = coords->x;
  align_tool->y2 = align_tool->y1 = coords->y;

  if (! pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool)))
    pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);

  pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
}

/* some rather complex logic here.  If the user clicks without modifiers,
 * then we start a new list, and use the first object in it as reference.
 * If the user clicks using Shift, or draws a rubber-band box, then
 * we add objects to the list, but do not specify which one should
 * be used as reference.
 */
static void
pika_align_tool_button_release (PikaTool              *tool,
                                const PikaCoords      *coords,
                                guint32                time,
                                GdkModifierType        state,
                                PikaButtonReleaseType  release_type,
                                PikaDisplay           *display)
{
  PikaAlignTool    *align_tool = PIKA_ALIGN_TOOL (tool);
  PikaAlignOptions *options    = PIKA_ALIGN_TOOL_GET_OPTIONS (tool);
  PikaDisplayShell *shell      = pika_display_get_shell (display);
  PikaImage        *image      = pika_display_get_image (display);
  GdkModifierType   extend_mask;

  extend_mask = pika_get_extend_selection_mask ();

  pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

  pika_tool_control_halt (tool->control);

  if (release_type == PIKA_BUTTON_RELEASE_CANCEL)
    {
      align_tool->x2 = align_tool->x1;
      align_tool->y2 = align_tool->y1;

      pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
      return;
    }

  if (state & GDK_MOD1_MASK)
    {
      PikaGuide *guide = NULL;

      if (pika_display_shell_get_show_guides (shell))
        {
          gint snap_distance = display->config->snap_distance;

          guide = pika_image_pick_guide (image,
                                          coords->x, coords->y,
                                          FUNSCALEX (shell, snap_distance),
                                          FUNSCALEY (shell, snap_distance));
        }

      pika_align_options_pick_guide (options, guide, (gboolean) state & extend_mask);
    }
  else
    {
      GObject *object = NULL;

      /* Check if a layer is fully included in the rubber-band rectangle.
       * Don't verify for too small rectangles.
       */
      /* FIXME: look for vectors too */
      if (hypot (coords->x - align_tool->x1,
                 coords->y - align_tool->y1) > EPSILON)
        {
          gint   X0 = MIN (coords->x, align_tool->x1);
          gint   X1 = MAX (coords->x, align_tool->x1);
          gint   Y0 = MIN (coords->y, align_tool->y1);
          gint   Y1 = MAX (coords->y, align_tool->y1);
          GList *all_layers;
          GList *list;

          all_layers = pika_image_get_layer_list (image);

          for (list = all_layers; list; list = g_list_next (list))
            {
              PikaLayer *layer = list->data;
              gint       x0, y0, x1, y1;

              if (! pika_item_get_visible (PIKA_ITEM (layer)))
                continue;

              pika_item_get_offset (PIKA_ITEM (layer), &x0, &y0);
              x1 = x0 + pika_item_get_width  (PIKA_ITEM (layer));
              y1 = y0 + pika_item_get_height (PIKA_ITEM (layer));

              if (x0 < X0 || y0 < Y0 || x1 > X1 || y1 > Y1)
                continue;

              object = G_OBJECT (layer);
              break;
            }

          g_list_free (all_layers);
        }

      if (object == NULL)
        {
          PikaVectors *vectors;
          PikaGuide   *guide;
          PikaLayer   *layer;
          GObject     *previously_picked;
          gint         snap_distance = display->config->snap_distance;

          previously_picked = pika_align_options_get_reference (options, FALSE);


          if ((vectors = pika_image_pick_vectors (image,
                                                  coords->x, coords->y,
                                                  FUNSCALEX (shell, snap_distance),
                                                  FUNSCALEY (shell, snap_distance))))
            {
              object = G_OBJECT (vectors);
            }
          else if (pika_display_shell_get_show_guides (shell) &&
                   (guide = pika_image_pick_guide (image,
                                                   coords->x, coords->y,
                                                   FUNSCALEX (shell, snap_distance),
                                                   FUNSCALEY (shell, snap_distance))))
            {
              object = G_OBJECT (guide);
            }
          else if ((layer = pika_image_pick_layer (image, coords->x, coords->y,
                                                   previously_picked && PIKA_IS_LAYER (previously_picked)? PIKA_LAYER (previously_picked) : NULL)))
            {
              object = G_OBJECT (layer);
            }
        }

      if (object)
        pika_align_options_pick_reference (options, object);
    }

  align_tool->x2 = align_tool->x1;
  align_tool->y2 = align_tool->y1;

  pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
}

static void
pika_align_tool_motion (PikaTool         *tool,
                        const PikaCoords *coords,
                        guint32           time,
                        GdkModifierType   state,
                        PikaDisplay      *display)
{
  PikaAlignTool *align_tool = PIKA_ALIGN_TOOL (tool);

  pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

  align_tool->x2 = coords->x;
  align_tool->y2 = coords->y;

  pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
}

static gboolean
pika_align_tool_key_press (PikaTool    *tool,
                           GdkEventKey *kevent,
                           PikaDisplay *display)
{
  if (display == tool->display)
    {
      switch (kevent->keyval)
        {
        case GDK_KEY_Escape:
          pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);
          return TRUE;

        default:
          break;
        }
    }

  return FALSE;
}

static void
pika_align_tool_oper_update (PikaTool         *tool,
                             const PikaCoords *coords,
                             GdkModifierType   state,
                             gboolean          proximity,
                             PikaDisplay      *display)
{
  PikaAlignTool    *align_tool    = PIKA_ALIGN_TOOL (tool);
  PikaAlignOptions *options       = PIKA_ALIGN_TOOL_GET_OPTIONS (align_tool);
  PikaDisplayShell *shell         = pika_display_get_shell (display);
  PikaImage        *image         = pika_display_get_image (display);
  gint              snap_distance = display->config->snap_distance;

  state &= pika_get_all_modifiers_mask ();

  align_tool->function = ALIGN_TOOL_NO_ACTION;

  if (pika_image_pick_vectors (image,
                               coords->x, coords->y,
                               FUNSCALEX (shell, snap_distance),
                               FUNSCALEY (shell, snap_distance)))
    {
      if (options->align_reference == PIKA_ALIGN_REFERENCE_PICK)
        align_tool->function = ALIGN_TOOL_REF_PICK_PATH;
    }
  else if (pika_display_shell_get_show_guides (shell) &&
           pika_image_pick_guide (image,
                                  coords->x, coords->y,
                                  FUNSCALEX (shell, snap_distance),
                                  FUNSCALEY (shell, snap_distance)))
    {
      if (state == (pika_get_extend_selection_mask () | GDK_MOD1_MASK))
        align_tool->function = ALIGN_TOOL_ALIGN_ADD_GUIDE;
      else if (state == GDK_MOD1_MASK)
        align_tool->function = ALIGN_TOOL_ALIGN_PICK_GUIDE;
      else if (options->align_reference == PIKA_ALIGN_REFERENCE_PICK)
        align_tool->function = ALIGN_TOOL_REF_PICK_GUIDE;
    }
  else if (pika_image_pick_layer_by_bounds (image, coords->x, coords->y))
    {
      if (options->align_reference == PIKA_ALIGN_REFERENCE_PICK)
        align_tool->function = ALIGN_TOOL_REF_PICK_LAYER;
    }
  else
    {
      if (state & GDK_MOD1_MASK)
        align_tool->function = ALIGN_TOOL_ALIGN_IDLE;
      else if (options->align_reference == PIKA_ALIGN_REFERENCE_PICK)
        align_tool->function = ALIGN_TOOL_REF_IDLE;
    }

  pika_align_tool_status_update (tool, display, state, proximity);

  if (! pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool)))
    pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);
}

static void
pika_align_tool_cursor_update (PikaTool         *tool,
                               const PikaCoords *coords,
                               GdkModifierType   state,
                               PikaDisplay      *display)
{
  PikaAlignTool      *align_tool  = PIKA_ALIGN_TOOL (tool);
  PikaToolCursorType  tool_cursor = PIKA_TOOL_CURSOR_NONE;
  PikaCursorModifier  modifier    = PIKA_CURSOR_MODIFIER_NONE;

  switch (align_tool->function)
    {
    case ALIGN_TOOL_REF_IDLE:
    case ALIGN_TOOL_ALIGN_IDLE:
      tool_cursor = PIKA_TOOL_CURSOR_RECT_SELECT;
      break;

    case ALIGN_TOOL_REF_PICK_LAYER:
      tool_cursor = PIKA_TOOL_CURSOR_HAND;
      break;

    case ALIGN_TOOL_ALIGN_ADD_GUIDE:
      modifier = PIKA_CURSOR_MODIFIER_PLUS;
    case ALIGN_TOOL_REF_PICK_GUIDE:
    case ALIGN_TOOL_ALIGN_PICK_GUIDE:
      tool_cursor = PIKA_TOOL_CURSOR_MOVE;
      break;

    case ALIGN_TOOL_REF_PICK_PATH:
      tool_cursor = PIKA_TOOL_CURSOR_PATHS;
      break;

    case ALIGN_TOOL_REF_DRAG_BOX:
    case ALIGN_TOOL_NO_ACTION:
      break;
    }

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

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

const gchar *
pika_align_tool_can_undo (PikaTool    *tool,
                          PikaDisplay *display)
{
  return _("Arrange Objects");
}

static gboolean
pika_align_tool_undo (PikaTool    *tool,
                      PikaDisplay *display)
{
  g_idle_add ((GSourceFunc) pika_align_tool_undo_idle, (gpointer) tool);

  return FALSE;
}

static void
pika_align_tool_status_update (PikaTool        *tool,
                               PikaDisplay     *display,
                               GdkModifierType  state,
                               gboolean         proximity)
{
  PikaAlignTool    *align_tool = PIKA_ALIGN_TOOL (tool);
  gchar            *status     = NULL;
  GdkModifierType   extend_mask;

  extend_mask = pika_get_extend_selection_mask ();

  pika_tool_pop_status (tool, display);

  if (proximity)
    {
      switch (align_tool->function)
        {
        case ALIGN_TOOL_REF_IDLE:
          status = g_strdup (_("Click on a layer, path or guide, "
                               "or Click-Drag to pick a reference"));
          break;
        case ALIGN_TOOL_REF_PICK_LAYER:
          status = g_strdup (_("Click to pick this layer as reference"));
          break;
        case ALIGN_TOOL_REF_PICK_GUIDE:
          status = pika_suggest_modifiers (_("Click to pick this guide as reference"),
                                           GDK_MOD1_MASK & ~state,
                                           NULL, NULL, NULL);
          break;
        case ALIGN_TOOL_REF_PICK_PATH:
          status = g_strdup (_("Click to pick this path as reference"));
          break;

        case ALIGN_TOOL_REF_DRAG_BOX:
          break;

        case ALIGN_TOOL_ALIGN_IDLE:
          status = g_strdup (_("Click on a guide to add it to objects to align, "
                               "click anywhere else to unselect all guides"));
          break;
        case ALIGN_TOOL_ALIGN_PICK_GUIDE:
          status = pika_suggest_modifiers (_("Click to select this guide for alignment"),
                                           extend_mask & ~state,
                                           NULL, NULL, NULL);
          break;
        case ALIGN_TOOL_ALIGN_ADD_GUIDE:
          status = g_strdup (_("Click to add this guide to the list of objects to align"));
          break;

        case ALIGN_TOOL_NO_ACTION:
          break;
        }
    }

  if (status)
    {
      pika_tool_push_status (tool, display, "%s", status);
      g_free (status);
    }
}

static void
pika_align_tool_draw (PikaDrawTool *draw_tool)
{
  PikaAlignTool    *align_tool = PIKA_ALIGN_TOOL (draw_tool);
  PikaAlignOptions *options    = PIKA_ALIGN_TOOL_GET_OPTIONS (align_tool);
  GObject          *reference;
  GList            *objects;
  GList            *iter;
  gint              x, y, w, h;

  /* draw rubber-band rectangle */
  x = MIN (align_tool->x2, align_tool->x1);
  y = MIN (align_tool->y2, align_tool->y1);
  w = MAX (align_tool->x2, align_tool->x1) - x;
  h = MAX (align_tool->y2, align_tool->y1) - y;

  if (w != 0 && h != 0)
    pika_draw_tool_add_rectangle (draw_tool, FALSE, x, y, w, h);

  /* Draw handles on the reference object. */
  reference = pika_align_options_get_reference (options, FALSE);
  if (PIKA_IS_ITEM (reference))
    {
      PikaItem *item = PIKA_ITEM (reference);
      gint      off_x, off_y;

      pika_item_bounds (item, &x, &y, &w, &h);

      pika_item_get_offset (item, &off_x, &off_y);
      x += off_x;
      y += off_y;

      if (pika_align_options_align_contents (options))
        {
          gint shrunk_x;
          gint shrunk_y;

          if (pika_pickable_auto_shrink (PIKA_PICKABLE (reference),
                                         0, 0,
                                         pika_item_get_width  (PIKA_ITEM (reference)),
                                         pika_item_get_height (PIKA_ITEM (reference)),
                                         &shrunk_x, &shrunk_y, &w, &h) == PIKA_AUTO_SHRINK_SHRINK)
            {
              x += shrunk_x;
              y += shrunk_y;
            }
        }

      pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                 x, y,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_HANDLE_ANCHOR_NORTH_WEST);
      pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                 x + w, y,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_HANDLE_ANCHOR_NORTH_EAST);
      pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                 x, y + h,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_HANDLE_ANCHOR_SOUTH_WEST);
      pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                 x + w, y + h,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_HANDLE_ANCHOR_SOUTH_EAST);
    }
  else if (PIKA_IS_GUIDE (reference))
    {
      PikaGuide *guide = PIKA_GUIDE (reference);
      PikaImage *image = pika_display_get_image (draw_tool->display);
      gint       x, y;
      gint       w, h;

      switch (pika_guide_get_orientation (guide))
        {
        case PIKA_ORIENTATION_VERTICAL:
          x = pika_guide_get_position (guide);
          h = pika_image_get_height (image);
          pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                     x, h,
                                     PIKA_TOOL_HANDLE_SIZE_SMALL,
                                     PIKA_TOOL_HANDLE_SIZE_SMALL,
                                     PIKA_HANDLE_ANCHOR_SOUTH);
          pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                     x, 0,
                                     PIKA_TOOL_HANDLE_SIZE_SMALL,
                                     PIKA_TOOL_HANDLE_SIZE_SMALL,
                                     PIKA_HANDLE_ANCHOR_NORTH);
          break;

        case PIKA_ORIENTATION_HORIZONTAL:
          y = pika_guide_get_position (guide);
          w = pika_image_get_width (image);
          pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                     w, y,
                                     PIKA_TOOL_HANDLE_SIZE_SMALL,
                                     PIKA_TOOL_HANDLE_SIZE_SMALL,
                                     PIKA_HANDLE_ANCHOR_EAST);
          pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                     0, y,
                                     PIKA_TOOL_HANDLE_SIZE_SMALL,
                                     PIKA_TOOL_HANDLE_SIZE_SMALL,
                                     PIKA_HANDLE_ANCHOR_WEST);
          break;

        default:
          break;
        }
    }
  else if (PIKA_IS_IMAGE (reference))
    {
      PikaImage *image  = PIKA_IMAGE (reference);
      gint       width  = pika_image_get_width (image);
      gint       height = pika_image_get_height (image);

      pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                 0, 0,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_HANDLE_ANCHOR_NORTH_WEST);
      pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                 width, 0,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_HANDLE_ANCHOR_NORTH_EAST);
      pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                 0, height,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_HANDLE_ANCHOR_SOUTH_WEST);
      pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_FILLED_SQUARE,
                                 width, height,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_TOOL_HANDLE_SIZE_SMALL,
                                 PIKA_HANDLE_ANCHOR_SOUTH_EAST);
    }

  /* Highlight picked guides, similarly to how they are highlighted in Move
   * tool.
   */
  objects = pika_align_options_get_objects (options);
  for (iter = objects; iter; iter = iter->next)
    {
      if (PIKA_IS_GUIDE (iter->data))
        {
          PikaGuide      *guide = iter->data;
          PikaCanvasItem *item;
          PikaGuideStyle  style;

          style = pika_guide_get_style (guide);

          item = pika_draw_tool_add_guide (draw_tool,
                                           pika_guide_get_orientation (guide),
                                           pika_guide_get_position (guide),
                                           style);
          pika_canvas_item_set_highlight (item, TRUE);
        }
    }
}

static void
pika_align_tool_redraw (PikaAlignTool *align_tool)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (align_tool);

  pika_draw_tool_pause (draw_tool);

  if (! pika_draw_tool_is_active (draw_tool) && draw_tool->display)
    pika_draw_tool_start (draw_tool, draw_tool->display);

  pika_draw_tool_resume (draw_tool);
}

static void
pika_align_tool_align (PikaAlignTool     *align_tool,
                       PikaAlignmentType  align_type)
{
  PikaAlignOptions *options = PIKA_ALIGN_TOOL_GET_OPTIONS (align_tool);
  PikaImage        *image;
  GObject          *reference_object = NULL;
  GList            *objects;
  GList            *list;
  gdouble           align_x = 0.0;
  gdouble           align_y = 0.0;

  /* if nothing is selected, just return */
  objects = pika_align_options_get_objects (options);
  if (! objects)
    return;

  image = pika_context_get_image (pika_get_user_context (PIKA_CONTEXT (options)->pika));
  list  = objects;

  if (align_type < PIKA_ARRANGE_HFILL)
    {
      reference_object = pika_align_options_get_reference (options, TRUE);
      if (! reference_object)
        return;
    }

  pika_draw_tool_pause (PIKA_DRAW_TOOL (align_tool));

  pika_align_options_get_pivot (options, &align_x, &align_y);
  pika_image_arrange_objects (image, list,
                              align_x, align_y,
                              reference_object,
                              align_type,
                              pika_align_options_align_contents (options));

  pika_draw_tool_resume (PIKA_DRAW_TOOL (align_tool));

  pika_image_flush (image);
  g_list_free (objects);
}

static gint
pika_align_tool_undo_idle (gpointer data)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (data);
  PikaDisplay  *display   = draw_tool->display;

  /* This makes sure that we properly undraw then redraw the various handles and
   * highlighted guides after undoing.
   */
  pika_draw_tool_stop (draw_tool);
  pika_draw_tool_start (draw_tool, display);

  return FALSE; /* remove idle */
}

static void
pika_align_tool_display_changed (PikaContext   *context,
                                 PikaDisplay   *display,
                                 PikaAlignTool *align_tool)
{
  PikaTool     *tool      = PIKA_TOOL (align_tool);
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (align_tool);

  pika_draw_tool_pause (draw_tool);

  if (display != tool->display)
    pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);

  tool->display = display;

  if (! pika_draw_tool_is_active (draw_tool) && display)
    pika_draw_tool_start (draw_tool, display);

  pika_draw_tool_resume (draw_tool);
}
