summaryrefslogtreecommitdiffstats
path: root/app/display/gimptoolfocus.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/display/gimptoolfocus.c1209
1 files changed, 1209 insertions, 0 deletions
diff --git a/app/display/gimptoolfocus.c b/app/display/gimptoolfocus.c
new file mode 100644
index 0000000..a607f06
--- /dev/null
+++ b/app/display/gimptoolfocus.c
@@ -0,0 +1,1209 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolfocus.c
+ * Copyright (C) 2020 Ell
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvasgroup.h"
+#include "gimpcanvashandle.h"
+#include "gimpcanvaslimit.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpdisplayshell-utils.h"
+#include "gimptoolfocus.h"
+
+#include "gimp-intl.h"
+
+
+#define HANDLE_SIZE 12.0
+#define SNAP_DISTANCE 12.0
+
+#define EPSILON 1e-6
+
+
+enum
+{
+ PROP_0,
+ PROP_TYPE,
+ PROP_X,
+ PROP_Y,
+ PROP_RADIUS,
+ PROP_ASPECT_RATIO,
+ PROP_ANGLE,
+ PROP_INNER_LIMIT,
+ PROP_MIDPOINT
+};
+
+enum
+{
+ LIMIT_OUTER,
+ LIMIT_INNER,
+ LIMIT_MIDDLE,
+
+ N_LIMITS
+};
+
+
+typedef enum
+{
+ HOVER_NONE,
+ HOVER_LIMIT,
+ HOVER_HANDLE,
+ HOVER_MOVE,
+ HOVER_ROTATE
+} Hover;
+
+
+typedef struct
+{
+ GimpCanvasItem *item;
+
+ GtkOrientation orientation;
+ GimpVector2 dir;
+} GimpToolFocusHandle;
+
+typedef struct
+{
+ GimpCanvasGroup *group;
+ GimpCanvasItem *item;
+
+ gint n_handles;
+ GimpToolFocusHandle handles[4];
+} GimpToolFocusLimit;
+
+struct _GimpToolFocusPrivate
+{
+ GimpLimitType type;
+
+ gdouble x;
+ gdouble y;
+ gdouble radius;
+ gdouble aspect_ratio;
+ gdouble angle;
+
+ gdouble inner_limit;
+ gdouble midpoint;
+
+ GimpToolFocusLimit limits[N_LIMITS];
+
+ Hover hover;
+ gint hover_limit;
+ gint hover_handle;
+ GimpCanvasItem *hover_item;
+
+ GimpCanvasItem *last_hover_item;
+
+ gdouble saved_x;
+ gdouble saved_y;
+ gdouble saved_radius;
+ gdouble saved_aspect_ratio;
+ gdouble saved_angle;
+
+ gdouble saved_inner_limit;
+ gdouble saved_midpoint;
+
+ gdouble orig_x;
+ gdouble orig_y;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_focus_constructed (GObject *object);
+static void gimp_tool_focus_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_focus_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_focus_changed (GimpToolWidget *widget);
+static gint gimp_tool_focus_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_focus_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_focus_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_focus_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_focus_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_focus_leave_notify (GimpToolWidget *widget);
+static void gimp_tool_focus_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static void gimp_tool_focus_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_focus_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_focus_update_hover (GimpToolFocus *focus,
+ const GimpCoords *coords,
+ gboolean proximity);
+
+static void gimp_tool_focus_update_highlight (GimpToolFocus *focus);
+static void gimp_tool_focus_update_status (GimpToolFocus *focus,
+ GdkModifierType state);
+
+static void gimp_tool_focus_save (GimpToolFocus *focus);
+static void gimp_tool_focus_restore (GimpToolFocus *focus);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolFocus, gimp_tool_focus,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_focus_parent_class
+
+
+/* private functions */
+
+static void
+gimp_tool_focus_class_init (GimpToolFocusClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_focus_constructed;
+ object_class->set_property = gimp_tool_focus_set_property;
+ object_class->get_property = gimp_tool_focus_get_property;
+
+ widget_class->changed = gimp_tool_focus_changed;
+ widget_class->button_press = gimp_tool_focus_button_press;
+ widget_class->button_release = gimp_tool_focus_button_release;
+ widget_class->motion = gimp_tool_focus_motion;
+ widget_class->hit = gimp_tool_focus_hit;
+ widget_class->hover = gimp_tool_focus_hover;
+ widget_class->leave_notify = gimp_tool_focus_leave_notify;
+ widget_class->motion_modifier = gimp_tool_focus_motion_modifier;
+ widget_class->hover_modifier = gimp_tool_focus_hover_modifier;
+ widget_class->get_cursor = gimp_tool_focus_get_cursor;
+ widget_class->update_on_scale = TRUE;
+
+ g_object_class_install_property (object_class, PROP_TYPE,
+ g_param_spec_enum ("type", NULL, NULL,
+ GIMP_TYPE_LIMIT_TYPE,
+ GIMP_LIMIT_CIRCLE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_RADIUS,
+ g_param_spec_double ("radius", NULL, NULL,
+ 0.0,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ASPECT_RATIO,
+ g_param_spec_double ("aspect-ratio", NULL, NULL,
+ -1.0,
+ +1.0,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ANGLE,
+ g_param_spec_double ("angle", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_INNER_LIMIT,
+ g_param_spec_double ("inner-limit", NULL, NULL,
+ 0.0,
+ 1.0,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_MIDPOINT,
+ g_param_spec_double ("midpoint", NULL, NULL,
+ 0.0,
+ 1.0,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_focus_init (GimpToolFocus *focus)
+{
+ GimpToolFocusPrivate *priv;
+
+ priv = gimp_tool_focus_get_instance_private (focus);
+
+ focus->priv = priv;
+
+ priv->hover = HOVER_NONE;
+}
+
+static void
+gimp_tool_focus_constructed (GObject *object)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolFocusPrivate *priv = focus->priv;
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ for (i = N_LIMITS - 1; i >= 0; i--)
+ {
+ priv->limits[i].group = gimp_tool_widget_add_group (widget);
+
+ gimp_tool_widget_push_group (widget, priv->limits[i].group);
+
+ priv->limits[i].item = gimp_tool_widget_add_limit (
+ widget,
+ GIMP_LIMIT_CIRCLE,
+ 0.0, 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ /* dashed = */ i == LIMIT_MIDDLE);
+
+ if (i == LIMIT_OUTER || i == LIMIT_INNER)
+ {
+ gint j;
+
+ priv->limits[i].n_handles = 4;
+
+ for (j = priv->limits[i].n_handles - 1; j >= 0; j--)
+ {
+ priv->limits[i].handles[j].item = gimp_tool_widget_add_handle (
+ widget,
+ GIMP_HANDLE_FILLED_CIRCLE,
+ 0.0, 0.0, HANDLE_SIZE, HANDLE_SIZE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ priv->limits[i].handles[j].orientation =
+ j % 2 == 0 ? GTK_ORIENTATION_HORIZONTAL :
+ GTK_ORIENTATION_VERTICAL;
+
+ priv->limits[i].handles[j].dir.x = cos (j * G_PI / 2.0);
+ priv->limits[i].handles[j].dir.y = sin (j * G_PI / 2.0);
+ }
+ }
+
+ gimp_tool_widget_pop_group (widget);
+ }
+
+ gimp_tool_focus_changed (widget);
+}
+
+static void
+gimp_tool_focus_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (object);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ priv->type = g_value_get_enum (value);
+ break;
+
+ case PROP_X:
+ priv->x = g_value_get_double (value);
+ break;
+
+ case PROP_Y:
+ priv->y = g_value_get_double (value);
+ break;
+
+ case PROP_RADIUS:
+ priv->radius = g_value_get_double (value);
+ break;
+
+ case PROP_ASPECT_RATIO:
+ priv->aspect_ratio = g_value_get_double (value);
+ break;
+
+ case PROP_ANGLE:
+ priv->angle = g_value_get_double (value);
+ break;
+
+ case PROP_INNER_LIMIT:
+ priv->inner_limit = g_value_get_double (value);
+ break;
+
+ case PROP_MIDPOINT:
+ priv->midpoint = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_focus_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (object);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ g_value_set_enum (value, priv->type);
+ break;
+
+ case PROP_X:
+ g_value_set_double (value, priv->x);
+ break;
+
+ case PROP_Y:
+ g_value_set_double (value, priv->y);
+ break;
+
+ case PROP_RADIUS:
+ g_value_set_double (value, priv->radius);
+ break;
+
+ case PROP_ASPECT_RATIO:
+ g_value_set_double (value, priv->aspect_ratio);
+ break;
+
+ case PROP_ANGLE:
+ g_value_set_double (value, priv->angle);
+ break;
+
+ case PROP_INNER_LIMIT:
+ g_value_set_double (value, priv->inner_limit);
+ break;
+
+ case PROP_MIDPOINT:
+ g_value_set_double (value, priv->midpoint);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_focus_changed (GimpToolWidget *widget)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+ gint i;
+
+ for (i = 0; i < N_LIMITS; i++)
+ {
+ gimp_canvas_item_begin_change (priv->limits[i].item);
+
+ g_object_set (priv->limits[i].item,
+ "type", priv->type,
+ "x", priv->x,
+ "y", priv->y,
+ "aspect-ratio", priv->aspect_ratio,
+ "angle", priv->angle,
+ NULL);
+ }
+
+ g_object_set (priv->limits[LIMIT_OUTER].item,
+ "radius", priv->radius,
+ NULL);
+
+ g_object_set (priv->limits[LIMIT_INNER].item,
+ "radius", priv->radius * priv->inner_limit,
+ NULL);
+
+ g_object_set (priv->limits[LIMIT_MIDDLE].item,
+ "radius", priv->radius * (priv->inner_limit +
+ (1.0 - priv->inner_limit) *
+ priv->midpoint),
+ NULL);
+
+ for (i = 0; i < N_LIMITS; i++)
+ {
+ gdouble rx, ry;
+ gdouble max_r = 0.0;
+ gint j;
+
+ gimp_canvas_limit_get_radii (GIMP_CANVAS_LIMIT (priv->limits[i].item),
+ &rx, &ry);
+
+ for (j = 0; j < priv->limits[i].n_handles; j++)
+ {
+ GimpVector2 p = priv->limits[i].handles[j].dir;
+ gdouble r;
+
+ p.x *= rx;
+ p.y *= ry;
+
+ gimp_vector2_rotate (&p, -priv->angle);
+
+ p.x += priv->x;
+ p.y += priv->y;
+
+ gimp_canvas_handle_set_position (priv->limits[i].handles[j].item,
+ p.x, p.y);
+
+ r = gimp_canvas_item_transform_distance (
+ priv->limits[i].handles[j].item,
+ priv->x, priv->y,
+ p.x, p.y);
+
+ max_r = MAX (max_r, r);
+ }
+
+ for (j = 0; j < priv->limits[i].n_handles; j++)
+ {
+ gimp_canvas_item_set_visible (priv->limits[i].handles[j].item,
+ priv->type != GIMP_LIMIT_HORIZONTAL &&
+ priv->type != GIMP_LIMIT_VERTICAL &&
+ max_r >= 1.5 * HANDLE_SIZE);
+ }
+
+ gimp_canvas_item_end_change (priv->limits[i].item);
+ }
+}
+
+static gint
+gimp_tool_focus_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ {
+ gimp_tool_focus_save (focus);
+
+ priv->orig_x = coords->x;
+ priv->orig_y = coords->y;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_tool_focus_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ gimp_tool_focus_restore (focus);
+}
+
+static void
+gimp_tool_focus_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ gboolean extend;
+ gboolean constrain;
+
+ extend = state & gimp_get_extend_selection_mask ();
+ constrain = state & gimp_get_constrain_behavior_mask ();
+
+ switch (priv->hover)
+ {
+ case HOVER_NONE:
+ break;
+
+ case HOVER_LIMIT:
+ {
+ GimpCanvasItem *limit = priv->limits[priv->hover_limit].item;
+ gdouble radius;
+ gdouble outer_radius;
+ gdouble inner_radius;
+ gdouble x, y;
+ gdouble cx, cy;
+
+ x = coords->x;
+ y = coords->y;
+
+ gimp_canvas_limit_center_point (GIMP_CANVAS_LIMIT (limit),
+ x, y,
+ &cx, &cy);
+
+ if (gimp_canvas_item_transform_distance (limit,
+ x, y,
+ cx, cy) <= SNAP_DISTANCE)
+ {
+ x = cx;
+ y = cy;
+ }
+
+ if (fabs (fabs (priv->aspect_ratio) - 1.0) <= EPSILON)
+ {
+ if (priv->radius <= EPSILON)
+ {
+ g_object_set (focus,
+ "aspect-ratio", 0.0,
+ NULL);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ radius = gimp_canvas_limit_boundary_radius (GIMP_CANVAS_LIMIT (limit),
+ x, y);
+
+ outer_radius = priv->radius;
+ inner_radius = priv->radius * priv->inner_limit;
+
+ switch (priv->hover_limit)
+ {
+ case LIMIT_OUTER:
+ {
+ outer_radius = radius;
+
+ if (extend)
+ inner_radius = priv->inner_limit * radius;
+ else
+ outer_radius = MAX (outer_radius, inner_radius);
+ }
+ break;
+
+ case LIMIT_INNER:
+ {
+ inner_radius = radius;
+
+ if (extend)
+ {
+ if (priv->inner_limit > EPSILON)
+ outer_radius = inner_radius / priv->inner_limit;
+ else
+ inner_radius = 0.0;
+ }
+ else
+ {
+ inner_radius = MIN (inner_radius, outer_radius);
+ }
+ }
+ break;
+
+ case LIMIT_MIDDLE:
+ {
+ if (extend)
+ {
+ if (priv->inner_limit > EPSILON || priv->midpoint > EPSILON)
+ {
+ outer_radius = radius / (priv->inner_limit +
+ (1.0 - priv->inner_limit) *
+ priv->midpoint);
+ inner_radius = priv->inner_limit * outer_radius;
+ }
+ else
+ {
+ radius = 0.0;
+ }
+ }
+ else
+ {
+ radius = CLAMP (radius, inner_radius, outer_radius);
+ }
+
+ if (fabs (outer_radius - inner_radius) > EPSILON)
+ {
+ g_object_set (focus,
+ "midpoint", MAX ((radius - inner_radius) /
+ (outer_radius - inner_radius),
+ 0.0),
+ NULL);
+ }
+ }
+ break;
+ }
+
+ g_object_set (focus,
+ "radius", outer_radius,
+ NULL);
+
+ if (outer_radius > EPSILON)
+ {
+ g_object_set (focus,
+ "inner-limit", inner_radius / outer_radius,
+ NULL);
+ }
+ }
+ break;
+
+ case HOVER_HANDLE:
+ {
+ GimpToolFocusHandle *handle;
+ GimpVector2 e;
+ GimpVector2 s;
+ GimpVector2 p;
+ gdouble rx, ry;
+ gdouble r;
+
+ handle = &priv->limits[priv->hover_limit].handles[priv->hover_handle];
+
+ e = handle->dir;
+
+ gimp_vector2_rotate (&e, -priv->angle);
+
+ s = e;
+
+ gimp_canvas_limit_get_radii (
+ GIMP_CANVAS_LIMIT (priv->limits[priv->hover_limit].item),
+ &rx, &ry);
+
+ if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+ gimp_vector2_mul (&s, ry);
+ else
+ gimp_vector2_mul (&s, rx);
+
+ p.x = coords->x - priv->x;
+ p.y = coords->y - priv->y;
+
+ r = gimp_vector2_inner_product (&p, &e);
+ r = MAX (r, 0.0);
+
+ p = e;
+
+ gimp_vector2_mul (&p, r);
+
+ if (extend)
+ {
+ if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (rx <= EPSILON && ry > EPSILON)
+ break;
+
+ ry = r * (1.0 - priv->aspect_ratio);
+ }
+ else
+ {
+ if (ry <= EPSILON && rx > EPSILON)
+ break;
+
+ rx = r * (1.0 + priv->aspect_ratio);
+ }
+ }
+ else
+ {
+ if (gimp_canvas_item_transform_distance (
+ priv->limits[priv->hover_limit].item,
+ s.x, s.y,
+ p.x, p.y) <= SNAP_DISTANCE * 0.75)
+ {
+ if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+ r = ry;
+ else
+ r = rx;
+ }
+ }
+
+ if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+ rx = r;
+ else
+ ry = r;
+
+ r = MAX (rx, ry);
+
+ if (priv->hover_limit == LIMIT_INNER)
+ r /= priv->inner_limit;
+
+ g_object_set (focus,
+ "radius", r,
+ NULL);
+
+ if (! extend)
+ {
+ gdouble aspect_ratio;
+
+ if (fabs (rx - ry) <= EPSILON)
+ aspect_ratio = 0.0;
+ else if (rx > ry)
+ aspect_ratio = 1.0 - ry / rx;
+ else
+ aspect_ratio = rx / ry - 1.0;
+
+ g_object_set (focus,
+ "aspect-ratio", aspect_ratio,
+ NULL);
+ }
+ }
+ break;
+
+ case HOVER_MOVE:
+ g_object_set (focus,
+ "x", priv->saved_x + (coords->x - priv->orig_x),
+ "y", priv->saved_y + (coords->y - priv->orig_y),
+ NULL);
+ break;
+
+ case HOVER_ROTATE:
+ {
+ gdouble angle;
+ gdouble orig_angle;
+
+ angle = atan2 (coords->y - priv->y, coords->x - priv->x);
+ orig_angle = atan2 (priv->orig_y - priv->y, priv->orig_x - priv->x);
+
+ angle = priv->saved_angle + (angle - orig_angle);
+
+ if (constrain)
+ angle = gimp_display_shell_constrain_angle (shell, angle, 12);
+
+ g_object_set (focus,
+ "angle", angle,
+ NULL);
+ }
+ break;
+ }
+}
+
+static GimpHit
+gimp_tool_focus_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ gimp_tool_focus_update_hover (focus, coords, proximity);
+
+ switch (priv->hover)
+ {
+ case HOVER_NONE:
+ return GIMP_HIT_NONE;
+
+ case HOVER_LIMIT:
+ case HOVER_HANDLE:
+ return GIMP_HIT_DIRECT;
+
+ case HOVER_MOVE:
+ case HOVER_ROTATE:
+ return GIMP_HIT_INDIRECT;
+ }
+
+ g_return_val_if_reached (GIMP_HIT_NONE);
+}
+
+static void
+gimp_tool_focus_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ gimp_tool_focus_update_hover (focus, coords, proximity);
+
+ gimp_tool_focus_update_highlight (focus);
+ gimp_tool_focus_update_status (focus, state);
+}
+
+static void
+gimp_tool_focus_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ gimp_tool_focus_update_hover (focus, NULL, FALSE);
+
+ gimp_tool_focus_update_highlight (focus);
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static void
+gimp_tool_focus_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ gimp_tool_focus_update_status (focus, state);
+}
+
+static void
+gimp_tool_focus_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ gimp_tool_focus_update_status (focus, state);
+}
+
+static gboolean
+gimp_tool_focus_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ switch (priv->hover)
+ {
+ case HOVER_NONE:
+ return FALSE;
+
+ case HOVER_LIMIT:
+ case HOVER_HANDLE:
+ *modifier = GIMP_CURSOR_MODIFIER_RESIZE;
+ return TRUE;
+
+ case HOVER_MOVE:
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ return TRUE;
+
+ case HOVER_ROTATE:
+ *modifier = GIMP_CURSOR_MODIFIER_ROTATE;
+ return TRUE;
+ }
+
+ g_return_val_if_reached (FALSE);
+}
+
+static void
+gimp_tool_focus_update_hover (GimpToolFocus *focus,
+ const GimpCoords *coords,
+ gboolean proximity)
+{
+ GimpToolFocusPrivate *priv = focus->priv;
+ gdouble min_handle_dist = HANDLE_SIZE;
+ gint i;
+
+ priv->hover = HOVER_NONE;
+ priv->hover_item = NULL;
+
+ if (! proximity)
+ return;
+
+ for (i = 0; i < N_LIMITS; i++)
+ {
+ gint j;
+
+ for (j = 0; j < priv->limits[i].n_handles; j++)
+ {
+ GimpCanvasItem *handle = priv->limits[i].handles[j].item;
+
+ if (gimp_canvas_item_get_visible (handle))
+ {
+ gdouble x, y;
+ gdouble dist;
+
+ g_object_get (handle,
+ "x", &x,
+ "y", &y,
+ NULL);
+
+ dist = gimp_canvas_item_transform_distance (handle,
+ x, y,
+ coords->x, coords->y);
+
+ if (dist < min_handle_dist)
+ {
+ min_handle_dist = dist;
+
+ priv->hover = HOVER_HANDLE;
+ priv->hover_limit = i;
+ priv->hover_handle = j;
+ priv->hover_item = handle;
+ }
+ }
+ }
+ }
+
+ if (priv->hover != HOVER_NONE)
+ return;
+
+ if ( gimp_canvas_limit_is_inside (
+ GIMP_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
+ coords->x, coords->y) &&
+ ! gimp_canvas_limit_is_inside (
+ GIMP_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
+ coords->x, coords->y))
+ {
+ if (gimp_canvas_item_hit (priv->limits[LIMIT_MIDDLE].item,
+ coords->x, coords->y))
+ {
+ priv->hover = HOVER_LIMIT;
+ priv->hover_limit = LIMIT_MIDDLE;
+ priv->hover_item = priv->limits[LIMIT_MIDDLE].item;
+
+ return;
+ }
+ }
+
+ if (! gimp_canvas_limit_is_inside (
+ GIMP_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
+ coords->x, coords->y))
+ {
+ if (gimp_canvas_item_hit (priv->limits[LIMIT_OUTER].item,
+ coords->x, coords->y))
+ {
+ priv->hover = HOVER_LIMIT;
+ priv->hover_limit = LIMIT_OUTER;
+ priv->hover_item = priv->limits[LIMIT_OUTER].item;
+
+ return;
+ }
+ }
+
+ if (gimp_canvas_item_hit (priv->limits[LIMIT_INNER].item,
+ coords->x, coords->y))
+ {
+ priv->hover = HOVER_LIMIT;
+ priv->hover_limit = LIMIT_INNER;
+ priv->hover_item = priv->limits[LIMIT_INNER].item;
+
+ return;
+ }
+
+ if (gimp_canvas_limit_is_inside (
+ GIMP_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
+ coords->x, coords->y))
+ {
+ priv->hover = HOVER_MOVE;
+ }
+ else
+ {
+ priv->hover = HOVER_ROTATE;
+ }
+}
+
+static void
+gimp_tool_focus_update_highlight (GimpToolFocus *focus)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (focus);
+ GimpToolFocusPrivate *priv = focus->priv;
+ gint i;
+
+ if (priv->hover_item == priv->last_hover_item)
+ return;
+
+ if (priv->last_hover_item)
+ gimp_canvas_item_set_highlight (priv->last_hover_item, FALSE);
+
+ #define RAISE_ITEM(item) \
+ G_STMT_START \
+ { \
+ g_object_ref (item); \
+ \
+ gimp_tool_widget_remove_item (widget, item); \
+ gimp_tool_widget_add_item (widget, item); \
+ \
+ g_object_unref (item); \
+ } \
+ G_STMT_END
+
+ for (i = N_LIMITS - 1; i >= 0; i--)
+ RAISE_ITEM (GIMP_CANVAS_ITEM (priv->limits[i].group));
+
+ if (priv->hover_item)
+ {
+ gimp_canvas_item_set_highlight (priv->hover_item, TRUE);
+
+ RAISE_ITEM (GIMP_CANVAS_ITEM (priv->limits[priv->hover_limit].group));
+ }
+
+ #undef RAISE_ITEM
+
+ priv->last_hover_item = priv->hover_item;
+}
+
+static void
+gimp_tool_focus_update_status (GimpToolFocus *focus,
+ GdkModifierType state)
+{
+ GimpToolFocusPrivate *priv = focus->priv;
+ GdkModifierType state_mask = 0;
+ const gchar *message = NULL;
+ const gchar *extend_selection_format = NULL;
+ const gchar *toggle_behavior_format = NULL;
+ gchar *status;
+
+ switch (priv->hover)
+ {
+ case HOVER_NONE:
+ break;
+
+ case HOVER_LIMIT:
+ if (! (state & gimp_get_extend_selection_mask ()))
+ {
+ if (priv->hover_limit == LIMIT_MIDDLE)
+ message = _("Click-Drag to change the midpoint");
+ else
+ message = _("Click-Drag to resize the limit");
+
+ extend_selection_format = _("%s to resize the focus");
+ state_mask |= gimp_get_extend_selection_mask ();
+ }
+ else
+ {
+ message = _("Click-Drag to resize the focus");
+ }
+ break;
+
+ case HOVER_HANDLE:
+ if (! (state & gimp_get_extend_selection_mask ()))
+ {
+ message = _("Click-Drag to change the aspect ratio");
+ extend_selection_format = _("%s to resize the focus");
+ state_mask |= gimp_get_extend_selection_mask ();
+ }
+ else
+ {
+ message = _("Click-Drag to resize the focus");
+ }
+ break;
+
+ case HOVER_MOVE:
+ message = _("Click-Drag to move the focus");
+ break;
+
+ case HOVER_ROTATE:
+ message = _("Click-Drag to rotate the focus");
+ toggle_behavior_format = _("%s for constrained angles");
+ state_mask |= gimp_get_constrain_behavior_mask ();
+ break;
+ }
+
+ status = gimp_suggest_modifiers (message,
+ ~state & state_mask,
+ extend_selection_format,
+ toggle_behavior_format,
+ NULL);
+
+ gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (focus), status);
+
+ g_free (status);
+}
+
+static void
+gimp_tool_focus_save (GimpToolFocus *focus)
+{
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ priv->saved_x = priv->x;
+ priv->saved_y = priv->y;
+ priv->saved_radius = priv->radius;
+ priv->saved_aspect_ratio = priv->aspect_ratio;
+ priv->saved_angle = priv->angle;
+
+ priv->saved_inner_limit = priv->inner_limit;
+ priv->saved_midpoint = priv->midpoint;
+}
+
+static void
+gimp_tool_focus_restore (GimpToolFocus *focus)
+{
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ g_object_set (focus,
+ "x", priv->saved_x,
+ "y", priv->saved_y,
+ "radius", priv->saved_radius,
+ "aspect-ratio", priv->saved_aspect_ratio,
+ "angle", priv->saved_angle,
+
+ "inner_limit", priv->saved_inner_limit,
+ "midpoint", priv->saved_midpoint,
+
+ NULL);
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_focus_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_FOCUS,
+ "shell", shell,
+ NULL);
+}