/* GIMP - The GNU Image Manipulation Program
 * 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <gtk/gtk.h>

#include "libgimpmath/gimpmath.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "tools-types.h"

#include "base/boundary.h"

#include "core/gimpchannel.h"
#include "core/gimpchannel-select.h"
#include "core/gimplayer-floating-sel.h"
#include "core/gimpimage.h"
#include "core/gimpimage-undo.h"
#include "core/gimppickable.h"
#include "core/gimp-utils.h"
#include "core/gimpundostack.h"

#include "widgets/gimpdialogfactory.h"
#include "widgets/gimphelp-ids.h"
#include "widgets/gimpviewabledialog.h"
#include "widgets/gimpwidgets-utils.h"

#include "display/gimpdisplay.h"
#include "display/gimpdisplayshell.h"
#include "display/gimpdisplayshell-transform.h"
#include "display/gimpdisplayshell-appearance.h"

#include "gimpeditselectiontool.h"
#include "gimpselectiontool.h"
#include "gimpselectionoptions.h"
#include "gimprectangletool.h"
#include "gimprectangleoptions.h"
#include "gimprectangleselecttool.h"
#include "gimprectangleselectoptions.h"
#include "gimptoolcontrol.h"

#include "gimp-intl.h"


typedef struct GimpRectangleSelectToolPrivate
{
  GimpChannelOps     operation;            /* remember for use when modifying   */
  gboolean           use_saved_op;         /* use operation or get from options */
  gboolean           saved_show_selection; /* used to remember existing value   */
  GimpUndo          *undo;
  GimpUndo          *redo;

  gboolean           round_corners;
  gdouble            corner_radius;

  gdouble            press_x;
  gdouble            press_y;
} GimpRectangleSelectToolPrivate;


#define GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE(obj) \
  ((GimpRectangleSelectToolPrivate *) ((GimpRectangleSelectTool *) (obj))->priv)


static void     gimp_rectangle_select_tool_rectangle_tool_iface_init (GimpRectangleToolInterface *iface);

static GObject *gimp_rectangle_select_tool_constructor    (GType                  type,
                                                           guint                  n_params,
                                                           GObjectConstructParam *params);
static void     gimp_rectangle_select_tool_control        (GimpTool              *tool,
                                                           GimpToolAction         action,
                                                           GimpDisplay           *display);
static void     gimp_rectangle_select_tool_button_press   (GimpTool              *tool,
                                                           GimpCoords            *coords,
                                                           guint32                time,
                                                           GdkModifierType        state,
                                                           GimpDisplay           *display);
static void     gimp_rectangle_select_tool_button_release (GimpTool              *tool,
                                                           GimpCoords            *coords,
                                                           guint32                time,
                                                           GdkModifierType        state,
                                                           GimpButtonReleaseType  release_type,
                                                           GimpDisplay           *display);
static void     gimp_rectangle_select_tool_active_modifier_key
                                                          (GimpTool              *tool,
                                                           GdkModifierType        key,
                                                           gboolean               press,
                                                           GdkModifierType        state,
                                                           GimpDisplay           *display);
static gboolean gimp_rectangle_select_tool_key_press      (GimpTool              *tool,
                                                           GdkEventKey           *kevent,
                                                           GimpDisplay           *display);
static void     gimp_rectangle_select_tool_oper_update    (GimpTool              *tool,
                                                           GimpCoords            *coords,
                                                           GdkModifierType        state,
                                                           gboolean               proximity,
                                                           GimpDisplay           *display);
static void     gimp_rectangle_select_tool_cursor_update  (GimpTool              *tool,
                                                           GimpCoords            *coords,
                                                           GdkModifierType        state,
                                                           GimpDisplay           *display);
static void     gimp_rectangle_select_tool_draw           (GimpDrawTool          *draw_tool);
static gboolean gimp_rectangle_select_tool_select         (GimpRectangleTool     *rect_tool,
                                                           gint                   x,
                                                           gint                   y,
                                                           gint                   w,
                                                           gint                   h);
static gboolean gimp_rectangle_select_tool_execute        (GimpRectangleTool     *rect_tool,
                                                           gint                   x,
                                                           gint                   y,
                                                           gint                   w,
                                                           gint                   h);
static void     gimp_rectangle_select_tool_cancel         (GimpRectangleTool     *rect_tool);
static gboolean gimp_rectangle_select_tool_rectangle_change_complete
                                                          (GimpRectangleTool     *rect_tool);
static void     gimp_rectangle_select_tool_real_select    (GimpRectangleSelectTool *rect_sel_tool,
                                                           GimpChannelOps         operation,
                                                           gint                   x,
                                                           gint                   y,
                                                           gint                   w,
                                                           gint                   h);
static void     gimp_rectangle_select_tool_update_option_defaults
                                                          (GimpRectangleSelectTool    *rect_sel_tool,
                                                           gboolean                    ignore_pending);

static void     gimp_rectangle_select_tool_round_corners_notify
                                                          (GimpRectangleSelectOptions *options,
                                                           GParamSpec                 *pspec,
                                                           GimpRectangleSelectTool    *rect_sel_tool);


G_DEFINE_TYPE_WITH_CODE (GimpRectangleSelectTool, gimp_rectangle_select_tool,
                         GIMP_TYPE_SELECTION_TOOL,
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_RECTANGLE_TOOL,
                                                gimp_rectangle_select_tool_rectangle_tool_iface_init))

#define parent_class gimp_rectangle_select_tool_parent_class


void
gimp_rectangle_select_tool_register (GimpToolRegisterCallback  callback,
                                     gpointer                  data)
{
  (* callback) (GIMP_TYPE_RECTANGLE_SELECT_TOOL,
                GIMP_TYPE_RECTANGLE_SELECT_OPTIONS,
                gimp_rectangle_select_options_gui,
                0,
                "gimp-rect-select-tool",
                _("Rectangle Select"),
                _("Rectangle Select Tool: Select a rectangular region"),
                N_("_Rectangle Select"), "R",
                NULL, GIMP_HELP_TOOL_RECT_SELECT,
                GIMP_STOCK_TOOL_RECT_SELECT,
                data);
}

static void
gimp_rectangle_select_tool_class_init (GimpRectangleSelectToolClass *klass)
{
  GObjectClass      *object_class    = G_OBJECT_CLASS (klass);
  GimpToolClass     *tool_class      = GIMP_TOOL_CLASS (klass);
  GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);

  g_type_class_add_private (klass, sizeof (GimpRectangleSelectToolPrivate));

  object_class->constructor       = gimp_rectangle_select_tool_constructor;
  object_class->set_property      = gimp_rectangle_tool_set_property;
  object_class->get_property      = gimp_rectangle_tool_get_property;

  gimp_rectangle_tool_install_properties (object_class);

  tool_class->control             = gimp_rectangle_select_tool_control;
  tool_class->button_press        = gimp_rectangle_select_tool_button_press;
  tool_class->button_release      = gimp_rectangle_select_tool_button_release;
  tool_class->motion              = gimp_rectangle_tool_motion;
  tool_class->key_press           = gimp_rectangle_select_tool_key_press;
  tool_class->active_modifier_key = gimp_rectangle_select_tool_active_modifier_key;
  tool_class->oper_update         = gimp_rectangle_select_tool_oper_update;
  tool_class->cursor_update       = gimp_rectangle_select_tool_cursor_update;

  draw_tool_class->draw           = gimp_rectangle_select_tool_draw;

  klass->select                   = gimp_rectangle_select_tool_real_select;
}

static void
gimp_rectangle_select_tool_rectangle_tool_iface_init (GimpRectangleToolInterface *iface)
{
  iface->execute                   = gimp_rectangle_select_tool_execute;
  iface->cancel                    = gimp_rectangle_select_tool_cancel;
  iface->rectangle_change_complete = gimp_rectangle_select_tool_rectangle_change_complete;
}

static void
gimp_rectangle_select_tool_init (GimpRectangleSelectTool *rect_sel_tool)
{
  GimpTool                       *tool = GIMP_TOOL (rect_sel_tool);
  GimpRectangleSelectToolPrivate *priv;

  gimp_rectangle_tool_init (GIMP_RECTANGLE_TOOL (rect_sel_tool));

  rect_sel_tool->priv = G_TYPE_INSTANCE_GET_PRIVATE (rect_sel_tool,
                                                     GIMP_TYPE_RECTANGLE_SELECT_TOOL,
                                                     GimpRectangleSelectToolPrivate);

  priv = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  gimp_tool_control_set_wants_click (tool->control, TRUE);
  gimp_tool_control_set_tool_cursor (tool->control,
                                     GIMP_TOOL_CURSOR_RECT_SELECT);
  gimp_tool_control_set_dirty_mask  (tool->control,
                                     GIMP_DIRTY_IMAGE_SIZE |
                                     GIMP_DIRTY_SELECTION);

  priv->undo    = NULL;
  priv->redo    = NULL;

  priv->press_x = 0.0;
  priv->press_y = 0.0;
}

static GObject *
gimp_rectangle_select_tool_constructor (GType                  type,
                                        guint                  n_params,
                                        GObjectConstructParam *params)
{
  GObject                         *object;
  GimpRectangleSelectTool         *rect_sel_tool;
  GimpRectangleSelectOptions      *options;
  GimpRectangleSelectToolPrivate  *priv;

  object = G_OBJECT_CLASS (parent_class)->constructor (type, n_params, params);

  gimp_rectangle_tool_constructor (object);

  rect_sel_tool = GIMP_RECTANGLE_SELECT_TOOL (object);
  options       = GIMP_RECTANGLE_SELECT_TOOL_GET_OPTIONS (rect_sel_tool);
  priv          = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  priv->round_corners = options->round_corners;
  priv->corner_radius = options->corner_radius;

  g_signal_connect_object (options, "notify::round-corners",
                           G_CALLBACK (gimp_rectangle_select_tool_round_corners_notify),
                           object, 0);
  g_signal_connect_object (options, "notify::corner-radius",
                           G_CALLBACK (gimp_rectangle_select_tool_round_corners_notify),
                           object, 0);

  gimp_rectangle_select_tool_update_option_defaults (rect_sel_tool, FALSE);

  return object;
}

static void
gimp_rectangle_select_tool_control (GimpTool       *tool,
                                    GimpToolAction  action,
                                    GimpDisplay    *display)
{
  gimp_rectangle_tool_control (tool, action, display);

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

static void
gimp_rectangle_select_tool_draw (GimpDrawTool *draw_tool)
{
  GimpRectangleSelectTool        *rect_sel_tool;
  GimpRectangleSelectToolPrivate *priv;

  rect_sel_tool = GIMP_RECTANGLE_SELECT_TOOL (draw_tool);
  priv          = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  gimp_rectangle_tool_draw (draw_tool);

  if (priv->round_corners)
    {
      gint    x1, y1, x2, y2;
      gdouble radius;
      gint    square_size;

      g_object_get (rect_sel_tool,
                    "x1", &x1,
                    "y1", &y1,
                    "x2", &x2,
                    "y2", &y2,
                    NULL);

      radius = MIN (priv->corner_radius,
                    MIN ((x2 - x1) / 2.0, (y2 - y1) / 2.0));

      square_size = (int) (radius * 2);

      gimp_draw_tool_draw_arc (draw_tool, FALSE,
                               x1, y1,
                               square_size, square_size,
                               90 * 64,  90 * 64,
                               FALSE);

      gimp_draw_tool_draw_arc (draw_tool, FALSE,
                               x2 - square_size, y1,
                               square_size, square_size,
                               0,        90 * 64,
                               FALSE);

      gimp_draw_tool_draw_arc (draw_tool, FALSE,
                               x2 - square_size, y2 - square_size,
                               square_size, square_size,
                               270 * 64, 90 * 64,
                               FALSE);

      gimp_draw_tool_draw_arc (draw_tool, FALSE,
                               x1, y2 - square_size,
                               square_size, square_size,
                               180 * 64, 90 * 64,
                               FALSE);
    }
}

static gboolean
gimp_rectangle_select_tool_delegate_button_press (GimpRectangleSelectTool *rect_sel_tool,
                                                  GimpCoords              *coords,
                                                  GimpDisplay             *display)
{
  GimpTool    *tool                   = GIMP_TOOL (rect_sel_tool);
  gboolean     button_press_delegated = FALSE;
  GimpDisplay *old_display            = tool->display;

  tool->display = display;
  if (! gimp_tool_control_is_active (tool->control))
    {
      gimp_tool_control_activate (tool->control);
    }

  button_press_delegated
    = gimp_selection_tool_start_edit (GIMP_SELECTION_TOOL (tool),
                                      coords);

  if (gimp_tool_control_is_active (tool->control))
    {
      gimp_tool_control_halt (tool->control);
    }
  tool->display = old_display;

  return button_press_delegated;
}

static void
gimp_rectangle_select_tool_button_press (GimpTool        *tool,
                                         GimpCoords      *coords,
                                         guint32          time,
                                         GdkModifierType  state,
                                         GimpDisplay     *display)
{
  GimpRectangleTool              *rectangle;
  GimpRectangleSelectTool        *rect_sel_tool;
  GimpDisplayShell               *shell;
  GimpRectangleSelectToolPrivate *priv;
  GimpRectangleFunction           function;

  rectangle     = GIMP_RECTANGLE_TOOL (tool);
  rect_sel_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
  shell         = GIMP_DISPLAY_SHELL (display->shell);
  priv          = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  if (tool->display && display != tool->display)
    {
      gimp_rectangle_tool_cancel (GIMP_RECTANGLE_TOOL (tool));
    }

  if (gimp_rectangle_select_tool_delegate_button_press (rect_sel_tool,
                                                        coords,
                                                        display))
    {
      /* In some cases we want to finnish the rectangle select tool
       * and hand over responsability to the selection tool
       */
      gimp_rectangle_tool_execute (rectangle);
      gimp_rectangle_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
      gimp_rectangle_select_tool_update_option_defaults (rect_sel_tool,
                                                         TRUE);
      return;
    }

  function = gimp_rectangle_tool_get_function (rectangle);

  priv->saved_show_selection = gimp_display_shell_get_show_selection (shell);

  /* 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 & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
    gimp_rectangle_tool_set_function (rectangle, GIMP_RECTANGLE_TOOL_CREATING);

  gimp_rectangle_tool_button_press (tool, coords, time, state, display);

  priv->press_x = coords->x;
  priv->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 = gimp_rectangle_tool_get_function (rectangle);

  if (function == GIMP_RECTANGLE_TOOL_CREATING)
    {
      priv->use_saved_op = FALSE;
    }
  else
    {
      GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
      GimpImage            *image   = tool->display->image;
      GimpUndo             *undo;
      GimpChannelOps        operation;

      if (priv->use_saved_op)
        operation = priv->operation;
      else
        operation = options->operation;

      undo = gimp_undo_stack_peek (image->undo_stack);

      if (undo && priv->undo == undo)
        {
          /* prevent this change from halting the tool */
          gimp_tool_control_set_preserve (tool->control, TRUE);

          gimp_image_undo (image);

          gimp_tool_control_set_preserve (tool->control, FALSE);

          /* we will need to redo if the user cancels or executes */
          priv->redo = gimp_undo_stack_peek (image->redo_stack);
        }

      /* if the operation is "Replace", turn off the marching ants,
         because they are confusing */
      if (operation == GIMP_CHANNEL_OP_REPLACE)
        gimp_display_shell_set_show_selection (shell, FALSE);
    }

  priv->undo = NULL;
}

static void
gimp_rectangle_select_tool_button_release (GimpTool              *tool,
                                           GimpCoords            *coords,
                                           guint32                time,
                                           GdkModifierType        state,
                                           GimpButtonReleaseType  release_type,
                                           GimpDisplay           *display)
{
  GimpRectangleSelectTool        *rect_sel_tool;
  GimpRectangleSelectToolPrivate *priv;

  rect_sel_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
  priv          = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  gimp_tool_pop_status (tool, display);
  gimp_display_shell_set_show_selection (GIMP_DISPLAY_SHELL (display->shell),
                                         priv->saved_show_selection);

  /*
   * if the user has not moved the mouse, we need to redo the operation
   * that was undone on button press.
   */
  if (release_type == GIMP_BUTTON_RELEASE_CLICK)
    {
      GimpImage *image = tool->display->image;
      GimpUndo  *redo  = gimp_undo_stack_peek (image->redo_stack);

      if (redo && priv->redo == redo)
        {
          /* prevent this from halting the tool */
          gimp_tool_control_set_preserve (tool->control, TRUE);

          gimp_image_redo (image);
          priv->redo = NULL;

          gimp_tool_control_set_preserve (tool->control, FALSE);
        }
    }

  gimp_rectangle_tool_button_release (tool, coords, time, state, release_type,
                                      display);

  if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
    {
      if (priv->redo)
        {
          /* prevent this from halting the tool */
          gimp_tool_control_set_preserve (tool->control, TRUE);

          gimp_image_redo (tool->display->image);

          gimp_tool_control_set_preserve (tool->control, FALSE);
        }

      priv->use_saved_op = TRUE;  /* is this correct? */
    }

  priv->redo = NULL;
}

static void
gimp_rectangle_select_tool_active_modifier_key (GimpTool        *tool,
                                                GdkModifierType  key,
                                                gboolean         press,
                                                GdkModifierType  state,
                                                GimpDisplay     *display)
{
  GIMP_TOOL_CLASS (parent_class)->active_modifier_key (tool, key, press, state,
                                                       display);

  gimp_rectangle_tool_active_modifier_key (tool, key, press, state, display);
}

static gboolean
gimp_rectangle_select_tool_key_press (GimpTool    *tool,
                                      GdkEventKey *kevent,
                                      GimpDisplay *display)
{
  return (gimp_rectangle_tool_key_press (tool, kevent, display) ||
          gimp_edit_selection_tool_key_press (tool, kevent, display));
}

static void
gimp_rectangle_select_tool_oper_update (GimpTool        *tool,
                                        GimpCoords      *coords,
                                        GdkModifierType  state,
                                        gboolean         proximity,
                                        GimpDisplay     *display)
{
  GimpRectangleFunction function;

  gimp_rectangle_tool_oper_update (tool, coords, state, proximity, display);

  function = gimp_rectangle_tool_get_function (GIMP_RECTANGLE_TOOL (tool));

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

static void
gimp_rectangle_select_tool_cursor_update (GimpTool        *tool,
                                          GimpCoords      *coords,
                                          GdkModifierType  state,
                                          GimpDisplay     *display)
{
  gimp_rectangle_tool_cursor_update (tool, coords, state, display);

  /* override the previous if shift or ctrl are down */
  if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
    gimp_tool_control_set_cursor (tool->control, GIMP_CURSOR_CROSSHAIR_SMALL);

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


static gboolean
gimp_rectangle_select_tool_select (GimpRectangleTool *rectangle,
                                   gint               x,
                                   gint               y,
                                   gint               w,
                                   gint               h)
{
  GimpTool                       *tool;
  GimpRectangleSelectTool        *rect_sel_tool;
  GimpSelectionOptions           *options;
  GimpImage                      *image;
  GimpRectangleSelectToolPrivate *priv;
  gboolean                        rectangle_exists;
  GimpChannelOps                  operation;

  tool          = GIMP_TOOL (rectangle);
  rect_sel_tool = GIMP_RECTANGLE_SELECT_TOOL (rectangle);
  options       = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
  priv          = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  image         = tool->display->image;

  gimp_tool_pop_status (tool, tool->display);

  rectangle_exists = (x <= gimp_image_get_width  (image) &&
                      y <= gimp_image_get_height (image) &&
                      x + w >= 0                         &&
                      y + h >= 0                         &&
                      w > 0                              &&
                      h > 0);

  if (priv->use_saved_op)
    operation = priv->operation;
  else
    operation = options->operation;

  /* if rectangle exists, turn it into a selection */
  if (rectangle_exists)
    GIMP_RECTANGLE_SELECT_TOOL_GET_CLASS (rect_sel_tool)->select (rect_sel_tool,
                                                                  operation,
                                                                  x, y, w, h);

  return rectangle_exists;
}

static void
gimp_rectangle_select_tool_real_select (GimpRectangleSelectTool *rect_sel_tool,
                                        GimpChannelOps           operation,
                                        gint                     x,
                                        gint                     y,
                                        gint                     w,
                                        gint                     h)
{
  GimpTool                   *tool    = GIMP_TOOL (rect_sel_tool);
  GimpSelectionOptions       *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
  GimpRectangleSelectOptions *rect_select_options;
  GimpChannel                *channel;

  rect_select_options = GIMP_RECTANGLE_SELECT_TOOL_GET_OPTIONS (tool);

  channel = gimp_image_get_mask (tool->display->image);

  if (rect_select_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_select_options->corner_radius, max);

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

/**
 * gimp_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
gimp_rectangle_select_tool_update_option_defaults (GimpRectangleSelectTool *rect_sel_tool,
                                                   gboolean                 ignore_pending)
{
  GimpTool             *tool;
  GimpRectangleTool    *rectangle_tool;
  GimpRectangleOptions *rectangle_options;

  tool              = GIMP_TOOL (rect_sel_tool);
  rectangle_tool    = GIMP_RECTANGLE_TOOL (tool);
  rectangle_options = GIMP_RECTANGLE_TOOL_GET_OPTIONS (rectangle_tool);

  if (tool->display != NULL && !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.
       */

      gimp_rectangle_tool_pending_size_set (rectangle_tool,
                                            G_OBJECT (rectangle_options),
                                            "default-aspect-numerator",
                                            "default-aspect-denominator");

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

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

/*
 * 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 gboolean
gimp_rectangle_select_tool_execute (GimpRectangleTool *rectangle,
                                    gint               x,
                                    gint               y,
                                    gint               w,
                                    gint               h)
{
  GimpTool                       *tool = GIMP_TOOL (rectangle);
  GimpRectangleSelectTool        *rect_sel_tool;
  GimpRectangleSelectToolPrivate *priv;

  rect_sel_tool = GIMP_RECTANGLE_SELECT_TOOL (rectangle);
  priv          = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  if (w == 0 && h == 0 && tool->display != NULL)
    {
      GimpImage   *image     = tool->display->image;
      GimpChannel *selection = gimp_image_get_mask (image);
      gint         pressx;
      gint         pressy;

      if (gimp_image_floating_sel (image))
        {
          floating_sel_anchor (gimp_image_floating_sel (image));
          gimp_image_flush (image);
          return TRUE;
        }

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

      /*  if the click was inside the marching ants  */
      if (gimp_pickable_get_opacity_at (GIMP_PICKABLE (selection),
                                        pressx, pressy) > BOUNDARY_HALF_WAY)
        {
          gint x1, y1, x2, y2;

          if (gimp_channel_bounds (selection, &x1, &y1, &x2, &y2))
            {
              g_object_set (rectangle,
                            "x1", x1,
                            "y1", y1,
                            "x2", x2,
                            "y2", y2,
                            NULL);
            }

          gimp_rectangle_tool_set_function (rectangle,
                                            GIMP_RECTANGLE_TOOL_MOVING);

          return FALSE;
        }
      else
        {
          GimpTool *tool = GIMP_TOOL (rectangle);

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

          /* otherwise clear the selection */
          gimp_channel_clear (selection, NULL, TRUE);
          gimp_image_flush (image);

          gimp_tool_control_set_preserve (tool->control, FALSE);
        }
    }

  gimp_rectangle_select_tool_update_option_defaults (rect_sel_tool, FALSE);

  /* Reset the automatic undo/redo mechanism */
  priv->undo = NULL;
  priv->redo = NULL;

  return TRUE;
}

static void
gimp_rectangle_select_tool_cancel (GimpRectangleTool *rectangle)
{
  GimpTool                       *tool;
  GimpRectangleSelectTool        *rect_sel_tool;
  GimpRectangleSelectToolPrivate *priv;

  tool          = GIMP_TOOL (rectangle);
  rect_sel_tool = GIMP_RECTANGLE_SELECT_TOOL (rectangle);
  priv          = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  if (tool->display)
    {
      GimpImage *image = tool->display->image;
      GimpUndo  *undo;

      /* 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
       */
      undo  = gimp_undo_stack_peek (image->undo_stack);

      if (undo && priv->undo == undo)
        {
          /* prevent this change from halting the tool */
          gimp_tool_control_set_preserve (tool->control, TRUE);

          gimp_image_undo (image);
          gimp_image_flush (image);

          gimp_tool_control_set_preserve (tool->control, FALSE);
        }
    }

  gimp_rectangle_select_tool_update_option_defaults (rect_sel_tool, TRUE);

  priv->undo = NULL;
  priv->redo = NULL;
}

static gboolean
gimp_rectangle_select_tool_rectangle_change_complete (GimpRectangleTool *rectangle)
{
  GimpTool                       *tool;
  GimpRectangleSelectTool        *rect_sel_tool;
  GimpRectangleSelectToolPrivate *priv;

  tool          = GIMP_TOOL (rectangle);
  rect_sel_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
  priv          = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

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

  if (tool->display && ! gimp_tool_control_is_active (tool->control))
    {
      GimpImage          *image = tool->display->image;
      GimpUndo           *undo;
      gint                x1, y1, x2, y2;

      /* if we got here via button release, we have already undone the
       * previous operation.  But if we got here by some other means,
       * we need to undo it now.
       */
      undo = gimp_undo_stack_peek (image->undo_stack);

      if (undo && priv->undo == undo)
        {
          gimp_image_undo (image);
          priv->undo = NULL;
        }

      g_object_get (rectangle,
                    "x1", &x1,
                    "y1", &y1,
                    "x2", &x2,
                    "y2", &y2,
                    NULL);

      if (gimp_rectangle_select_tool_select (rectangle, x1, y1, x2 - x1, y2 - y1))
        {
          /* save the undo that we got when executing, but only if
           * we actually selected something
           */
          priv->undo = gimp_undo_stack_peek (image->undo_stack);
          priv->redo = NULL;
        }

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

          options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);

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

      gimp_image_flush (image);
    }

  gimp_tool_control_set_preserve (tool->control, FALSE);

  gimp_rectangle_select_tool_update_option_defaults (rect_sel_tool, FALSE);

  return TRUE;
}

static void
gimp_rectangle_select_tool_round_corners_notify (GimpRectangleSelectOptions *options,
                                                 GParamSpec                 *pspec,
                                                 GimpRectangleSelectTool    *rect_sel_tool)
{
  GimpDrawTool                   *draw_tool = GIMP_DRAW_TOOL (rect_sel_tool);
  GimpRectangleTool              *rect_tool = GIMP_RECTANGLE_TOOL (rect_sel_tool);
  GimpRectangleSelectToolPrivate *priv;

  priv = GIMP_RECTANGLE_SELECT_TOOL_GET_PRIVATE (rect_sel_tool);

  gimp_draw_tool_pause (draw_tool);

  priv->round_corners = options->round_corners;
  priv->corner_radius = options->corner_radius;

  gimp_rectangle_select_tool_rectangle_change_complete (rect_tool);

  gimp_draw_tool_resume (draw_tool);
}
