/* 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 . */ #include "config.h" #include #include #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); }