PIKApp/app/display/pikatoolfocus.c

1214 lines
38 KiB
C

/* 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
*
* pikatoolfocus.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 "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "display-types.h"
#include "widgets/pikawidgets-utils.h"
#include "pikacanvasgroup.h"
#include "pikacanvashandle.h"
#include "pikacanvaslimit.h"
#include "pikadisplayshell.h"
#include "pikadisplayshell-transform.h"
#include "pikadisplayshell-utils.h"
#include "pikatoolfocus.h"
#include "pika-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
{
PikaCanvasItem *item;
GtkOrientation orientation;
PikaVector2 dir;
} PikaToolFocusHandle;
typedef struct
{
PikaCanvasGroup *group;
PikaCanvasItem *item;
gint n_handles;
PikaToolFocusHandle handles[4];
} PikaToolFocusLimit;
struct _PikaToolFocusPrivate
{
PikaLimitType type;
gdouble x;
gdouble y;
gdouble radius;
gdouble aspect_ratio;
gdouble angle;
gdouble inner_limit;
gdouble midpoint;
PikaToolFocusLimit limits[N_LIMITS];
Hover hover;
gint hover_limit;
gint hover_handle;
PikaCanvasItem *hover_item;
PikaCanvasItem *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 pika_tool_focus_constructed (GObject *object);
static void pika_tool_focus_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_tool_focus_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_tool_focus_changed (PikaToolWidget *widget);
static gint pika_tool_focus_button_press (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonPressType press_type);
static void pika_tool_focus_button_release (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonReleaseType release_type);
static void pika_tool_focus_motion (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state);
static PikaHit pika_tool_focus_hit (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity);
static void pika_tool_focus_hover (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity);
static void pika_tool_focus_leave_notify (PikaToolWidget *widget);
static void pika_tool_focus_motion_modifier (PikaToolWidget *widget,
GdkModifierType key,
gboolean press,
GdkModifierType state);
static void pika_tool_focus_hover_modifier (PikaToolWidget *widget,
GdkModifierType key,
gboolean press,
GdkModifierType state);
static gboolean pika_tool_focus_get_cursor (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
PikaCursorType *cursor,
PikaToolCursorType *tool_cursor,
PikaCursorModifier *modifier);
static void pika_tool_focus_update_hover (PikaToolFocus *focus,
const PikaCoords *coords,
gboolean proximity);
static void pika_tool_focus_update_highlight (PikaToolFocus *focus);
static void pika_tool_focus_update_status (PikaToolFocus *focus,
GdkModifierType state);
static void pika_tool_focus_save (PikaToolFocus *focus);
static void pika_tool_focus_restore (PikaToolFocus *focus);
G_DEFINE_TYPE_WITH_PRIVATE (PikaToolFocus, pika_tool_focus,
PIKA_TYPE_TOOL_WIDGET)
#define parent_class pika_tool_focus_parent_class
/* private functions */
static void
pika_tool_focus_class_init (PikaToolFocusClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass);
object_class->constructed = pika_tool_focus_constructed;
object_class->set_property = pika_tool_focus_set_property;
object_class->get_property = pika_tool_focus_get_property;
widget_class->changed = pika_tool_focus_changed;
widget_class->button_press = pika_tool_focus_button_press;
widget_class->button_release = pika_tool_focus_button_release;
widget_class->motion = pika_tool_focus_motion;
widget_class->hit = pika_tool_focus_hit;
widget_class->hover = pika_tool_focus_hover;
widget_class->leave_notify = pika_tool_focus_leave_notify;
widget_class->motion_modifier = pika_tool_focus_motion_modifier;
widget_class->hover_modifier = pika_tool_focus_hover_modifier;
widget_class->get_cursor = pika_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,
PIKA_TYPE_LIMIT_TYPE,
PIKA_LIMIT_CIRCLE,
PIKA_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,
PIKA_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,
PIKA_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,
PIKA_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,
PIKA_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,
PIKA_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,
PIKA_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,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
pika_tool_focus_init (PikaToolFocus *focus)
{
PikaToolFocusPrivate *priv;
priv = pika_tool_focus_get_instance_private (focus);
focus->priv = priv;
priv->hover = HOVER_NONE;
}
static void
pika_tool_focus_constructed (GObject *object)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (object);
PikaToolWidget *widget = PIKA_TOOL_WIDGET (object);
PikaToolFocusPrivate *priv = focus->priv;
gint i;
G_OBJECT_CLASS (parent_class)->constructed (object);
for (i = N_LIMITS - 1; i >= 0; i--)
{
priv->limits[i].group = pika_tool_widget_add_group (widget);
pika_tool_widget_push_group (widget, priv->limits[i].group);
priv->limits[i].item = pika_tool_widget_add_limit (
widget,
PIKA_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 = pika_tool_widget_add_handle (
widget,
PIKA_HANDLE_FILLED_CIRCLE,
0.0, 0.0, HANDLE_SIZE, HANDLE_SIZE,
PIKA_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);
}
}
pika_tool_widget_pop_group (widget);
}
pika_tool_focus_changed (widget);
}
static void
pika_tool_focus_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (object);
PikaToolFocusPrivate *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
pika_tool_focus_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (object);
PikaToolFocusPrivate *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
pika_tool_focus_changed (PikaToolWidget *widget)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
PikaToolFocusPrivate *priv = focus->priv;
gint i;
for (i = 0; i < N_LIMITS; i++)
{
pika_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;
pika_canvas_limit_get_radii (PIKA_CANVAS_LIMIT (priv->limits[i].item),
&rx, &ry);
for (j = 0; j < priv->limits[i].n_handles; j++)
{
PikaVector2 p = priv->limits[i].handles[j].dir;
gdouble r;
p.x *= rx;
p.y *= ry;
pika_vector2_rotate (&p, -priv->angle);
p.x += priv->x;
p.y += priv->y;
pika_canvas_handle_set_position (priv->limits[i].handles[j].item,
p.x, p.y);
r = pika_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++)
{
pika_canvas_item_set_visible (priv->limits[i].handles[j].item,
priv->type != PIKA_LIMIT_HORIZONTAL &&
priv->type != PIKA_LIMIT_VERTICAL &&
max_r >= 1.5 * HANDLE_SIZE);
}
pika_canvas_item_end_change (priv->limits[i].item);
}
}
static gint
pika_tool_focus_button_press (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonPressType press_type)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
PikaToolFocusPrivate *priv = focus->priv;
if (press_type == PIKA_BUTTON_PRESS_NORMAL)
{
pika_tool_focus_save (focus);
priv->orig_x = coords->x;
priv->orig_y = coords->y;
return TRUE;
}
return FALSE;
}
static void
pika_tool_focus_button_release (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonReleaseType release_type)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
if (release_type == PIKA_BUTTON_RELEASE_CANCEL)
pika_tool_focus_restore (focus);
}
static void
pika_tool_focus_motion (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
PikaToolFocusPrivate *priv = focus->priv;
PikaDisplayShell *shell = pika_tool_widget_get_shell (widget);
gboolean extend;
gboolean constrain;
extend = state & pika_get_extend_selection_mask ();
constrain = state & pika_get_constrain_behavior_mask ();
switch (priv->hover)
{
case HOVER_NONE:
break;
case HOVER_LIMIT:
{
PikaCanvasItem *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;
pika_canvas_limit_center_point (PIKA_CANVAS_LIMIT (limit),
x, y,
&cx, &cy);
if (pika_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 = pika_canvas_limit_boundary_radius (PIKA_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:
{
PikaToolFocusHandle *handle;
PikaVector2 e;
PikaVector2 s;
PikaVector2 p;
gdouble rx, ry;
gdouble r;
handle = &priv->limits[priv->hover_limit].handles[priv->hover_handle];
e = handle->dir;
pika_vector2_rotate (&e, -priv->angle);
s = e;
pika_canvas_limit_get_radii (
PIKA_CANVAS_LIMIT (priv->limits[priv->hover_limit].item),
&rx, &ry);
if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
pika_vector2_mul (&s, ry);
else
pika_vector2_mul (&s, rx);
p.x = coords->x - priv->x;
p.y = coords->y - priv->y;
r = pika_vector2_inner_product (&p, &e);
r = MAX (r, 0.0);
p = e;
pika_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 (pika_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 = pika_display_shell_constrain_angle (shell, angle, 12);
g_object_set (focus,
"angle", angle,
NULL);
}
break;
}
}
static PikaHit
pika_tool_focus_hit (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
PikaToolFocusPrivate *priv = focus->priv;
pika_tool_focus_update_hover (focus, coords, proximity);
switch (priv->hover)
{
case HOVER_NONE:
return PIKA_HIT_NONE;
case HOVER_LIMIT:
case HOVER_HANDLE:
return PIKA_HIT_DIRECT;
case HOVER_MOVE:
case HOVER_ROTATE:
return PIKA_HIT_INDIRECT;
}
g_return_val_if_reached (PIKA_HIT_NONE);
}
static void
pika_tool_focus_hover (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
pika_tool_focus_update_hover (focus, coords, proximity);
pika_tool_focus_update_highlight (focus);
pika_tool_focus_update_status (focus, state);
}
static void
pika_tool_focus_leave_notify (PikaToolWidget *widget)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
pika_tool_focus_update_hover (focus, NULL, FALSE);
pika_tool_focus_update_highlight (focus);
PIKA_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
}
static void
pika_tool_focus_motion_modifier (PikaToolWidget *widget,
GdkModifierType key,
gboolean press,
GdkModifierType state)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
pika_tool_focus_update_status (focus, state);
}
static void
pika_tool_focus_hover_modifier (PikaToolWidget *widget,
GdkModifierType key,
gboolean press,
GdkModifierType state)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
pika_tool_focus_update_status (focus, state);
}
static gboolean
pika_tool_focus_get_cursor (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
PikaCursorType *cursor,
PikaToolCursorType *tool_cursor,
PikaCursorModifier *modifier)
{
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
PikaToolFocusPrivate *priv = focus->priv;
switch (priv->hover)
{
case HOVER_NONE:
return FALSE;
case HOVER_LIMIT:
case HOVER_HANDLE:
*modifier = PIKA_CURSOR_MODIFIER_RESIZE;
return TRUE;
case HOVER_MOVE:
*modifier = PIKA_CURSOR_MODIFIER_MOVE;
return TRUE;
case HOVER_ROTATE:
*modifier = PIKA_CURSOR_MODIFIER_ROTATE;
return TRUE;
}
g_return_val_if_reached (FALSE);
}
static void
pika_tool_focus_update_hover (PikaToolFocus *focus,
const PikaCoords *coords,
gboolean proximity)
{
PikaToolFocusPrivate *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++)
{
PikaCanvasItem *handle = priv->limits[i].handles[j].item;
if (pika_canvas_item_get_visible (handle))
{
gdouble x, y;
gdouble dist;
g_object_get (handle,
"x", &x,
"y", &y,
NULL);
dist = pika_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 ( pika_canvas_limit_is_inside (
PIKA_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
coords->x, coords->y) &&
! pika_canvas_limit_is_inside (
PIKA_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
coords->x, coords->y))
{
if (pika_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 (! pika_canvas_limit_is_inside (
PIKA_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
coords->x, coords->y))
{
if (pika_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 (pika_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 (pika_canvas_limit_is_inside (
PIKA_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
coords->x, coords->y))
{
priv->hover = HOVER_MOVE;
}
else
{
priv->hover = HOVER_ROTATE;
}
}
static void
pika_tool_focus_update_highlight (PikaToolFocus *focus)
{
PikaToolWidget *widget = PIKA_TOOL_WIDGET (focus);
PikaToolFocusPrivate *priv = focus->priv;
gint i;
if (priv->hover_item == priv->last_hover_item)
return;
if (priv->last_hover_item)
pika_canvas_item_set_highlight (priv->last_hover_item, FALSE);
#define RAISE_ITEM(item) \
G_STMT_START \
{ \
g_object_ref (item); \
\
pika_tool_widget_remove_item (widget, item); \
pika_tool_widget_add_item (widget, item); \
\
g_object_unref (item); \
} \
G_STMT_END
for (i = N_LIMITS - 1; i >= 0; i--)
RAISE_ITEM (PIKA_CANVAS_ITEM (priv->limits[i].group));
if (priv->hover_item)
{
pika_canvas_item_set_highlight (priv->hover_item, TRUE);
RAISE_ITEM (PIKA_CANVAS_ITEM (priv->limits[priv->hover_limit].group));
}
#undef RAISE_ITEM
priv->last_hover_item = priv->hover_item;
}
static void
pika_tool_focus_update_status (PikaToolFocus *focus,
GdkModifierType state)
{
PikaToolFocusPrivate *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 & pika_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 |= pika_get_extend_selection_mask ();
}
else
{
message = _("Click-Drag to resize the focus");
}
break;
case HOVER_HANDLE:
if (! (state & pika_get_extend_selection_mask ()))
{
message = _("Click-Drag to change the aspect ratio");
extend_selection_format = _("%s to resize the focus");
state_mask |= pika_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 |= pika_get_constrain_behavior_mask ();
break;
}
status = pika_suggest_modifiers (message,
~state & state_mask,
extend_selection_format,
toggle_behavior_format,
NULL);
pika_tool_widget_set_status (PIKA_TOOL_WIDGET (focus), status);
g_free (status);
}
static void
pika_tool_focus_save (PikaToolFocus *focus)
{
PikaToolFocusPrivate *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
pika_tool_focus_restore (PikaToolFocus *focus)
{
PikaToolFocusPrivate *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 */
PikaToolWidget *
pika_tool_focus_new (PikaDisplayShell *shell)
{
g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), NULL);
return g_object_new (PIKA_TYPE_TOOL_FOCUS,
"shell", shell,
NULL);
}