/* 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/pikachannel-select.h"
#include "core/pikachannel.h"
#include "core/pikaimage.h"
#include "core/pikalayer-floating-selection.h"
#include "core/pikapickable.h"

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

#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikatoolrectangle.h"

#include "pikaeditselectiontool.h"
#include "pikarectangleoptions.h"
#include "pikarectangleselecttool.h"
#include "pikarectangleselectoptions.h"
#include "pikatoolcontrol.h"

#include "pika-intl.h"


struct _PikaRectangleSelectToolPrivate
{
  PikaChannelOps  operation;    /* remember for use when modifying   */
  gboolean        use_saved_op; /* use operation or get from options */

  gdouble         press_x;
  gdouble         press_y;

  PikaToolWidget *widget;
  PikaToolWidget *grab_widget;
  GList          *bindings;
};


static void     pika_rectangle_select_tool_control        (PikaTool              *tool,
                                                           PikaToolAction         action,
                                                           PikaDisplay           *display);
static void     pika_rectangle_select_tool_button_press   (PikaTool              *tool,
                                                           const PikaCoords      *coords,
                                                           guint32                time,
                                                           GdkModifierType        state,
                                                           PikaButtonPressType    press_type,
                                                           PikaDisplay           *display);
static void     pika_rectangle_select_tool_button_release (PikaTool              *tool,
                                                           const PikaCoords      *coords,
                                                           guint32                time,
                                                           GdkModifierType        state,
                                                           PikaButtonReleaseType  release_type,
                                                           PikaDisplay           *display);
static void     pika_rectangle_select_tool_motion         (PikaTool              *tool,
                                                           const PikaCoords      *coords,
                                                           guint32                time,
                                                           GdkModifierType        state,
                                                           PikaDisplay           *display);
static gboolean pika_rectangle_select_tool_key_press      (PikaTool              *tool,
                                                           GdkEventKey           *kevent,
                                                           PikaDisplay           *display);
static void     pika_rectangle_select_tool_oper_update    (PikaTool              *tool,
                                                           const PikaCoords      *coords,
                                                           GdkModifierType        state,
                                                           gboolean               proximity,
                                                           PikaDisplay           *display);
static void     pika_rectangle_select_tool_cursor_update  (PikaTool              *tool,
                                                           const PikaCoords      *coords,
                                                           GdkModifierType        state,
                                                           PikaDisplay           *display);
static void     pika_rectangle_select_tool_options_notify (PikaTool              *tool,
                                                           PikaToolOptions       *options,
                                                           const GParamSpec      *pspec);

static gboolean pika_rectangle_select_tool_select         (PikaRectangleSelectTool *rect_tool,
                                                           gint                   x,
                                                           gint                   y,
                                                           gint                   w,
                                                           gint                   h);
static void     pika_rectangle_select_tool_real_select    (PikaRectangleSelectTool *rect_tool,
                                                           PikaChannelOps         operation,
                                                           gint                   x,
                                                           gint                   y,
                                                           gint                   w,
                                                           gint                   h);

static void     pika_rectangle_select_tool_rectangle_response
                                                          (PikaToolWidget          *widget,
                                                           gint                     response_id,
                                                           PikaRectangleSelectTool *rect_tool);
static void     pika_rectangle_select_tool_rectangle_change_complete
                                                          (PikaToolWidget          *widget,
                                                           PikaRectangleSelectTool *rect_tool);

static void     pika_rectangle_select_tool_start          (PikaRectangleSelectTool *rect_tool,
                                                           PikaDisplay             *display);
static void     pika_rectangle_select_tool_commit         (PikaRectangleSelectTool *rect_tool);
static void     pika_rectangle_select_tool_halt           (PikaRectangleSelectTool *rect_tool);

static PikaChannelOps
                pika_rectangle_select_tool_get_operation  (PikaRectangleSelectTool *rect_tool);
static void     pika_rectangle_select_tool_update_option_defaults
                                                          (PikaRectangleSelectTool *rect_tool,
                                                           gboolean                 ignore_pending);
static void     pika_rectangle_select_tool_update         (PikaRectangleSelectTool *rect_tool);
static void     pika_rectangle_select_tool_auto_shrink    (PikaRectangleSelectTool *rect_tool);


G_DEFINE_TYPE_WITH_PRIVATE (PikaRectangleSelectTool, pika_rectangle_select_tool,
                            PIKA_TYPE_SELECTION_TOOL)

#define parent_class pika_rectangle_select_tool_parent_class


void
pika_rectangle_select_tool_register (PikaToolRegisterCallback  callback,
                                     gpointer                  data)
{
  (* callback) (PIKA_TYPE_RECTANGLE_SELECT_TOOL,
                PIKA_TYPE_RECTANGLE_SELECT_OPTIONS,
                pika_rectangle_select_options_gui,
                0,
                "pika-rect-select-tool",
                _("Rectangle Select"),
                _("Rectangle Select Tool: Select a rectangular region"),
                N_("_Rectangle Select"), "R",
                NULL, PIKA_HELP_TOOL_RECT_SELECT,
                PIKA_ICON_TOOL_RECT_SELECT,
                data);
}

static void
pika_rectangle_select_tool_class_init (PikaRectangleSelectToolClass *klass)
{
  PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass);

  tool_class->control        = pika_rectangle_select_tool_control;
  tool_class->button_press   = pika_rectangle_select_tool_button_press;
  tool_class->button_release = pika_rectangle_select_tool_button_release;
  tool_class->motion         = pika_rectangle_select_tool_motion;
  tool_class->key_press      = pika_rectangle_select_tool_key_press;
  tool_class->oper_update    = pika_rectangle_select_tool_oper_update;
  tool_class->cursor_update  = pika_rectangle_select_tool_cursor_update;
  tool_class->options_notify = pika_rectangle_select_tool_options_notify;

  klass->select              = pika_rectangle_select_tool_real_select;
}

static void
pika_rectangle_select_tool_init (PikaRectangleSelectTool *rect_tool)
{
  PikaTool *tool = PIKA_TOOL (rect_tool);

  rect_tool->private =
    pika_rectangle_select_tool_get_instance_private (rect_tool);

  pika_tool_control_set_wants_click      (tool->control, TRUE);
  pika_tool_control_set_active_modifiers (tool->control,
                                          PIKA_TOOL_ACTIVE_MODIFIERS_SEPARATE);
  pika_tool_control_set_precision        (tool->control,
                                          PIKA_CURSOR_PRECISION_PIXEL_BORDER);
  pika_tool_control_set_tool_cursor      (tool->control,
                                          PIKA_TOOL_CURSOR_RECT_SELECT);
  pika_tool_control_set_preserve         (tool->control, FALSE);
  pika_tool_control_set_dirty_mask       (tool->control,
                                          PIKA_DIRTY_IMAGE_SIZE |
                                          PIKA_DIRTY_SELECTION);
  pika_tool_control_set_dirty_action     (tool->control,
                                          PIKA_TOOL_ACTION_COMMIT);
}

static void
pika_rectangle_select_tool_control (PikaTool       *tool,
                                    PikaToolAction  action,
                                    PikaDisplay    *display)
{
  PikaRectangleSelectTool *rect_tool = PIKA_RECTANGLE_SELECT_TOOL (tool);

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

    case PIKA_TOOL_ACTION_HALT:
      pika_rectangle_select_tool_halt (rect_tool);
      break;

    case PIKA_TOOL_ACTION_COMMIT:
      pika_rectangle_select_tool_commit (rect_tool);
      break;
    }

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

static void
pika_rectangle_select_tool_button_press (PikaTool            *tool,
                                         const PikaCoords    *coords,
                                         guint32              time,
                                         GdkModifierType      state,
                                         PikaButtonPressType  press_type,
                                         PikaDisplay         *display)
{
  PikaRectangleSelectTool        *rect_tool = PIKA_RECTANGLE_SELECT_TOOL (tool);
  PikaRectangleSelectToolPrivate *private   = rect_tool->private;
  PikaRectangleFunction           function;

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

  if (pika_selection_tool_start_edit (PIKA_SELECTION_TOOL (tool),
                                      display, coords))
    {
      /* In some cases we want to finish the rectangle select tool
       * and hand over responsibility to the selection tool
       */

      gboolean zero_rect = FALSE;

      if (private->widget)
        {
          gdouble x1, y1, x2, y2;

          pika_tool_rectangle_get_public_rect (PIKA_TOOL_RECTANGLE (private->widget),
                                               &x1, &y1, &x2, &y2);
          if (x1 == x2 && y1 == y2)
            zero_rect = TRUE;
        }

      /* Don't commit a zero-size rectangle, it would look like a
       * click to commit() and that could anchor the floating
       * selection or do other evil things. Instead, simply cancel a
       * zero-size rectangle. See bug #796073.
       */
      if (zero_rect)
        pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);
      else
        pika_tool_control (tool, PIKA_TOOL_ACTION_COMMIT, display);

      pika_rectangle_select_tool_update_option_defaults (rect_tool, TRUE);
      return;
    }

  if (! tool->display)
    {
      pika_rectangle_select_tool_start (rect_tool, display);

      pika_tool_widget_hover (private->widget, coords, state, TRUE);

      /* HACK: force CREATING on a newly created rectangle; otherwise,
       * the above binding of properties would cause the rectangle to
       * start with the size from tool options.
       */
      pika_tool_rectangle_set_function (PIKA_TOOL_RECTANGLE (private->widget),
                                        PIKA_TOOL_RECTANGLE_CREATING);
    }

  /* if the shift or ctrl keys are down, we don't want to adjust, we
   * want to create a new rectangle, regardless of pointer loc
   */
  if (state & (pika_get_extend_selection_mask () |
               pika_get_modify_selection_mask ()))
    {
      pika_tool_rectangle_set_function (PIKA_TOOL_RECTANGLE (private->widget),
                                        PIKA_TOOL_RECTANGLE_CREATING);
    }

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

  private->press_x = coords->x;
  private->press_y = coords->y;

  /* if we have an existing rectangle in the current display, then
   * we have already "executed", and need to undo at this point,
   * unless the user has done something in the meantime
   */
  function =
    pika_tool_rectangle_get_function (PIKA_TOOL_RECTANGLE (private->widget));

  if (function == PIKA_TOOL_RECTANGLE_CREATING)
    private->use_saved_op = FALSE;

  pika_selection_tool_start_change (
    PIKA_SELECTION_TOOL (tool),
    function == PIKA_TOOL_RECTANGLE_CREATING,
    pika_rectangle_select_tool_get_operation (rect_tool));

  pika_tool_control_activate (tool->control);
}

static void
pika_rectangle_select_tool_button_release (PikaTool              *tool,
                                           const PikaCoords      *coords,
                                           guint32                time,
                                           GdkModifierType        state,
                                           PikaButtonReleaseType  release_type,
                                           PikaDisplay           *display)
{
  PikaRectangleSelectTool        *rect_tool = PIKA_RECTANGLE_SELECT_TOOL (tool);
  PikaRectangleSelectToolPrivate *priv      = rect_tool->private;

  pika_tool_control_halt (tool->control);

  pika_selection_tool_end_change (PIKA_SELECTION_TOOL (tool),
                                  /* if the user has not moved the mouse,
                                   * cancel the change
                                   */
                                  release_type == PIKA_BUTTON_RELEASE_CLICK ||
                                  release_type == PIKA_BUTTON_RELEASE_CANCEL);

  pika_tool_pop_status (tool, display);

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

static void
pika_rectangle_select_tool_motion (PikaTool         *tool,
                                   const PikaCoords *coords,
                                   guint32           time,
                                   GdkModifierType   state,
                                   PikaDisplay      *display)
{
  PikaRectangleSelectTool        *rect_tool = PIKA_RECTANGLE_SELECT_TOOL (tool);
  PikaRectangleSelectToolPrivate *priv      = rect_tool->private;

  if (priv->grab_widget)
    {
      pika_tool_widget_motion (priv->grab_widget, coords, time, state);
    }
}

static gboolean
pika_rectangle_select_tool_key_press (PikaTool    *tool,
                                      GdkEventKey *kevent,
                                      PikaDisplay *display)
{
  PikaRectangleSelectTool        *rect_tool = PIKA_RECTANGLE_SELECT_TOOL (tool);
  PikaRectangleSelectToolPrivate *priv      = rect_tool->private;

  if (priv->widget && display == tool->display)
    {
      if (pika_tool_widget_key_press (priv->widget, kevent))
        return TRUE;
    }

  return pika_edit_selection_tool_key_press (tool, kevent, display);
}

static void
pika_rectangle_select_tool_oper_update (PikaTool         *tool,
                                        const PikaCoords *coords,
                                        GdkModifierType   state,
                                        gboolean          proximity,
                                        PikaDisplay      *display)
{
  PikaRectangleSelectTool        *rect_tool = PIKA_RECTANGLE_SELECT_TOOL (tool);
  PikaRectangleSelectToolPrivate *priv      = rect_tool->private;

  if (priv->widget && display == tool->display)
    {
      pika_tool_widget_hover (priv->widget, coords, state, proximity);
    }

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

static void
pika_rectangle_select_tool_cursor_update (PikaTool         *tool,
                                          const PikaCoords *coords,
                                          GdkModifierType   state,
                                          PikaDisplay      *display)
{
  PikaRectangleSelectTool        *rect_tool = PIKA_RECTANGLE_SELECT_TOOL (tool);
  PikaRectangleSelectToolPrivate *private   = rect_tool->private;
  PikaCursorType                  cursor    = PIKA_CURSOR_CROSSHAIR_SMALL;
  PikaCursorModifier              modifier  = PIKA_CURSOR_MODIFIER_NONE;

  if (private->widget && display == tool->display)
    {
      pika_tool_widget_get_cursor (private->widget, coords, state,
                                   &cursor, NULL, &modifier);
    }

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

  /* override the previous if shift or ctrl are down */
  if (state & (pika_get_extend_selection_mask () |
               pika_get_modify_selection_mask ()))
    {
      pika_tool_control_set_cursor (tool->control,
                                    PIKA_CURSOR_CROSSHAIR_SMALL);
    }

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

static void
pika_rectangle_select_tool_options_notify (PikaTool         *tool,
                                           PikaToolOptions  *options,
                                           const GParamSpec *pspec)
{
  if (! strcmp (pspec->name, "antialias")      ||
      ! strcmp (pspec->name, "feather")        ||
      ! strcmp (pspec->name, "feather-radius") ||
      ! strcmp (pspec->name, "round-corners")  ||
      ! strcmp (pspec->name, "corner-radius"))
    {
      pika_rectangle_select_tool_update (PIKA_RECTANGLE_SELECT_TOOL (tool));
    }

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

static gboolean
pika_rectangle_select_tool_select (PikaRectangleSelectTool *rect_tool,
                                   gint                     x,
                                   gint                     y,
                                   gint                     w,
                                   gint                     h)
{
  PikaTool       *tool  = PIKA_TOOL (rect_tool);
  PikaImage      *image = pika_display_get_image (tool->display);
  gboolean        rectangle_exists;
  PikaChannelOps  operation;

  pika_tool_pop_status (tool, tool->display);

  rectangle_exists = (x <= pika_image_get_width  (image) &&
                      y <= pika_image_get_height (image) &&
                      x + w >= 0                         &&
                      y + h >= 0                         &&
                      w > 0                              &&
                      h > 0);

  operation = pika_rectangle_select_tool_get_operation (rect_tool);

  /* if rectangle exists, turn it into a selection */
  if (rectangle_exists)
    {
      pika_selection_tool_start_change (PIKA_SELECTION_TOOL (rect_tool),
                                        FALSE,
                                        operation);

      PIKA_RECTANGLE_SELECT_TOOL_GET_CLASS (rect_tool)->select (rect_tool,
                                                                operation,
                                                                x, y, w, h);

      pika_selection_tool_end_change (PIKA_SELECTION_TOOL (rect_tool),
                                      FALSE);
    }

  return rectangle_exists;
}

static void
pika_rectangle_select_tool_real_select (PikaRectangleSelectTool *rect_tool,
                                        PikaChannelOps           operation,
                                        gint                     x,
                                        gint                     y,
                                        gint                     w,
                                        gint                     h)
{
  PikaTool                   *tool    = PIKA_TOOL (rect_tool);
  PikaSelectionOptions       *options = PIKA_SELECTION_TOOL_GET_OPTIONS (tool);
  PikaRectangleSelectOptions *rect_options;
  PikaChannel                *channel;

  rect_options = PIKA_RECTANGLE_SELECT_TOOL_GET_OPTIONS (tool);

  channel = pika_image_get_mask (pika_display_get_image (tool->display));

  if (rect_options->round_corners)
    {
      /* To prevent elliptification of the rectangle,
       * we must cap the corner radius.
       */
      gdouble max    = MIN (w / 2.0, h / 2.0);
      gdouble radius = MIN (rect_options->corner_radius, max);

      pika_channel_select_round_rect (channel,
                                      x, y, w, h,
                                      radius, radius,
                                      operation,
                                      options->antialias,
                                      options->feather,
                                      options->feather_radius,
                                      options->feather_radius,
                                      TRUE);
    }
  else
    {
      pika_channel_select_rectangle (channel,
                                     x, y, w, h,
                                     operation,
                                     options->feather,
                                     options->feather_radius,
                                     options->feather_radius,
                                     TRUE);
    }
}

static void
pika_rectangle_select_tool_rectangle_response (PikaToolWidget          *widget,
                                               gint                     response_id,
                                               PikaRectangleSelectTool *rect_tool)
{
  PikaTool *tool = PIKA_TOOL (rect_tool);

  switch (response_id)
    {
    case PIKA_TOOL_WIDGET_RESPONSE_CONFIRM:
      {
        gdouble x1, y1, x2, y2;

        pika_tool_rectangle_get_public_rect (PIKA_TOOL_RECTANGLE (widget),
                                             &x1, &y1, &x2, &y2);
        if (x1 == x2 && y1 == y2)
          {
            /*  if there are no extents, we got here because of a
             *  click, call commit() directly because we might want to
             *  reconfigure the rectangle and continue, instead of
             *  HALTing it like calling COMMIT would do
             */
            pika_rectangle_select_tool_commit (rect_tool);

            pika_tool_rectangle_get_public_rect (PIKA_TOOL_RECTANGLE (widget),
                                                 &x1, &y1, &x2, &y2);
            if (x1 == x2 && y1 == y2)
              {
                /*  if there still is no rectangle after the
                 *  tool_commit(), the click was outside the selection
                 *  and we HALT to get rid of a zero-size tool widget.
                 */
                pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
              }
         }
        else
          {
            pika_tool_control (tool, PIKA_TOOL_ACTION_COMMIT, tool->display);
          }
      }
      break;

    case PIKA_TOOL_WIDGET_RESPONSE_CANCEL:
      pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
      break;
    }
}

static void
pika_rectangle_select_tool_rectangle_change_complete (PikaToolWidget          *widget,
                                                      PikaRectangleSelectTool *rect_tool)
{
  pika_rectangle_select_tool_update (rect_tool);
}

static void
pika_rectangle_select_tool_start (PikaRectangleSelectTool *rect_tool,
                                  PikaDisplay             *display)
{
  static const gchar *properties[] =
  {
    "highlight",
    "highlight-opacity",
    "guide",
    "round-corners",
    "corner-radius",
    "x",
    "y",
    "width",
    "height",
    "fixed-rule-active",
    "fixed-rule",
    "desired-fixed-width",
    "desired-fixed-height",
    "desired-fixed-size-width",
    "desired-fixed-size-height",
    "aspect-numerator",
    "aspect-denominator",
    "fixed-center"
  };

  PikaTool                       *tool    = PIKA_TOOL (rect_tool);
  PikaRectangleSelectToolPrivate *private = rect_tool->private;
  PikaDisplayShell               *shell   = pika_display_get_shell (display);
  PikaRectangleSelectOptions     *options;
  PikaToolWidget                 *widget;
  gboolean                        draw_ellipse;
  gint                            i;

  options = PIKA_RECTANGLE_SELECT_TOOL_GET_OPTIONS (rect_tool);

  tool->display = display;

  private->widget = widget = pika_tool_rectangle_new (shell);

  draw_ellipse = PIKA_RECTANGLE_SELECT_TOOL_GET_CLASS (rect_tool)->draw_ellipse;

  g_object_set (widget,
                "draw-ellipse", draw_ellipse,
                "status-title", draw_ellipse ? _("Ellipse: ") : _("Rectangle: "),
                NULL);

  pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), widget);

  for (i = 0; i < G_N_ELEMENTS (properties); i++)
    {
      GBinding *binding =
        g_object_bind_property (G_OBJECT (options), properties[i],
                                G_OBJECT (widget),  properties[i],
                                G_BINDING_SYNC_CREATE |
                                G_BINDING_BIDIRECTIONAL);

      private->bindings = g_list_prepend (private->bindings, binding);
    }

  pika_rectangle_options_connect (PIKA_RECTANGLE_OPTIONS (options),
                                  pika_display_get_image (shell->display),
                                  G_CALLBACK (pika_rectangle_select_tool_auto_shrink),
                                  rect_tool);

  g_signal_connect (widget, "response",
                    G_CALLBACK (pika_rectangle_select_tool_rectangle_response),
                    rect_tool);
  g_signal_connect (widget, "change-complete",
                    G_CALLBACK (pika_rectangle_select_tool_rectangle_change_complete),
                    rect_tool);

  pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);
}

/* This function is called if the user clicks and releases the left
 * button without moving it.  There are the things we might want
 * to do here:
 * 1) If there is an existing rectangle and we are inside it, we
 *    convert it into a selection.
 * 2) If there is an existing rectangle and we are outside it, we
 *    clear it.
 * 3) If there is no rectangle and there is a floating selection,
 *    we anchor it.
 * 4) If there is no rectangle and we are inside the selection, we
 *    create a rectangle from the selection bounds.
 * 5) If there is no rectangle and we are outside the selection,
 *    we clear the selection.
 */
static void
pika_rectangle_select_tool_commit (PikaRectangleSelectTool *rect_tool)
{
  PikaTool                       *tool = PIKA_TOOL (rect_tool);
  PikaRectangleSelectToolPrivate *priv = rect_tool->private;

  if (priv->widget)
    {
      gdouble x1, y1, x2, y2;
      gint    w, h;

      pika_tool_rectangle_get_public_rect (PIKA_TOOL_RECTANGLE (priv->widget),
                                           &x1, &y1, &x2, &y2);
      w = x2 - x1;
      h = y2 - y1;

      if (w == 0 && h == 0)
        {
          PikaImage   *image     = pika_display_get_image (tool->display);
          PikaChannel *selection = pika_image_get_mask (image);
          gint         press_x;
          gint         press_y;

          if (pika_image_get_floating_selection (image))
            {
              floating_sel_anchor (pika_image_get_floating_selection (image));
              pika_image_flush (image);

              return;
            }

          press_x = ROUND (priv->press_x);
          press_y = ROUND (priv->press_y);

          /*  if the click was inside the marching ants  */
          if (pika_pickable_get_opacity_at (PIKA_PICKABLE (selection),
                                            press_x, press_y) > 0.5)
            {
              gint x, y, w, h;

              if (pika_item_bounds (PIKA_ITEM (selection), &x, &y, &w, &h))
                {
                  g_object_set (priv->widget,
                                "x1", (gdouble) x,
                                "y1", (gdouble) y,
                                "x2", (gdouble) (x + w),
                                "y2", (gdouble) (y + h),
                                NULL);
                }

              pika_rectangle_select_tool_update (rect_tool);
            }
          else
            {
              PikaChannelOps  operation;

              /* prevent this change from halting the tool */
              pika_tool_control_push_preserve (tool->control, TRUE);

              /* We can conceptually think of a click outside of the
               * selection as adding a 0px selection. Behave intuitively
               * for the current selection mode
               */
              operation = pika_rectangle_select_tool_get_operation (rect_tool);

              switch (operation)
                {
                case PIKA_CHANNEL_OP_REPLACE:
                case PIKA_CHANNEL_OP_INTERSECT:
                  pika_channel_clear (selection, NULL, TRUE);
                  pika_image_flush (image);
                  break;

                case PIKA_CHANNEL_OP_ADD:
                case PIKA_CHANNEL_OP_SUBTRACT:
                default:
                  /* Do nothing */
                  break;
                }

              pika_tool_control_pop_preserve (tool->control);
            }
        }

      pika_rectangle_select_tool_update_option_defaults (rect_tool, FALSE);
    }
}

static void
pika_rectangle_select_tool_halt (PikaRectangleSelectTool *rect_tool)
{
  PikaTool                       *tool = PIKA_TOOL (rect_tool);
  PikaRectangleSelectToolPrivate *priv = rect_tool->private;
  PikaRectangleSelectOptions     *options;

  options = PIKA_RECTANGLE_SELECT_TOOL_GET_OPTIONS (rect_tool);

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

      pika_display_shell_set_highlight (shell, NULL, 0.0);

      pika_rectangle_options_disconnect (PIKA_RECTANGLE_OPTIONS (options),
                                         G_CALLBACK (pika_rectangle_select_tool_auto_shrink),
                                         rect_tool);
    }

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

  /*  disconnect bindings manually so they are really gone *now*, we
   *  might be in the middle of a signal emission that keeps the
   *  widget and its bindings alive.
   */
  g_list_free_full (priv->bindings, (GDestroyNotify) g_object_unref);
  priv->bindings = NULL;

  pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), NULL);
  g_clear_object (&priv->widget);

  tool->display = NULL;

  pika_rectangle_select_tool_update_option_defaults (rect_tool, TRUE);
}

static PikaChannelOps
pika_rectangle_select_tool_get_operation (PikaRectangleSelectTool *rect_tool)
{
  PikaRectangleSelectToolPrivate *priv = rect_tool->private;
  PikaSelectionOptions           *options;

  options = PIKA_SELECTION_TOOL_GET_OPTIONS (rect_tool);

  if (priv->use_saved_op)
    return priv->operation;
  else
    return options->operation;
}

/**
 * pika_rectangle_select_tool_update_option_defaults:
 * @crop_tool:
 * @ignore_pending: %TRUE to ignore any pending crop rectangle.
 *
 * Sets the default Fixed: Aspect ratio and Fixed: Size option
 * properties.
 */
static void
pika_rectangle_select_tool_update_option_defaults (PikaRectangleSelectTool *rect_tool,
                                                   gboolean                 ignore_pending)
{
  PikaRectangleSelectToolPrivate *priv = rect_tool->private;
  PikaTool                       *tool = PIKA_TOOL (rect_tool);
  PikaRectangleOptions           *rect_options;

  rect_options = PIKA_RECTANGLE_OPTIONS (pika_tool_get_options (tool));

  if (priv->widget && ! ignore_pending)
    {
      /* There is a pending rectangle and we should not ignore it, so
       * set default Fixed: Size to the same as the current pending
       * rectangle width/height.
       */

      pika_tool_rectangle_pending_size_set (PIKA_TOOL_RECTANGLE (priv->widget),
                                            G_OBJECT (rect_options),
                                            "default-aspect-numerator",
                                            "default-aspect-denominator");

      g_object_set (G_OBJECT (rect_options),
                    "use-string-current", TRUE,
                    NULL);
    }
  else
    {
      g_object_set (G_OBJECT (rect_options),
                    "default-aspect-numerator",   1.0,
                    "default-aspect-denominator", 1.0,
                    NULL);

      g_object_set (G_OBJECT (rect_options),
                    "use-string-current", FALSE,
                    NULL);
    }
}

static void
pika_rectangle_select_tool_update (PikaRectangleSelectTool *rect_tool)
{
  PikaTool                       *tool = PIKA_TOOL (rect_tool);
  PikaRectangleSelectToolPrivate *priv = rect_tool->private;

  /* prevent change in selection from halting the tool */
  pika_tool_control_push_preserve (tool->control, TRUE);

  if (tool->display && ! pika_tool_control_is_active (tool->control))
    {
      gdouble x1, y1, x2, y2;

      pika_tool_rectangle_get_public_rect (PIKA_TOOL_RECTANGLE (priv->widget),
                                           &x1, &y1, &x2, &y2);

      pika_rectangle_select_tool_select (rect_tool,
                                         x1, y1, x2 - x1, y2 - y1);

      if (! priv->use_saved_op)
        {
          PikaSelectionOptions *options;

          options = PIKA_SELECTION_TOOL_GET_OPTIONS (tool);

          /* remember the operation now in case we modify the rectangle */
          priv->operation    = options->operation;
          priv->use_saved_op = TRUE;
        }
    }

  pika_tool_control_pop_preserve (tool->control);

  pika_rectangle_select_tool_update_option_defaults (rect_tool, FALSE);
}

static void
pika_rectangle_select_tool_auto_shrink (PikaRectangleSelectTool *rect_tool)
{
  PikaRectangleSelectToolPrivate *private = rect_tool->private;
  gboolean                        shrink_merged;

  g_object_get (pika_tool_get_options (PIKA_TOOL (rect_tool)),
                "shrink-merged", &shrink_merged,
                NULL);

  pika_tool_rectangle_auto_shrink (PIKA_TOOL_RECTANGLE (private->widget),
                                   shrink_merged);
}
