1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimptoolrectangle.c
5 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
6 *
7 * Based on GimpRectangleTool
8 * Copyright (C) 2007 Martin Nordholts
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
24 #include "config.h"
25
26 #include <gegl.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29
30 #include "libgimpbase/gimpbase.h"
31 #include "libgimpmath/gimpmath.h"
32
33 #include "display-types.h"
34
35 #include "core/gimp.h"
36 #include "core/gimpcontext.h"
37 #include "core/gimpimage.h"
38 #include "core/gimpitem.h"
39 #include "core/gimpmarshal.h"
40 #include "core/gimppickable.h"
41 #include "core/gimppickable-auto-shrink.h"
42
43 #include "widgets/gimpwidgets-utils.h"
44
45 #include "gimpcanvasarc.h"
46 #include "gimpcanvascorner.h"
47 #include "gimpcanvashandle.h"
48 #include "gimpcanvasitem-utils.h"
49 #include "gimpcanvasrectangle.h"
50 #include "gimpcanvasrectangleguides.h"
51 #include "gimpdisplay.h"
52 #include "gimpdisplayshell.h"
53 #include "gimpdisplayshell-scroll.h"
54 #include "gimptoolrectangle.h"
55
56 #include "gimp-intl.h"
57
58
59 /* speed of key movement */
60 #define ARROW_VELOCITY 25
61
62 #define MAX_HANDLE_SIZE 50
63 #define MIN_HANDLE_SIZE 15
64 #define NARROW_MODE_HANDLE_SIZE 15
65 #define NARROW_MODE_THRESHOLD 45
66
67
68 enum
69 {
70 PROP_0,
71 PROP_X1,
72 PROP_Y1,
73 PROP_X2,
74 PROP_Y2,
75 PROP_CONSTRAINT,
76 PROP_PRECISION,
77 PROP_NARROW_MODE,
78 PROP_FORCE_NARROW_MODE,
79 PROP_DRAW_ELLIPSE,
80 PROP_ROUND_CORNERS,
81 PROP_CORNER_RADIUS,
82 PROP_STATUS_TITLE,
83
84 PROP_HIGHLIGHT,
85 PROP_HIGHLIGHT_OPACITY,
86 PROP_GUIDE,
87 PROP_X,
88 PROP_Y,
89 PROP_WIDTH,
90 PROP_HEIGHT,
91 PROP_FIXED_RULE_ACTIVE,
92 PROP_FIXED_RULE,
93 PROP_DESIRED_FIXED_WIDTH,
94 PROP_DESIRED_FIXED_HEIGHT,
95 PROP_DESIRED_FIXED_SIZE_WIDTH,
96 PROP_DESIRED_FIXED_SIZE_HEIGHT,
97 PROP_ASPECT_NUMERATOR,
98 PROP_ASPECT_DENOMINATOR,
99 PROP_FIXED_CENTER
100 };
101
102 enum
103 {
104 CHANGE_COMPLETE,
105 LAST_SIGNAL
106 };
107
108 typedef enum
109 {
110 CLAMPED_NONE = 0,
111 CLAMPED_LEFT = 1 << 0,
112 CLAMPED_RIGHT = 1 << 1,
113 CLAMPED_TOP = 1 << 2,
114 CLAMPED_BOTTOM = 1 << 3
115 } ClampedSide;
116
117 typedef enum
118 {
119 SIDE_TO_RESIZE_NONE,
120 SIDE_TO_RESIZE_LEFT,
121 SIDE_TO_RESIZE_RIGHT,
122 SIDE_TO_RESIZE_TOP,
123 SIDE_TO_RESIZE_BOTTOM,
124 SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY,
125 SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY,
126 } SideToResize;
127
128
129 #define FEQUAL(a,b) (fabs ((a) - (b)) < 0.0001)
130 #define PIXEL_FEQUAL(a,b) (fabs ((a) - (b)) < 0.5)
131
132
133 struct _GimpToolRectanglePrivate
134 {
135 /* The following members are "constants", that is, variables that are setup
136 * during gimp_tool_rectangle_button_press and then only read.
137 */
138
139 /* Whether or not the rectangle currently being rubber-banded is the
140 * first one created with this instance, this determines if we can
141 * undo it on button_release.
142 */
143 gboolean is_first;
144
145 /* Whether or not the rectangle currently being rubber-banded was
146 * created from scratch.
147 */
148 gboolean is_new;
149
150 /* Holds the coordinate that should be used as the "other side" when
151 * fixed-center is turned off.
152 */
153 gdouble other_side_x;
154 gdouble other_side_y;
155
156 /* Holds the coordinate to be used as center when fixed-center is used. */
157 gdouble center_x_on_fixed_center;
158 gdouble center_y_on_fixed_center;
159
160 /* True when the rectangle is being adjusted (moved or
161 * rubber-banded).
162 */
163 gboolean rect_adjusting;
164
165
166 /* The rest of the members are internal state variables, that is, variables
167 * that might change during the manipulation session of the rectangle. Make
168 * sure these variables are in consistent states.
169 */
170
171 /* Coordinates of upper left and lower right rectangle corners. */
172 gdouble x1, y1;
173 gdouble x2, y2;
174
175 /* Integer coordinats of upper left corner and size. We must
176 * calculate this separately from the gdouble ones because sometimes
177 * we don't want to affect the integer size (e.g. when moving the
178 * rectangle), but that will be the case if we always calculate the
179 * integer coordinates based on rounded values of the gdouble
180 * coordinates even if the gdouble width remains constant.
181 *
182 * TODO: Change the internal double-representation of the rectangle
183 * to x,y width,height instead of x1,y1 x2,y2. That way we don't
184 * need to keep a separate representation of the integer version of
185 * the rectangle; rounding width an height will yield consistent
186 * results and not depend on position of the rectangle.
187 */
188 gint x1_int, y1_int;
189 gint width_int, height_int;
190
191 /* How to constrain the rectangle. */
192 GimpRectangleConstraint constraint;
193
194 /* What precision the rectangle will appear to have externally (it
195 * will always be double internally)
196 */
197 GimpRectanglePrecision precision;
198
199 /* Previous coordinate applied to the rectangle. */
200 gdouble lastx;
201 gdouble lasty;
202
203 /* Width and height of corner handles. */
204 gint corner_handle_w;
205 gint corner_handle_h;
206
207 /* Width and height of side handles. */
208 gint top_and_bottom_handle_w;
209 gint left_and_right_handle_h;
210
211 /* Whether or not the rectangle is in a 'narrow situation' i.e. it is
212 * too small for reasonable sized handle to be inside. In this case
213 * we put handles on the outside.
214 */
215 gboolean narrow_mode;
216
217 /* This boolean allows to always set narrow mode */
218 gboolean force_narrow_mode;
219
220 /* Whether or not to draw an ellipse inside the rectangle */
221 gboolean draw_ellipse;
222
223 /* Whether to draw round corners */
224 gboolean round_corners;
225 gdouble corner_radius;
226
227 /* The title for the statusbar coords */
228 gchar *status_title;
229
230 /* For saving in case of cancellation. */
231 gdouble saved_x1;
232 gdouble saved_y1;
233 gdouble saved_x2;
234 gdouble saved_y2;
235
236 gint suppress_updates;
237
238 GimpRectangleFunction function;
239
240 /* The following values are externally synced with GimpRectangleOptions */
241
242 gboolean highlight;
243 gdouble highlight_opacity;
244 GimpGuidesType guide;
245
246 gdouble x;
247 gdouble y;
248 gdouble width;
249 gdouble height;
250
251 gboolean fixed_rule_active;
252 GimpRectangleFixedRule fixed_rule;
253 gdouble desired_fixed_width;
254 gdouble desired_fixed_height;
255 gdouble desired_fixed_size_width;
256 gdouble desired_fixed_size_height;
257 gdouble aspect_numerator;
258 gdouble aspect_denominator;
259 gboolean fixed_center;
260
261 /* Canvas items for drawing the GUI */
262
263 GimpCanvasItem *guides;
264 GimpCanvasItem *rectangle;
265 GimpCanvasItem *ellipse;
266 GimpCanvasItem *corners[4];
267 GimpCanvasItem *center;
268 GimpCanvasItem *creating_corners[4];
269 GimpCanvasItem *handles[GIMP_N_TOOL_RECTANGLE_FUNCTIONS];
270 GimpCanvasItem *highlight_handles[GIMP_N_TOOL_RECTANGLE_FUNCTIONS];
271 };
272
273
274 /* local function prototypes */
275
276 static void gimp_tool_rectangle_constructed (GObject *object);
277 static void gimp_tool_rectangle_finalize (GObject *object);
278 static void gimp_tool_rectangle_set_property (GObject *object,
279 guint property_id,
280 const GValue *value,
281 GParamSpec *pspec);
282 static void gimp_tool_rectangle_get_property (GObject *object,
283 guint property_id,
284 GValue *value,
285 GParamSpec *pspec);
286 static void gimp_tool_rectangle_notify (GObject *object,
287 GParamSpec *pspec);
288
289 static void gimp_tool_rectangle_changed (GimpToolWidget *widget);
290 static gint gimp_tool_rectangle_button_press (GimpToolWidget *widget,
291 const GimpCoords *coords,
292 guint32 time,
293 GdkModifierType state,
294 GimpButtonPressType press_type);
295 static void gimp_tool_rectangle_button_release (GimpToolWidget *widget,
296 const GimpCoords *coords,
297 guint32 time,
298 GdkModifierType state,
299 GimpButtonReleaseType release_type);
300 static void gimp_tool_rectangle_motion (GimpToolWidget *widget,
301 const GimpCoords *coords,
302 guint32 time,
303 GdkModifierType state);
304 static GimpHit gimp_tool_rectangle_hit (GimpToolWidget *widget,
305 const GimpCoords *coords,
306 GdkModifierType state,
307 gboolean proximity);
308 static void gimp_tool_rectangle_hover (GimpToolWidget *widget,
309 const GimpCoords *coords,
310 GdkModifierType state,
311 gboolean proximity);
312 static void gimp_tool_rectangle_leave_notify (GimpToolWidget *widget);
313 static gboolean gimp_tool_rectangle_key_press (GimpToolWidget *widget,
314 GdkEventKey *kevent);
315 static void gimp_tool_rectangle_motion_modifier (GimpToolWidget *widget,
316 GdkModifierType key,
317 gboolean press,
318 GdkModifierType state);
319 static gboolean gimp_tool_rectangle_get_cursor (GimpToolWidget *widget,
320 const GimpCoords *coords,
321 GdkModifierType state,
322 GimpCursorType *cursor,
323 GimpToolCursorType *tool_cursor,
324 GimpCursorModifier *modifier);
325
326 static void gimp_tool_rectangle_change_complete (GimpToolRectangle *rectangle);
327
328 static void gimp_tool_rectangle_update_options (GimpToolRectangle *rectangle);
329 static void gimp_tool_rectangle_update_handle_sizes
330 (GimpToolRectangle *rectangle);
331 static void gimp_tool_rectangle_update_status (GimpToolRectangle *rectangle);
332
333 static void gimp_tool_rectangle_synthesize_motion
334 (GimpToolRectangle *rectangle,
335 gint function,
336 gdouble new_x,
337 gdouble new_y);
338
339 static GimpRectangleFunction
340 gimp_tool_rectangle_calc_function (GimpToolRectangle *rectangle,
341 const GimpCoords *coords,
342 gboolean proximity);
343 static void gimp_tool_rectangle_check_function (GimpToolRectangle *rectangle);
344
345 static gboolean gimp_tool_rectangle_coord_outside (GimpToolRectangle *rectangle,
346 const GimpCoords *coords);
347
348 static gboolean gimp_tool_rectangle_coord_on_handle (GimpToolRectangle *rectangle,
349 const GimpCoords *coords,
350 GimpHandleAnchor anchor);
351
352 static GimpHandleAnchor gimp_tool_rectangle_get_anchor
353 (GimpRectangleFunction function);
354 static gboolean gimp_tool_rectangle_rect_rubber_banding_func
355 (GimpToolRectangle *rectangle);
356 static gboolean gimp_tool_rectangle_rect_adjusting_func
357 (GimpToolRectangle *rectangle);
358
359 static void gimp_tool_rectangle_get_other_side (GimpToolRectangle *rectangle,
360 gdouble **other_x,
361 gdouble **other_y);
362 static void gimp_tool_rectangle_get_other_side_coord
363 (GimpToolRectangle *rectangle,
364 gdouble *other_side_x,
365 gdouble *other_side_y);
366 static void gimp_tool_rectangle_set_other_side_coord
367 (GimpToolRectangle *rectangle,
368 gdouble other_side_x,
369 gdouble other_side_y);
370
371 static void gimp_tool_rectangle_apply_coord (GimpToolRectangle *rectangle,
372 gdouble coord_x,
373 gdouble coord_y);
374 static void gimp_tool_rectangle_setup_snap_offsets
375 (GimpToolRectangle *rectangle,
376 const GimpCoords *coords);
377
378 static void gimp_tool_rectangle_clamp (GimpToolRectangle *rectangle,
379 ClampedSide *clamped_sides,
380 GimpRectangleConstraint constraint,
381 gboolean symmetrically);
382 static void gimp_tool_rectangle_clamp_width (GimpToolRectangle *rectangle,
383 ClampedSide *clamped_sides,
384 GimpRectangleConstraint constraint,
385 gboolean symmetrically);
386 static void gimp_tool_rectangle_clamp_height (GimpToolRectangle *rectangle,
387 ClampedSide *clamped_sides,
388 GimpRectangleConstraint constraint,
389 gboolean symmetrically);
390
391 static void gimp_tool_rectangle_keep_inside (GimpToolRectangle *rectangle,
392 GimpRectangleConstraint constraint);
393 static void gimp_tool_rectangle_keep_inside_horizontally
394 (GimpToolRectangle *rectangle,
395 GimpRectangleConstraint constraint);
396 static void gimp_tool_rectangle_keep_inside_vertically
397 (GimpToolRectangle *rectangle,
398 GimpRectangleConstraint constraint);
399
400 static void gimp_tool_rectangle_apply_fixed_width
401 (GimpToolRectangle *rectangle,
402 GimpRectangleConstraint constraint,
403 gdouble width);
404 static void gimp_tool_rectangle_apply_fixed_height
405 (GimpToolRectangle *rectangle,
406 GimpRectangleConstraint constraint,
407 gdouble height);
408
409 static void gimp_tool_rectangle_apply_aspect (GimpToolRectangle *rectangle,
410 gdouble aspect,
411 gint clamped_sides);
412
413 static void gimp_tool_rectangle_update_with_coord
414 (GimpToolRectangle *rectangle,
415 gdouble new_x,
416 gdouble new_y);
417 static void gimp_tool_rectangle_apply_fixed_rule(GimpToolRectangle *rectangle);
418
419 static void gimp_tool_rectangle_get_constraints (GimpToolRectangle *rectangle,
420 gint *min_x,
421 gint *min_y,
422 gint *max_x,
423 gint *max_y,
424 GimpRectangleConstraint constraint);
425
426 static void gimp_tool_rectangle_handle_general_clamping
427 (GimpToolRectangle *rectangle);
428 static void gimp_tool_rectangle_update_int_rect (GimpToolRectangle *rectangle);
429 static void gimp_tool_rectangle_adjust_coord (GimpToolRectangle *rectangle,
430 gdouble coord_x_input,
431 gdouble coord_y_input,
432 gdouble *coord_x_output,
433 gdouble *coord_y_output);
434 static void gimp_tool_rectangle_recalculate_center_xy
435 (GimpToolRectangle *rectangle);
436
437
438 G_DEFINE_TYPE_WITH_PRIVATE (GimpToolRectangle, gimp_tool_rectangle,
439 GIMP_TYPE_TOOL_WIDGET)
440
441 #define parent_class gimp_tool_rectangle_parent_class
442
443 static guint rectangle_signals[LAST_SIGNAL] = { 0, };
444
445
446 static void
gimp_tool_rectangle_class_init(GimpToolRectangleClass * klass)447 gimp_tool_rectangle_class_init (GimpToolRectangleClass *klass)
448 {
449 GObjectClass *object_class = G_OBJECT_CLASS (klass);
450 GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
451
452 object_class->constructed = gimp_tool_rectangle_constructed;
453 object_class->finalize = gimp_tool_rectangle_finalize;
454 object_class->set_property = gimp_tool_rectangle_set_property;
455 object_class->get_property = gimp_tool_rectangle_get_property;
456 object_class->notify = gimp_tool_rectangle_notify;
457
458 widget_class->changed = gimp_tool_rectangle_changed;
459 widget_class->button_press = gimp_tool_rectangle_button_press;
460 widget_class->button_release = gimp_tool_rectangle_button_release;
461 widget_class->motion = gimp_tool_rectangle_motion;
462 widget_class->hit = gimp_tool_rectangle_hit;
463 widget_class->hover = gimp_tool_rectangle_hover;
464 widget_class->leave_notify = gimp_tool_rectangle_leave_notify;
465 widget_class->key_press = gimp_tool_rectangle_key_press;
466 widget_class->motion_modifier = gimp_tool_rectangle_motion_modifier;
467 widget_class->get_cursor = gimp_tool_rectangle_get_cursor;
468 widget_class->update_on_scale = TRUE;
469
470 rectangle_signals[CHANGE_COMPLETE] =
471 g_signal_new ("change-complete",
472 G_TYPE_FROM_CLASS (klass),
473 G_SIGNAL_RUN_FIRST,
474 G_STRUCT_OFFSET (GimpToolRectangleClass, change_complete),
475 NULL, NULL,
476 g_cclosure_marshal_VOID__VOID,
477 G_TYPE_NONE, 0);
478
479 g_object_class_install_property (object_class, PROP_X1,
480 g_param_spec_double ("x1",
481 NULL, NULL,
482 -GIMP_MAX_IMAGE_SIZE,
483 GIMP_MAX_IMAGE_SIZE,
484 0.0,
485 GIMP_PARAM_READWRITE |
486 G_PARAM_CONSTRUCT));
487
488 g_object_class_install_property (object_class, PROP_Y1,
489 g_param_spec_double ("y1",
490 NULL, NULL,
491 -GIMP_MAX_IMAGE_SIZE,
492 GIMP_MAX_IMAGE_SIZE,
493 0.0,
494 GIMP_PARAM_READWRITE |
495 G_PARAM_CONSTRUCT));
496
497 g_object_class_install_property (object_class, PROP_X2,
498 g_param_spec_double ("x2",
499 NULL, NULL,
500 -GIMP_MAX_IMAGE_SIZE,
501 GIMP_MAX_IMAGE_SIZE,
502 0.0,
503 GIMP_PARAM_READWRITE |
504 G_PARAM_CONSTRUCT));
505
506 g_object_class_install_property (object_class, PROP_Y2,
507 g_param_spec_double ("y2",
508 NULL, NULL,
509 -GIMP_MAX_IMAGE_SIZE,
510 GIMP_MAX_IMAGE_SIZE,
511 0.0,
512 GIMP_PARAM_READWRITE |
513 G_PARAM_CONSTRUCT));
514
515 g_object_class_install_property (object_class, PROP_CONSTRAINT,
516 g_param_spec_enum ("constraint",
517 NULL, NULL,
518 GIMP_TYPE_RECTANGLE_CONSTRAINT,
519 GIMP_RECTANGLE_CONSTRAIN_NONE,
520 GIMP_PARAM_READWRITE |
521 G_PARAM_CONSTRUCT));
522
523 g_object_class_install_property (object_class, PROP_PRECISION,
524 g_param_spec_enum ("precision",
525 NULL, NULL,
526 GIMP_TYPE_RECTANGLE_PRECISION,
527 GIMP_RECTANGLE_PRECISION_INT,
528 GIMP_PARAM_READWRITE |
529 G_PARAM_CONSTRUCT));
530
531 g_object_class_install_property (object_class, PROP_NARROW_MODE,
532 g_param_spec_boolean ("narrow-mode",
533 NULL, NULL,
534 FALSE,
535 GIMP_PARAM_READWRITE |
536 G_PARAM_CONSTRUCT));
537
538 g_object_class_install_property (object_class, PROP_FORCE_NARROW_MODE,
539 g_param_spec_boolean ("force-narrow-mode",
540 NULL, NULL,
541 FALSE,
542 GIMP_PARAM_READWRITE |
543 G_PARAM_CONSTRUCT));
544
545 g_object_class_install_property (object_class, PROP_DRAW_ELLIPSE,
546 g_param_spec_boolean ("draw-ellipse",
547 NULL, NULL,
548 FALSE,
549 GIMP_PARAM_READWRITE |
550 G_PARAM_CONSTRUCT));
551
552 g_object_class_install_property (object_class, PROP_ROUND_CORNERS,
553 g_param_spec_boolean ("round-corners",
554 NULL, NULL,
555 FALSE,
556 GIMP_PARAM_READWRITE |
557 G_PARAM_CONSTRUCT));
558
559 g_object_class_install_property (object_class, PROP_CORNER_RADIUS,
560 g_param_spec_double ("corner-radius",
561 NULL, NULL,
562 0.0, 10000.0, 10.0,
563 GIMP_PARAM_READWRITE |
564 G_PARAM_CONSTRUCT));
565
566 g_object_class_install_property (object_class, PROP_STATUS_TITLE,
567 g_param_spec_string ("status-title",
568 NULL, NULL,
569 _("Rectangle: "),
570 GIMP_PARAM_READWRITE |
571 G_PARAM_CONSTRUCT));
572
573 g_object_class_install_property (object_class, PROP_HIGHLIGHT,
574 g_param_spec_boolean ("highlight",
575 NULL, NULL,
576 FALSE,
577 GIMP_PARAM_READWRITE |
578 G_PARAM_CONSTRUCT));
579
580 g_object_class_install_property (object_class, PROP_HIGHLIGHT_OPACITY,
581 g_param_spec_double ("highlight-opacity",
582 NULL, NULL,
583 0.0, 1.0, 0.5,
584 GIMP_PARAM_READWRITE |
585 G_PARAM_CONSTRUCT));
586
587 g_object_class_install_property (object_class, PROP_GUIDE,
588 g_param_spec_enum ("guide",
589 NULL, NULL,
590 GIMP_TYPE_GUIDES_TYPE,
591 GIMP_GUIDES_NONE,
592 GIMP_PARAM_READWRITE |
593 G_PARAM_CONSTRUCT));
594
595 g_object_class_install_property (object_class, PROP_X,
596 g_param_spec_double ("x",
597 NULL, NULL,
598 -GIMP_MAX_IMAGE_SIZE,
599 GIMP_MAX_IMAGE_SIZE,
600 0.0,
601 GIMP_PARAM_READWRITE |
602 G_PARAM_CONSTRUCT));
603
604 g_object_class_install_property (object_class, PROP_Y,
605 g_param_spec_double ("y",
606 NULL, NULL,
607 -GIMP_MAX_IMAGE_SIZE,
608 GIMP_MAX_IMAGE_SIZE,
609 0.0,
610 GIMP_PARAM_READWRITE |
611 G_PARAM_CONSTRUCT));
612
613 g_object_class_install_property (object_class, PROP_WIDTH,
614 g_param_spec_double ("width",
615 NULL, NULL,
616 0.0,
617 GIMP_MAX_IMAGE_SIZE,
618 0.0,
619 GIMP_PARAM_READWRITE |
620 G_PARAM_CONSTRUCT));
621
622 g_object_class_install_property (object_class, PROP_HEIGHT,
623 g_param_spec_double ("height",
624 NULL, NULL,
625 0.0,
626 GIMP_MAX_IMAGE_SIZE,
627 0.0,
628 GIMP_PARAM_READWRITE |
629 G_PARAM_CONSTRUCT));
630
631 g_object_class_install_property (object_class, PROP_FIXED_RULE_ACTIVE,
632 g_param_spec_boolean ("fixed-rule-active",
633 NULL, NULL,
634 FALSE,
635 GIMP_PARAM_READWRITE |
636 G_PARAM_CONSTRUCT));
637
638 g_object_class_install_property (object_class, PROP_FIXED_RULE,
639 g_param_spec_enum ("fixed-rule",
640 NULL, NULL,
641 GIMP_TYPE_RECTANGLE_FIXED_RULE,
642 GIMP_RECTANGLE_FIXED_ASPECT,
643 GIMP_PARAM_READWRITE |
644 G_PARAM_CONSTRUCT));
645
646 g_object_class_install_property (object_class, PROP_DESIRED_FIXED_WIDTH,
647 g_param_spec_double ("desired-fixed-width",
648 NULL, NULL,
649 0.0, GIMP_MAX_IMAGE_SIZE,
650 100.0,
651 GIMP_PARAM_READWRITE |
652 G_PARAM_CONSTRUCT));
653
654 g_object_class_install_property (object_class, PROP_DESIRED_FIXED_HEIGHT,
655 g_param_spec_double ("desired-fixed-height",
656 NULL, NULL,
657 0.0, GIMP_MAX_IMAGE_SIZE,
658 100.0,
659 GIMP_PARAM_READWRITE |
660 G_PARAM_CONSTRUCT));
661
662 g_object_class_install_property (object_class, PROP_DESIRED_FIXED_SIZE_WIDTH,
663 g_param_spec_double ("desired-fixed-size-width",
664 NULL, NULL,
665 0.0, GIMP_MAX_IMAGE_SIZE,
666 100.0,
667 GIMP_PARAM_READWRITE |
668 G_PARAM_CONSTRUCT));
669
670 g_object_class_install_property (object_class, PROP_DESIRED_FIXED_SIZE_HEIGHT,
671 g_param_spec_double ("desired-fixed-size-height",
672 NULL, NULL,
673 0.0, GIMP_MAX_IMAGE_SIZE,
674 100.0,
675 GIMP_PARAM_READWRITE |
676 G_PARAM_CONSTRUCT));
677
678 g_object_class_install_property (object_class, PROP_ASPECT_NUMERATOR,
679 g_param_spec_double ("aspect-numerator",
680 NULL, NULL,
681 0.0, GIMP_MAX_IMAGE_SIZE,
682 1.0,
683 GIMP_PARAM_READWRITE |
684 G_PARAM_CONSTRUCT));
685
686 g_object_class_install_property (object_class, PROP_ASPECT_DENOMINATOR,
687 g_param_spec_double ("aspect-denominator",
688 NULL, NULL,
689 0.0, GIMP_MAX_IMAGE_SIZE,
690 1.0,
691 GIMP_PARAM_READWRITE |
692 G_PARAM_CONSTRUCT));
693
694 g_object_class_install_property (object_class, PROP_FIXED_CENTER,
695 g_param_spec_boolean ("fixed-center",
696 NULL, NULL,
697 FALSE,
698 GIMP_PARAM_READWRITE |
699 G_PARAM_CONSTRUCT));
700 }
701
702 static void
gimp_tool_rectangle_init(GimpToolRectangle * rectangle)703 gimp_tool_rectangle_init (GimpToolRectangle *rectangle)
704 {
705 rectangle->private = gimp_tool_rectangle_get_instance_private (rectangle);
706
707 rectangle->private->function = GIMP_TOOL_RECTANGLE_CREATING;
708 rectangle->private->is_first = TRUE;
709 }
710
711 static void
gimp_tool_rectangle_constructed(GObject * object)712 gimp_tool_rectangle_constructed (GObject *object)
713 {
714 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
715 GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
716 GimpToolRectanglePrivate *private = rectangle->private;
717 GimpCanvasGroup *stroke_group;
718 gint i;
719
720 G_OBJECT_CLASS (parent_class)->constructed (object);
721
722 stroke_group = gimp_tool_widget_add_stroke_group (widget);
723
724 gimp_tool_widget_push_group (widget, stroke_group);
725
726 private->guides = gimp_tool_widget_add_rectangle_guides (widget,
727 0, 0, 10, 10,
728 GIMP_GUIDES_NONE);
729
730 private->rectangle = gimp_tool_widget_add_rectangle (widget,
731 0, 0, 10, 10,
732 FALSE);
733
734 private->ellipse = gimp_tool_widget_add_arc (widget,
735 0, 0, 10, 10,
736 0.0, 2 * G_PI,
737 FALSE);
738
739 for (i = 0; i < 4; i++)
740 private->corners[i] = gimp_tool_widget_add_arc (widget,
741 0, 0, 10, 10,
742 0.0, 2 * G_PI,
743 FALSE);
744
745 gimp_tool_widget_pop_group (widget);
746
747 private->center = gimp_tool_widget_add_handle (widget,
748 GIMP_HANDLE_CROSS,
749 0, 0,
750 GIMP_CANVAS_HANDLE_SIZE_SMALL,
751 GIMP_CANVAS_HANDLE_SIZE_SMALL,
752 GIMP_HANDLE_ANCHOR_CENTER);
753
754 gimp_tool_widget_push_group (widget, stroke_group);
755
756 private->creating_corners[0] =
757 gimp_tool_widget_add_corner (widget,
758 0, 0, 10, 10,
759 GIMP_HANDLE_ANCHOR_NORTH_WEST,
760 10, 10,
761 FALSE);
762
763 private->creating_corners[1] =
764 gimp_tool_widget_add_corner (widget,
765 0, 0, 10, 10,
766 GIMP_HANDLE_ANCHOR_NORTH_EAST,
767 10, 10,
768 FALSE);
769
770 private->creating_corners[2] =
771 gimp_tool_widget_add_corner (widget,
772 0, 0, 10, 10,
773 GIMP_HANDLE_ANCHOR_SOUTH_WEST,
774 10, 10,
775 FALSE);
776
777 private->creating_corners[3] =
778 gimp_tool_widget_add_corner (widget,
779 0, 0, 10, 10,
780 GIMP_HANDLE_ANCHOR_SOUTH_EAST,
781 10, 10,
782 FALSE);
783
784 gimp_tool_widget_pop_group (widget);
785
786 for (i = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
787 i <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
788 i++)
789 {
790 GimpHandleAnchor anchor;
791
792 anchor = gimp_tool_rectangle_get_anchor (i);
793
794 gimp_tool_widget_push_group (widget, stroke_group);
795
796 private->handles[i] = gimp_tool_widget_add_corner (widget,
797 0, 0, 10, 10,
798 anchor,
799 10, 10,
800 FALSE);
801
802 gimp_tool_widget_pop_group (widget);
803
804 private->highlight_handles[i] = gimp_tool_widget_add_corner (widget,
805 0, 0, 10, 10,
806 anchor,
807 10, 10,
808 FALSE);
809 gimp_canvas_item_set_highlight (private->highlight_handles[i], TRUE);
810 }
811
812 gimp_tool_rectangle_changed (widget);
813 }
814
815 static void
gimp_tool_rectangle_finalize(GObject * object)816 gimp_tool_rectangle_finalize (GObject *object)
817 {
818 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
819 GimpToolRectanglePrivate *private = rectangle->private;
820
821 g_clear_pointer (&private->status_title, g_free);
822
823 G_OBJECT_CLASS (parent_class)->finalize (object);
824 }
825
826 static void
gimp_tool_rectangle_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)827 gimp_tool_rectangle_set_property (GObject *object,
828 guint property_id,
829 const GValue *value,
830 GParamSpec *pspec)
831 {
832 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
833 GimpToolRectanglePrivate *private = rectangle->private;
834
835 switch (property_id)
836 {
837 case PROP_X1:
838 private->x1 = g_value_get_double (value);
839 break;
840 case PROP_Y1:
841 private->y1 = g_value_get_double (value);
842 break;
843 case PROP_X2:
844 private->x2 = g_value_get_double (value);
845 break;
846 case PROP_Y2:
847 private->y2 = g_value_get_double (value);
848 break;
849
850 case PROP_CONSTRAINT:
851 private->constraint = g_value_get_enum (value);
852 break;
853 case PROP_PRECISION:
854 private->precision = g_value_get_enum (value);
855 break;
856
857 case PROP_NARROW_MODE:
858 private->narrow_mode = g_value_get_boolean (value);
859 break;
860 case PROP_FORCE_NARROW_MODE:
861 private->force_narrow_mode = g_value_get_boolean (value);
862 break;
863 case PROP_DRAW_ELLIPSE:
864 private->draw_ellipse = g_value_get_boolean (value);
865 break;
866 case PROP_ROUND_CORNERS:
867 private->round_corners = g_value_get_boolean (value);
868 break;
869 case PROP_CORNER_RADIUS:
870 private->corner_radius = g_value_get_double (value);
871 break;
872
873 case PROP_STATUS_TITLE:
874 g_free (private->status_title);
875 private->status_title = g_value_dup_string (value);
876 if (! private->status_title)
877 private->status_title = g_strdup (_("Rectangle: "));
878 break;
879
880 case PROP_HIGHLIGHT:
881 private->highlight = g_value_get_boolean (value);
882 break;
883 case PROP_HIGHLIGHT_OPACITY:
884 private->highlight_opacity = g_value_get_double (value);
885 break;
886 case PROP_GUIDE:
887 private->guide = g_value_get_enum (value);
888 break;
889
890 case PROP_X:
891 private->x = g_value_get_double (value);
892 break;
893 case PROP_Y:
894 private->y = g_value_get_double (value);
895 break;
896 case PROP_WIDTH:
897 private->width = g_value_get_double (value);
898 break;
899 case PROP_HEIGHT:
900 private->height = g_value_get_double (value);
901 break;
902
903 case PROP_FIXED_RULE_ACTIVE:
904 private->fixed_rule_active = g_value_get_boolean (value);
905 break;
906 case PROP_FIXED_RULE:
907 private->fixed_rule = g_value_get_enum (value);
908 break;
909 case PROP_DESIRED_FIXED_WIDTH:
910 private->desired_fixed_width = g_value_get_double (value);
911 break;
912 case PROP_DESIRED_FIXED_HEIGHT:
913 private->desired_fixed_height = g_value_get_double (value);
914 break;
915 case PROP_DESIRED_FIXED_SIZE_WIDTH:
916 private->desired_fixed_size_width = g_value_get_double (value);
917 break;
918 case PROP_DESIRED_FIXED_SIZE_HEIGHT:
919 private->desired_fixed_size_height = g_value_get_double (value);
920 break;
921 case PROP_ASPECT_NUMERATOR:
922 private->aspect_numerator = g_value_get_double (value);
923 break;
924 case PROP_ASPECT_DENOMINATOR:
925 private->aspect_denominator = g_value_get_double (value);
926 break;
927
928 case PROP_FIXED_CENTER:
929 private->fixed_center = g_value_get_boolean (value);
930 break;
931
932 default:
933 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
934 break;
935 }
936 }
937
938 static void
gimp_tool_rectangle_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)939 gimp_tool_rectangle_get_property (GObject *object,
940 guint property_id,
941 GValue *value,
942 GParamSpec *pspec)
943 {
944 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
945 GimpToolRectanglePrivate *private = rectangle->private;
946
947 switch (property_id)
948 {
949 case PROP_X1:
950 g_value_set_double (value, private->x1);
951 break;
952 case PROP_Y1:
953 g_value_set_double (value, private->y1);
954 break;
955 case PROP_X2:
956 g_value_set_double (value, private->x2);
957 break;
958 case PROP_Y2:
959 g_value_set_double (value, private->y2);
960 break;
961
962 case PROP_CONSTRAINT:
963 g_value_set_enum (value, private->constraint);
964 break;
965 case PROP_PRECISION:
966 g_value_set_enum (value, private->precision);
967 break;
968
969 case PROP_NARROW_MODE:
970 g_value_set_boolean (value, private->narrow_mode);
971 break;
972 case PROP_FORCE_NARROW_MODE:
973 g_value_set_boolean (value, private->force_narrow_mode);
974 break;
975 case PROP_DRAW_ELLIPSE:
976 g_value_set_boolean (value, private->draw_ellipse);
977 break;
978 case PROP_ROUND_CORNERS:
979 g_value_set_boolean (value, private->round_corners);
980 break;
981 case PROP_CORNER_RADIUS:
982 g_value_set_double (value, private->corner_radius);
983 break;
984
985 case PROP_STATUS_TITLE:
986 g_value_set_string (value, private->status_title);
987 break;
988
989 case PROP_HIGHLIGHT:
990 g_value_set_boolean (value, private->highlight);
991 break;
992 case PROP_HIGHLIGHT_OPACITY:
993 g_value_set_double (value, private->highlight_opacity);
994 break;
995 case PROP_GUIDE:
996 g_value_set_enum (value, private->guide);
997 break;
998
999 case PROP_X:
1000 g_value_set_double (value, private->x);
1001 break;
1002 case PROP_Y:
1003 g_value_set_double (value, private->y);
1004 break;
1005 case PROP_WIDTH:
1006 g_value_set_double (value, private->width);
1007 break;
1008 case PROP_HEIGHT:
1009 g_value_set_double (value, private->height);
1010 break;
1011
1012 case PROP_FIXED_RULE_ACTIVE:
1013 g_value_set_boolean (value, private->fixed_rule_active);
1014 break;
1015 case PROP_FIXED_RULE:
1016 g_value_set_enum (value, private->fixed_rule);
1017 break;
1018 case PROP_DESIRED_FIXED_WIDTH:
1019 g_value_set_double (value, private->desired_fixed_width);
1020 break;
1021 case PROP_DESIRED_FIXED_HEIGHT:
1022 g_value_set_double (value, private->desired_fixed_height);
1023 break;
1024 case PROP_DESIRED_FIXED_SIZE_WIDTH:
1025 g_value_set_double (value, private->desired_fixed_size_width);
1026 break;
1027 case PROP_DESIRED_FIXED_SIZE_HEIGHT:
1028 g_value_set_double (value, private->desired_fixed_size_height);
1029 break;
1030 case PROP_ASPECT_NUMERATOR:
1031 g_value_set_double (value, private->aspect_numerator);
1032 break;
1033 case PROP_ASPECT_DENOMINATOR:
1034 g_value_set_double (value, private->aspect_denominator);
1035 break;
1036
1037 case PROP_FIXED_CENTER:
1038 g_value_set_boolean (value, private->fixed_center);
1039 break;
1040
1041 default:
1042 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1043 break;
1044 }
1045 }
1046
1047 static void
gimp_tool_rectangle_notify(GObject * object,GParamSpec * pspec)1048 gimp_tool_rectangle_notify (GObject *object,
1049 GParamSpec *pspec)
1050 {
1051 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
1052 GimpToolRectanglePrivate *private = rectangle->private;
1053
1054 if (G_OBJECT_CLASS (parent_class)->notify)
1055 G_OBJECT_CLASS (parent_class)->notify (object, pspec);
1056
1057 if (! strcmp (pspec->name, "x1") ||
1058 ! strcmp (pspec->name, "y1") ||
1059 ! strcmp (pspec->name, "x2") ||
1060 ! strcmp (pspec->name, "y2"))
1061 {
1062 gimp_tool_rectangle_update_int_rect (rectangle);
1063
1064 gimp_tool_rectangle_recalculate_center_xy (rectangle);
1065
1066 gimp_tool_rectangle_update_options (rectangle);
1067 }
1068 else if (! strcmp (pspec->name, "x") &&
1069 ! PIXEL_FEQUAL (private->x1, private->x))
1070 {
1071 gimp_tool_rectangle_synthesize_motion (rectangle,
1072 GIMP_TOOL_RECTANGLE_MOVING,
1073 private->x,
1074 private->y1);
1075 }
1076 else if (! strcmp (pspec->name, "y") &&
1077 ! PIXEL_FEQUAL (private->y1, private->y))
1078 {
1079 gimp_tool_rectangle_synthesize_motion (rectangle,
1080 GIMP_TOOL_RECTANGLE_MOVING,
1081 private->x1,
1082 private->y);
1083 }
1084 else if (! strcmp (pspec->name, "width") &&
1085 ! PIXEL_FEQUAL (private->x2 - private->x1, private->width))
1086 {
1087 /* Calculate x2, y2 that will create a rectangle of given width,
1088 * for the current options.
1089 */
1090 gdouble x2;
1091
1092 if (private->fixed_center)
1093 {
1094 x2 = private->center_x_on_fixed_center +
1095 private->width / 2;
1096 }
1097 else
1098 {
1099 x2 = private->x1 + private->width;
1100 }
1101
1102 gimp_tool_rectangle_synthesize_motion (rectangle,
1103 GIMP_TOOL_RECTANGLE_RESIZING_RIGHT,
1104 x2,
1105 private->y2);
1106 }
1107 else if (! strcmp (pspec->name, "height") &&
1108 ! PIXEL_FEQUAL (private->y2 - private->y1, private->height))
1109 {
1110 /* Calculate x2, y2 that will create a rectangle of given
1111 * height, for the current options.
1112 */
1113 gdouble y2;
1114
1115 if (private->fixed_center)
1116 {
1117 y2 = private->center_y_on_fixed_center +
1118 private->height / 2;
1119 }
1120 else
1121 {
1122 y2 = private->y1 + private->height;
1123 }
1124
1125 gimp_tool_rectangle_synthesize_motion (rectangle,
1126 GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM,
1127 private->x2,
1128 y2);
1129 }
1130 else if (! strcmp (pspec->name, "desired-fixed-size-width"))
1131 {
1132 /* We are only interested in when width and height swaps, so
1133 * it's enough to only check e.g. for width.
1134 */
1135
1136 gdouble width = private->x2 - private->x1;
1137 gdouble height = private->y2 - private->y1;
1138
1139 /* Depending on a bunch of conditions, we might want to
1140 * immedieately switch width and height of the pending
1141 * rectangle.
1142 */
1143 if (private->fixed_rule_active &&
1144 #if 0
1145 tool->button_press_state == 0 &&
1146 tool->active_modifier_state == 0 &&
1147 #endif
1148 FEQUAL (private->desired_fixed_size_width, height) &&
1149 FEQUAL (private->desired_fixed_size_height, width))
1150 {
1151 gdouble x = private->x1;
1152 gdouble y = private->y1;
1153
1154 gimp_tool_rectangle_synthesize_motion (rectangle,
1155 GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT,
1156 private->x2,
1157 private->y2);
1158
1159 /* For some reason these needs to be set separately... */
1160 g_object_set (rectangle,
1161 "x", x,
1162 NULL);
1163 g_object_set (rectangle,
1164 "y", y,
1165 NULL);
1166 }
1167 }
1168 else if (! strcmp (pspec->name, "aspect-numerator"))
1169 {
1170 /* We are only interested in when numerator and denominator
1171 * swaps, so it's enough to only check e.g. for numerator.
1172 */
1173
1174 double width = private->x2 - private->x1;
1175 double height = private->y2 - private->y1;
1176 gdouble new_inverse_ratio = private->aspect_denominator /
1177 private->aspect_numerator;
1178 gdouble lower_ratio;
1179 gdouble higher_ratio;
1180
1181 /* The ratio of the Fixed: Aspect ratio rule and the pending
1182 * rectangle is very rarely exactly the same so use an
1183 * interval. For small rectangles the below code will
1184 * automatically yield a more generous accepted ratio interval
1185 * which is exactly what we want.
1186 */
1187 if (width > height && height > 1.0)
1188 {
1189 lower_ratio = width / (height + 1.0);
1190 higher_ratio = width / (height - 1.0);
1191 }
1192 else
1193 {
1194 lower_ratio = (width - 1.0) / height;
1195 higher_ratio = (width + 1.0) / height;
1196 }
1197
1198 /* Depending on a bunch of conditions, we might want to
1199 * immedieately switch width and height of the pending
1200 * rectangle.
1201 */
1202 if (private->fixed_rule_active &&
1203 #if 0
1204 tool->button_press_state == 0 &&
1205 tool->active_modifier_state == 0 &&
1206 #endif
1207 lower_ratio < new_inverse_ratio &&
1208 higher_ratio > new_inverse_ratio)
1209 {
1210 gdouble new_x2 = private->x1 + private->y2 - private->y1;
1211 gdouble new_y2 = private->y1 + private->x2 - private->x1;
1212
1213 gimp_tool_rectangle_synthesize_motion (rectangle,
1214 GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT,
1215 new_x2,
1216 new_y2);
1217 }
1218 }
1219 }
1220
1221 static void
gimp_tool_rectangle_changed(GimpToolWidget * widget)1222 gimp_tool_rectangle_changed (GimpToolWidget *widget)
1223 {
1224 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1225 GimpToolRectanglePrivate *private = rectangle->private;
1226 GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
1227 gdouble x1, y1, x2, y2;
1228 gint handle_width;
1229 gint handle_height;
1230 gint i;
1231
1232 gimp_tool_rectangle_update_handle_sizes (rectangle);
1233
1234 gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
1235
1236 gimp_canvas_rectangle_guides_set (private->guides,
1237 x1, y1,
1238 x2 - x1,
1239 y2 - y1,
1240 private->guide, 4);
1241
1242 gimp_canvas_rectangle_set (private->rectangle,
1243 x1, y1,
1244 x2 - x1,
1245 y2 - y1);
1246
1247 if (private->draw_ellipse)
1248 {
1249 gimp_canvas_arc_set (private->ellipse,
1250 (x1 + x2) / 2.0,
1251 (y1 + y2) / 2.0,
1252 (x2 - x1) / 2.0,
1253 (y2 - y1) / 2.0,
1254 0.0, 2 * G_PI);
1255 gimp_canvas_item_set_visible (private->ellipse, TRUE);
1256 }
1257 else
1258 {
1259 gimp_canvas_item_set_visible (private->ellipse, FALSE);
1260 }
1261
1262 if (private->round_corners && private->corner_radius > 0.0)
1263 {
1264 gdouble radius;
1265
1266 radius = MIN (private->corner_radius,
1267 MIN ((x2 - x1) / 2.0, (y2 - y1) / 2.0));
1268
1269 gimp_canvas_arc_set (private->corners[0],
1270 x1 + radius,
1271 y1 + radius,
1272 radius, radius,
1273 G_PI / 2.0, G_PI / 2.0);
1274
1275 gimp_canvas_arc_set (private->corners[1],
1276 x2 - radius,
1277 y1 + radius,
1278 radius, radius,
1279 0.0, G_PI / 2.0);
1280
1281 gimp_canvas_arc_set (private->corners[2],
1282 x1 + radius,
1283 y2 - radius,
1284 radius, radius,
1285 G_PI, G_PI / 2.0);
1286
1287 gimp_canvas_arc_set (private->corners[3],
1288 x2 - radius,
1289 y2 - radius,
1290 radius, radius,
1291 G_PI * 1.5, G_PI / 2.0);
1292
1293 for (i = 0; i < 4; i++)
1294 gimp_canvas_item_set_visible (private->corners[i], TRUE);
1295 }
1296 else
1297 {
1298 for (i = 0; i < 4; i++)
1299 gimp_canvas_item_set_visible (private->corners[i], FALSE);
1300 }
1301
1302 gimp_canvas_item_set_visible (private->center, FALSE);
1303
1304 for (i = 0; i < 4; i++)
1305 {
1306 gimp_canvas_item_set_visible (private->creating_corners[i], FALSE);
1307 gimp_canvas_item_set_highlight (private->creating_corners[i], FALSE);
1308 }
1309
1310 for (i = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
1311 i <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
1312 i++)
1313 {
1314 gimp_canvas_item_set_visible (private->handles[i], FALSE);
1315 gimp_canvas_item_set_visible (private->highlight_handles[i], FALSE);
1316 }
1317
1318 handle_width = private->corner_handle_w;
1319 handle_height = private->corner_handle_h;
1320
1321 switch (private->function)
1322 {
1323 case GIMP_TOOL_RECTANGLE_MOVING:
1324 if (private->rect_adjusting)
1325 {
1326 /* Mark the center because we snap to it */
1327 gimp_canvas_handle_set_position (private->center,
1328 (x1 + x2) / 2.0,
1329 (y1 + y2) / 2.0);
1330 gimp_canvas_item_set_visible (private->center, TRUE);
1331 break;
1332 }
1333
1334 /* else fallthrough */
1335
1336 case GIMP_TOOL_RECTANGLE_DEAD:
1337 case GIMP_TOOL_RECTANGLE_CREATING:
1338 case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
1339 for (i = 0; i < 4; i++)
1340 {
1341 gimp_canvas_corner_set (private->creating_corners[i],
1342 x1, y1, x2 - x1, y2 - y1,
1343 handle_width, handle_height,
1344 private->narrow_mode);
1345 gimp_canvas_item_set_visible (private->creating_corners[i], TRUE);
1346 }
1347 break;
1348
1349 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
1350 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
1351 handle_width = private->top_and_bottom_handle_w;
1352 break;
1353
1354 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
1355 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
1356 handle_height = private->left_and_right_handle_h;
1357 break;
1358
1359 default:
1360 break;
1361 }
1362
1363 if (handle_width >= 3 &&
1364 handle_height >= 3 &&
1365 private->function >= GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT &&
1366 private->function <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM)
1367 {
1368 GimpCanvasItem *corner;
1369
1370 if (private->rect_adjusting)
1371 corner = private->handles[private->function];
1372 else
1373 corner = private->highlight_handles[private->function];
1374
1375 gimp_canvas_corner_set (corner,
1376 x1, y1, x2 - x1, y2 - y1,
1377 handle_width, handle_height,
1378 private->narrow_mode);
1379 gimp_canvas_item_set_visible (corner, TRUE);
1380 }
1381
1382 if (private->highlight && ! private->rect_adjusting)
1383 {
1384 GdkRectangle rect;
1385
1386 rect.x = x1;
1387 rect.y = y1;
1388 rect.width = x2 - x1;
1389 rect.height = y2 - y1;
1390
1391 gimp_display_shell_set_highlight (shell, &rect, private->highlight_opacity);
1392 }
1393 else
1394 {
1395 gimp_display_shell_set_highlight (shell, NULL, 0.0);
1396 }
1397 }
1398
1399 gint
gimp_tool_rectangle_button_press(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonPressType press_type)1400 gimp_tool_rectangle_button_press (GimpToolWidget *widget,
1401 const GimpCoords *coords,
1402 guint32 time,
1403 GdkModifierType state,
1404 GimpButtonPressType press_type)
1405 {
1406 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1407 GimpToolRectanglePrivate *private = rectangle->private;
1408 gdouble snapped_x, snapped_y;
1409 gint snap_x, snap_y;
1410
1411 /* save existing shape in case of cancellation */
1412 private->saved_x1 = private->x1;
1413 private->saved_y1 = private->y1;
1414 private->saved_x2 = private->x2;
1415 private->saved_y2 = private->y2;
1416
1417 gimp_tool_rectangle_setup_snap_offsets (rectangle, coords);
1418 gimp_tool_widget_get_snap_offsets (widget, &snap_x, &snap_y, NULL, NULL);
1419
1420 snapped_x = coords->x + snap_x;
1421 snapped_y = coords->y + snap_y;
1422
1423 private->lastx = snapped_x;
1424 private->lasty = snapped_y;
1425
1426 if (private->function == GIMP_TOOL_RECTANGLE_CREATING)
1427 {
1428 /* Remember that this rectangle was created from scratch. */
1429 private->is_new = TRUE;
1430
1431 private->x1 = private->x2 = snapped_x;
1432 private->y1 = private->y2 = snapped_y;
1433
1434 /* Unless forced, created rectangles should not be started in
1435 * narrow-mode
1436 */
1437 if (private->force_narrow_mode)
1438 private->narrow_mode = TRUE;
1439 else
1440 private->narrow_mode = FALSE;
1441
1442 /* If the rectangle is being modified we want the center on
1443 * fixed_center to be at the center of the currently existing
1444 * rectangle, otherwise we want the point where the user clicked
1445 * to be the center on fixed_center.
1446 */
1447 private->center_x_on_fixed_center = snapped_x;
1448 private->center_y_on_fixed_center = snapped_y;
1449
1450 /* When the user toggles modifier keys, we want to keep track of
1451 * what coordinates the "other side" should have. If we are
1452 * creating a rectangle, use the current mouse coordinates as
1453 * the coordinate of the "other side", otherwise use the
1454 * immediate "other side" for that.
1455 */
1456 private->other_side_x = snapped_x;
1457 private->other_side_y = snapped_y;
1458 }
1459 else
1460 {
1461 /* This rectangle was not created from scratch. */
1462 private->is_new = FALSE;
1463
1464 private->center_x_on_fixed_center = (private->x1 + private->x2) / 2;
1465 private->center_y_on_fixed_center = (private->y1 + private->y2) / 2;
1466
1467 gimp_tool_rectangle_get_other_side_coord (rectangle,
1468 &private->other_side_x,
1469 &private->other_side_y);
1470 }
1471
1472 gimp_tool_rectangle_update_int_rect (rectangle);
1473
1474 /* Is the rectangle being rubber-banded? */
1475 private->rect_adjusting = gimp_tool_rectangle_rect_adjusting_func (rectangle);
1476
1477 gimp_tool_rectangle_changed (widget);
1478
1479 gimp_tool_rectangle_update_status (rectangle);
1480
1481 return 1;
1482 }
1483
1484 void
gimp_tool_rectangle_button_release(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonReleaseType release_type)1485 gimp_tool_rectangle_button_release (GimpToolWidget *widget,
1486 const GimpCoords *coords,
1487 guint32 time,
1488 GdkModifierType state,
1489 GimpButtonReleaseType release_type)
1490 {
1491 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1492 GimpToolRectanglePrivate *private = rectangle->private;
1493 gint response = 0;
1494
1495 gimp_tool_widget_set_status (widget, NULL);
1496
1497 /* On button release, we are not rubber-banding the rectangle any longer. */
1498 private->rect_adjusting = FALSE;
1499
1500 gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
1501
1502 switch (release_type)
1503 {
1504 case GIMP_BUTTON_RELEASE_NO_MOTION:
1505 /* If the first created rectangle was not expanded, halt the
1506 * tool...
1507 */
1508 if (gimp_tool_rectangle_rectangle_is_first (rectangle))
1509 {
1510 response = GIMP_TOOL_WIDGET_RESPONSE_CANCEL;
1511 break;
1512 }
1513
1514 /* ...else fallthrough and treat a long click without movement
1515 * like a normal change
1516 */
1517
1518 case GIMP_BUTTON_RELEASE_NORMAL:
1519 /* If a normal click-drag-release actually created a rectangle
1520 * with content...
1521 */
1522 if (private->x1 != private->x2 &&
1523 private->y1 != private->y2)
1524 {
1525 gimp_tool_rectangle_change_complete (rectangle);
1526 break;
1527 }
1528
1529 /* ...else fallthrough and undo the operation, we can't have
1530 * zero-extent rectangles
1531 */
1532
1533 case GIMP_BUTTON_RELEASE_CANCEL:
1534 private->x1 = private->saved_x1;
1535 private->y1 = private->saved_y1;
1536 private->x2 = private->saved_x2;
1537 private->y2 = private->saved_y2;
1538
1539 gimp_tool_rectangle_update_int_rect (rectangle);
1540
1541 /* If the first created rectangle was canceled, halt the tool */
1542 if (gimp_tool_rectangle_rectangle_is_first (rectangle))
1543 response = GIMP_TOOL_WIDGET_RESPONSE_CANCEL;
1544 break;
1545
1546 case GIMP_BUTTON_RELEASE_CLICK:
1547 /* When a dead area is clicked, don't execute. */
1548 if (private->function != GIMP_TOOL_RECTANGLE_DEAD)
1549 response = GIMP_TOOL_WIDGET_RESPONSE_CONFIRM;
1550 break;
1551 }
1552
1553 /* We must update this. */
1554 gimp_tool_rectangle_recalculate_center_xy (rectangle);
1555
1556 gimp_tool_rectangle_update_options (rectangle);
1557
1558 gimp_tool_rectangle_changed (widget);
1559
1560 private->is_first = FALSE;
1561
1562 /* emit response at the end, so everything is up to date even if
1563 * a signal handler decides hot to shut down the rectangle
1564 */
1565 if (response != 0)
1566 gimp_tool_widget_response (widget, response);
1567 }
1568
1569 void
gimp_tool_rectangle_motion(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state)1570 gimp_tool_rectangle_motion (GimpToolWidget *widget,
1571 const GimpCoords *coords,
1572 guint32 time,
1573 GdkModifierType state)
1574 {
1575 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1576 GimpToolRectanglePrivate *private = rectangle->private;
1577 gdouble snapped_x;
1578 gdouble snapped_y;
1579 gint snap_x, snap_y;
1580
1581 /* Motion events should be ignored when we're just waiting for the
1582 * button release event to execute or if the user has grabbed a dead
1583 * area of the rectangle.
1584 */
1585 if (private->function == GIMP_TOOL_RECTANGLE_EXECUTING ||
1586 private->function == GIMP_TOOL_RECTANGLE_DEAD)
1587 return;
1588
1589 /* Handle snapping. */
1590 gimp_tool_widget_get_snap_offsets (widget, &snap_x, &snap_y, NULL, NULL);
1591
1592 snapped_x = coords->x + snap_x;
1593 snapped_y = coords->y + snap_y;
1594
1595 /* This is the core rectangle shape updating function: */
1596 gimp_tool_rectangle_update_with_coord (rectangle, snapped_x, snapped_y);
1597
1598 gimp_tool_rectangle_update_status (rectangle);
1599
1600 if (private->function == GIMP_TOOL_RECTANGLE_CREATING)
1601 {
1602 GimpRectangleFunction function = GIMP_TOOL_RECTANGLE_CREATING;
1603 gdouble dx = snapped_x - private->lastx;
1604 gdouble dy = snapped_y - private->lasty;
1605
1606 /* When the user starts to move the cursor, set the current
1607 * function to one of the corner-grabbed functions, depending on
1608 * in what direction the user starts dragging the rectangle.
1609 */
1610 if (dx < 0)
1611 {
1612 function = (dy < 0 ?
1613 GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT :
1614 GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT);
1615 }
1616 else if (dx > 0)
1617 {
1618 function = (dy < 0 ?
1619 GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT :
1620 GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT);
1621 }
1622 else if (dy < 0)
1623 {
1624 function = (dx < 0 ?
1625 GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT :
1626 GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT);
1627 }
1628 else if (dy > 0)
1629 {
1630 function = (dx < 0 ?
1631 GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT :
1632 GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT);
1633 }
1634
1635 gimp_tool_rectangle_set_function (rectangle, function);
1636
1637 if (private->fixed_rule_active &&
1638 private->fixed_rule == GIMP_RECTANGLE_FIXED_SIZE)
1639 {
1640 /* For fixed size, set the function to moving immediately since the
1641 * rectangle can not be resized anyway.
1642 */
1643
1644 /* We fake a coord update to get the right size. */
1645 gimp_tool_rectangle_update_with_coord (rectangle,
1646 snapped_x,
1647 snapped_y);
1648
1649 gimp_tool_widget_set_snap_offsets (widget,
1650 -(private->x2 - private->x1) / 2,
1651 -(private->y2 - private->y1) / 2,
1652 private->x2 - private->x1,
1653 private->y2 - private->y1);
1654
1655 gimp_tool_rectangle_set_function (rectangle,
1656 GIMP_TOOL_RECTANGLE_MOVING);
1657 }
1658 }
1659
1660 gimp_tool_rectangle_update_options (rectangle);
1661
1662 private->lastx = snapped_x;
1663 private->lasty = snapped_y;
1664 }
1665
1666 GimpHit
gimp_tool_rectangle_hit(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)1667 gimp_tool_rectangle_hit (GimpToolWidget *widget,
1668 const GimpCoords *coords,
1669 GdkModifierType state,
1670 gboolean proximity)
1671 {
1672 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1673 GimpToolRectanglePrivate *private = rectangle->private;
1674 GimpRectangleFunction function;
1675
1676 if (private->suppress_updates)
1677 {
1678 function = gimp_tool_rectangle_get_function (rectangle);
1679 }
1680 else
1681 {
1682 function = gimp_tool_rectangle_calc_function (rectangle,
1683 coords, proximity);
1684 }
1685
1686 switch (function)
1687 {
1688 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
1689 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
1690 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
1691 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
1692 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
1693 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
1694 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
1695 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
1696 return GIMP_HIT_DIRECT;
1697
1698 case GIMP_TOOL_RECTANGLE_CREATING:
1699 case GIMP_TOOL_RECTANGLE_MOVING:
1700 return GIMP_HIT_INDIRECT;
1701
1702 case GIMP_TOOL_RECTANGLE_DEAD:
1703 case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
1704 case GIMP_TOOL_RECTANGLE_EXECUTING:
1705 default:
1706 return GIMP_HIT_NONE;
1707 }
1708 }
1709
1710 void
gimp_tool_rectangle_hover(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)1711 gimp_tool_rectangle_hover (GimpToolWidget *widget,
1712 const GimpCoords *coords,
1713 GdkModifierType state,
1714 gboolean proximity)
1715 {
1716 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1717 GimpToolRectanglePrivate *private = rectangle->private;
1718 GimpRectangleFunction function;
1719
1720 if (private->suppress_updates)
1721 {
1722 private->suppress_updates--;
1723 return;
1724 }
1725
1726 function = gimp_tool_rectangle_calc_function (rectangle, coords, proximity);
1727
1728 gimp_tool_rectangle_set_function (rectangle, function);
1729 }
1730
1731 static void
gimp_tool_rectangle_leave_notify(GimpToolWidget * widget)1732 gimp_tool_rectangle_leave_notify (GimpToolWidget *widget)
1733 {
1734 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1735
1736 gimp_tool_rectangle_set_function (rectangle, GIMP_TOOL_RECTANGLE_DEAD);
1737
1738 GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
1739 }
1740
1741 static gboolean
gimp_tool_rectangle_key_press(GimpToolWidget * widget,GdkEventKey * kevent)1742 gimp_tool_rectangle_key_press (GimpToolWidget *widget,
1743 GdkEventKey *kevent)
1744 {
1745 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1746 GimpToolRectanglePrivate *private = rectangle->private;
1747 gint dx = 0;
1748 gint dy = 0;
1749 gdouble new_x = 0;
1750 gdouble new_y = 0;
1751
1752 switch (kevent->keyval)
1753 {
1754 case GDK_KEY_Up:
1755 dy = -1;
1756 break;
1757 case GDK_KEY_Left:
1758 dx = -1;
1759 break;
1760 case GDK_KEY_Right:
1761 dx = 1;
1762 break;
1763 case GDK_KEY_Down:
1764 dy = 1;
1765 break;
1766
1767 default:
1768 return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
1769 }
1770
1771 /* If the shift key is down, move by an accelerated increment */
1772 if (kevent->state & gimp_get_extend_selection_mask ())
1773 {
1774 dx *= ARROW_VELOCITY;
1775 dy *= ARROW_VELOCITY;
1776 }
1777
1778 gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
1779
1780 /* Resize the rectangle if the mouse is over a handle, otherwise move it */
1781 switch (private->function)
1782 {
1783 case GIMP_TOOL_RECTANGLE_MOVING:
1784 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
1785 new_x = private->x1 + dx;
1786 new_y = private->y1 + dy;
1787 private->lastx = new_x;
1788 private->lasty = new_y;
1789 break;
1790 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
1791 new_x = private->x2 + dx;
1792 new_y = private->y1 + dy;
1793 private->lastx = new_x;
1794 private->lasty = new_y;
1795 break;
1796 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
1797 new_x = private->x1 + dx;
1798 new_y = private->y2 + dy;
1799 private->lastx = new_x;
1800 private->lasty = new_y;
1801 break;
1802 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
1803 new_x = private->x2 + dx;
1804 new_y = private->y2 + dy;
1805 private->lastx = new_x;
1806 private->lasty = new_y;
1807 break;
1808 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
1809 new_x = private->x1 + dx;
1810 private->lastx = new_x;
1811 break;
1812 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
1813 new_x = private->x2 + dx;
1814 private->lastx = new_x;
1815 break;
1816 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
1817 new_y = private->y1 + dy;
1818 private->lasty = new_y;
1819 break;
1820 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
1821 new_y = private->y2 + dy;
1822 private->lasty = new_y;
1823 break;
1824
1825 default:
1826 return TRUE;
1827 }
1828
1829 gimp_tool_rectangle_update_with_coord (rectangle, new_x, new_y);
1830
1831 gimp_tool_rectangle_recalculate_center_xy (rectangle);
1832
1833 gimp_tool_rectangle_update_options (rectangle);
1834
1835 gimp_tool_rectangle_change_complete (rectangle);
1836
1837 /* Evil hack to suppress oper updates. We do this because we don't
1838 * want the rectangle tool to change function while the rectangle
1839 * is being resized or moved using the keyboard.
1840 */
1841 private->suppress_updates = 2;
1842
1843 return TRUE;
1844 }
1845
1846 static void
gimp_tool_rectangle_motion_modifier(GimpToolWidget * widget,GdkModifierType key,gboolean press,GdkModifierType state)1847 gimp_tool_rectangle_motion_modifier (GimpToolWidget *widget,
1848 GdkModifierType key,
1849 gboolean press,
1850 GdkModifierType state)
1851 {
1852 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1853 GimpToolRectanglePrivate *private = rectangle->private;
1854 gboolean button1_down;
1855
1856 button1_down = (state & GDK_BUTTON1_MASK);
1857
1858 if (key == gimp_get_extend_selection_mask ())
1859 {
1860 #if 0
1861 /* Here we want to handle manually when to update the rectangle, so we
1862 * don't want gimp_tool_rectangle_options_notify to do anything.
1863 */
1864 g_signal_handlers_block_by_func (options,
1865 gimp_tool_rectangle_options_notify,
1866 rectangle);
1867 #endif
1868
1869 g_object_set (rectangle,
1870 "fixed-rule-active", ! private->fixed_rule_active,
1871 NULL);
1872
1873 #if 0
1874 g_signal_handlers_unblock_by_func (options,
1875 gimp_tool_rectangle_options_notify,
1876 rectangle);
1877 #endif
1878
1879 /* Only change the shape if the mouse is still down (i.e. the user is
1880 * still editing the rectangle.
1881 */
1882 if (button1_down)
1883 {
1884 if (! private->fixed_rule_active)
1885 {
1886 /* Reset anchor point */
1887 gimp_tool_rectangle_set_other_side_coord (rectangle,
1888 private->other_side_x,
1889 private->other_side_y);
1890 }
1891
1892 gimp_tool_rectangle_update_with_coord (rectangle,
1893 private->lastx,
1894 private->lasty);
1895 }
1896 }
1897
1898 if (key == gimp_get_toggle_behavior_mask ())
1899 {
1900 g_object_set (rectangle,
1901 "fixed-center", ! private->fixed_center,
1902 NULL);
1903
1904 if (private->fixed_center)
1905 {
1906 gimp_tool_rectangle_update_with_coord (rectangle,
1907 private->lastx,
1908 private->lasty);
1909
1910 /* Only emit the rectangle-changed signal if the button is
1911 * not down. If it is down, the signal will and shall be
1912 * emitted on _button_release instead.
1913 */
1914 if (! button1_down)
1915 {
1916 gimp_tool_rectangle_change_complete (rectangle);
1917 }
1918 }
1919 else if (button1_down)
1920 {
1921 /* If we are leaving fixed_center mode we want to set the
1922 * "other side" where it should be. Don't do anything if we
1923 * came here by a mouse-click though, since then the user
1924 * has confirmed the shape and we don't want to modify it
1925 * afterwards.
1926 */
1927 gimp_tool_rectangle_set_other_side_coord (rectangle,
1928 private->other_side_x,
1929 private->other_side_y);
1930 }
1931 }
1932
1933 gimp_tool_rectangle_update_options (rectangle);
1934 }
1935
1936 static gboolean
gimp_tool_rectangle_get_cursor(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,GimpCursorType * cursor,GimpToolCursorType * tool_cursor,GimpCursorModifier * modifier)1937 gimp_tool_rectangle_get_cursor (GimpToolWidget *widget,
1938 const GimpCoords *coords,
1939 GdkModifierType state,
1940 GimpCursorType *cursor,
1941 GimpToolCursorType *tool_cursor,
1942 GimpCursorModifier *modifier)
1943 {
1944 GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
1945 GimpToolRectanglePrivate *private = rectangle->private;
1946
1947 switch (private->function)
1948 {
1949 case GIMP_TOOL_RECTANGLE_CREATING:
1950 *cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
1951 break;
1952 case GIMP_TOOL_RECTANGLE_MOVING:
1953 *cursor = GIMP_CURSOR_MOVE;
1954 *modifier = GIMP_CURSOR_MODIFIER_MOVE;
1955 break;
1956 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
1957 *cursor = GIMP_CURSOR_CORNER_TOP_LEFT;
1958 break;
1959 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
1960 *cursor = GIMP_CURSOR_CORNER_TOP_RIGHT;
1961 break;
1962 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
1963 *cursor = GIMP_CURSOR_CORNER_BOTTOM_LEFT;
1964 break;
1965 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
1966 *cursor = GIMP_CURSOR_CORNER_BOTTOM_RIGHT;
1967 break;
1968 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
1969 *cursor = GIMP_CURSOR_SIDE_LEFT;
1970 break;
1971 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
1972 *cursor = GIMP_CURSOR_SIDE_RIGHT;
1973 break;
1974 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
1975 *cursor = GIMP_CURSOR_SIDE_TOP;
1976 break;
1977 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
1978 *cursor = GIMP_CURSOR_SIDE_BOTTOM;
1979 break;
1980
1981 default:
1982 return FALSE;
1983 }
1984
1985 return TRUE;
1986 }
1987
1988 static void
gimp_tool_rectangle_change_complete(GimpToolRectangle * rectangle)1989 gimp_tool_rectangle_change_complete (GimpToolRectangle *rectangle)
1990 {
1991 g_signal_emit (rectangle, rectangle_signals[CHANGE_COMPLETE], 0);
1992 }
1993
1994 static void
gimp_tool_rectangle_update_options(GimpToolRectangle * rectangle)1995 gimp_tool_rectangle_update_options (GimpToolRectangle *rectangle)
1996 {
1997 GimpToolRectanglePrivate *private = rectangle->private;
1998 gdouble x1, y1;
1999 gdouble x2, y2;
2000
2001 gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
2002
2003 #if 0
2004 g_signal_handlers_block_by_func (options,
2005 gimp_tool_rectangle_options_notify,
2006 rect_tool);
2007 #endif
2008
2009 g_object_freeze_notify (G_OBJECT (rectangle));
2010
2011 if (! FEQUAL (private->x, x1))
2012 g_object_set (rectangle, "x", x1, NULL);
2013
2014 if (! FEQUAL (private->y, y1))
2015 g_object_set (rectangle, "y", y1, NULL);
2016
2017 if (! FEQUAL (private->width, x2 - x1))
2018 g_object_set (rectangle, "width", x2 - x1, NULL);
2019
2020 if (! FEQUAL (private->height, y2 - y1))
2021 g_object_set (rectangle, "height", y2 - y1, NULL);
2022
2023 g_object_thaw_notify (G_OBJECT (rectangle));
2024
2025 #if 0
2026 g_signal_handlers_unblock_by_func (options,
2027 gimp_tool_rectangle_options_notify,
2028 rect_tool);
2029 #endif
2030 }
2031
2032 static void
gimp_tool_rectangle_update_handle_sizes(GimpToolRectangle * rectangle)2033 gimp_tool_rectangle_update_handle_sizes (GimpToolRectangle *rectangle)
2034 {
2035 GimpToolRectanglePrivate *private = rectangle->private;
2036 GimpDisplayShell *shell;
2037 gint visible_rectangle_width;
2038 gint visible_rectangle_height;
2039 gint rectangle_width;
2040 gint rectangle_height;
2041 gdouble pub_x1, pub_y1;
2042 gdouble pub_x2, pub_y2;
2043
2044 shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
2045
2046 gimp_tool_rectangle_get_public_rect (rectangle,
2047 &pub_x1, &pub_y1, &pub_x2, &pub_y2);
2048 {
2049 /* Calculate rectangles of the selection rectangle and the display
2050 * shell, with origin at (0, 0) of image, and in screen coordinate
2051 * scale.
2052 */
2053 gint x1 = pub_x1 * shell->scale_x;
2054 gint y1 = pub_y1 * shell->scale_y;
2055 gint w1 = (pub_x2 - pub_x1) * shell->scale_x;
2056 gint h1 = (pub_y2 - pub_y1) * shell->scale_y;
2057
2058 gint x2, y2, w2, h2;
2059
2060 gimp_display_shell_scroll_get_scaled_viewport (shell, &x2, &y2, &w2, &h2);
2061
2062 rectangle_width = w1;
2063 rectangle_height = h1;
2064
2065 /* Handle size calculations shall be based on the visible part of
2066 * the rectangle, so calculate the size for the visible rectangle
2067 * by intersecting with the viewport rectangle.
2068 */
2069 gimp_rectangle_intersect (x1, y1,
2070 w1, h1,
2071 x2, y2,
2072 w2, h2,
2073 NULL, NULL,
2074 &visible_rectangle_width,
2075 &visible_rectangle_height);
2076
2077 /* Determine if we are in narrow-mode or not. */
2078 if (private->force_narrow_mode)
2079 private->narrow_mode = TRUE;
2080 else
2081 private->narrow_mode = (visible_rectangle_width < NARROW_MODE_THRESHOLD ||
2082 visible_rectangle_height < NARROW_MODE_THRESHOLD);
2083 }
2084
2085 if (private->narrow_mode)
2086 {
2087 /* Corner handles always have the same (on-screen) size in
2088 * narrow-mode.
2089 */
2090 private->corner_handle_w = NARROW_MODE_HANDLE_SIZE;
2091 private->corner_handle_h = NARROW_MODE_HANDLE_SIZE;
2092
2093 private->top_and_bottom_handle_w = CLAMP (rectangle_width,
2094 MIN (rectangle_width - 2,
2095 NARROW_MODE_HANDLE_SIZE),
2096 G_MAXINT);
2097 private->left_and_right_handle_h = CLAMP (rectangle_height,
2098 MIN (rectangle_height - 2,
2099 NARROW_MODE_HANDLE_SIZE),
2100 G_MAXINT);
2101 }
2102 else
2103 {
2104 /* Calculate and clamp corner handle size. */
2105
2106 private->corner_handle_w = visible_rectangle_width / 4;
2107 private->corner_handle_h = visible_rectangle_height / 4;
2108
2109 private->corner_handle_w = CLAMP (private->corner_handle_w,
2110 MIN_HANDLE_SIZE,
2111 MAX_HANDLE_SIZE);
2112 private->corner_handle_h = CLAMP (private->corner_handle_h,
2113 MIN_HANDLE_SIZE,
2114 MAX_HANDLE_SIZE);
2115
2116 /* Calculate and clamp side handle size. */
2117
2118 private->top_and_bottom_handle_w = rectangle_width - 3 * private->corner_handle_w;
2119 private->left_and_right_handle_h = rectangle_height - 3 * private->corner_handle_h;
2120
2121 private->top_and_bottom_handle_w = CLAMP (private->top_and_bottom_handle_w,
2122 MIN_HANDLE_SIZE,
2123 G_MAXINT);
2124 private->left_and_right_handle_h = CLAMP (private->left_and_right_handle_h,
2125 MIN_HANDLE_SIZE,
2126 G_MAXINT);
2127 }
2128 }
2129
2130 static void
gimp_tool_rectangle_update_status(GimpToolRectangle * rectangle)2131 gimp_tool_rectangle_update_status (GimpToolRectangle *rectangle)
2132 {
2133 GimpToolRectanglePrivate *private = rectangle->private;
2134 gdouble x1, y1, x2, y2;
2135
2136 gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
2137
2138 if (private->function == GIMP_TOOL_RECTANGLE_MOVING)
2139 {
2140 gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (rectangle),
2141 _("Position: "),
2142 x1, ", ", y1,
2143 NULL);
2144 }
2145 else
2146 {
2147 gchar *aspect_text = NULL;
2148 gint width = x2 - x1;
2149 gint height = y2 - y1;
2150
2151 if (width > 0.0 && height > 0.0)
2152 {
2153 aspect_text = g_strdup_printf (" (%.2f:1)",
2154 (gdouble) width / (gdouble) height);
2155 }
2156
2157 gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (rectangle),
2158 private->status_title,
2159 width, " × ", height,
2160 aspect_text);
2161 g_free (aspect_text);
2162 }
2163 }
2164
2165 static void
gimp_tool_rectangle_synthesize_motion(GimpToolRectangle * rectangle,gint function,gdouble new_x,gdouble new_y)2166 gimp_tool_rectangle_synthesize_motion (GimpToolRectangle *rectangle,
2167 gint function,
2168 gdouble new_x,
2169 gdouble new_y)
2170 {
2171 GimpToolRectanglePrivate *private = rectangle->private;
2172 GimpRectangleFunction old_function;
2173
2174 /* We don't want to synthesize motions if the tool control is active
2175 * since that means the mouse button is down and the rectangle will
2176 * get updated in _motion anyway. The reason we want to prevent this
2177 * function from executing is that is emits the
2178 * rectangle-changed-complete signal which we don't want in the
2179 * middle of a rectangle change.
2180 *
2181 * In addition to that, we don't want to synthesize a motion if
2182 * there is no pending rectangle because that doesn't make any
2183 * sense.
2184 */
2185 if (private->rect_adjusting)
2186 return;
2187
2188 old_function = private->function;
2189
2190 gimp_tool_rectangle_set_function (rectangle, function);
2191
2192 gimp_tool_rectangle_update_with_coord (rectangle, new_x, new_y);
2193
2194 /* We must update this. */
2195 gimp_tool_rectangle_recalculate_center_xy (rectangle);
2196
2197 gimp_tool_rectangle_update_options (rectangle);
2198
2199 gimp_tool_rectangle_set_function (rectangle, old_function);
2200
2201 gimp_tool_rectangle_change_complete (rectangle);
2202 }
2203
2204 static void
swap_doubles(gdouble * i,gdouble * j)2205 swap_doubles (gdouble *i,
2206 gdouble *j)
2207 {
2208 gdouble tmp;
2209
2210 tmp = *i;
2211 *i = *j;
2212 *j = tmp;
2213 }
2214
2215 static GimpRectangleFunction
gimp_tool_rectangle_calc_function(GimpToolRectangle * rectangle,const GimpCoords * coords,gboolean proximity)2216 gimp_tool_rectangle_calc_function (GimpToolRectangle *rectangle,
2217 const GimpCoords *coords,
2218 gboolean proximity)
2219 {
2220 if (! proximity)
2221 {
2222 return GIMP_TOOL_RECTANGLE_DEAD;
2223 }
2224 else if (gimp_tool_rectangle_coord_outside (rectangle, coords))
2225 {
2226 /* The cursor is outside of the rectangle, clicking should
2227 * create a new rectangle.
2228 */
2229 return GIMP_TOOL_RECTANGLE_CREATING;
2230 }
2231 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2232 coords,
2233 GIMP_HANDLE_ANCHOR_NORTH_WEST))
2234 {
2235 return GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
2236 }
2237 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2238 coords,
2239 GIMP_HANDLE_ANCHOR_SOUTH_EAST))
2240 {
2241 return GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
2242 }
2243 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2244 coords,
2245 GIMP_HANDLE_ANCHOR_NORTH_EAST))
2246 {
2247 return GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
2248 }
2249 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2250 coords,
2251 GIMP_HANDLE_ANCHOR_SOUTH_WEST))
2252 {
2253 return GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
2254 }
2255 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2256 coords,
2257 GIMP_HANDLE_ANCHOR_WEST))
2258 {
2259 return GIMP_TOOL_RECTANGLE_RESIZING_LEFT;
2260 }
2261 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2262 coords,
2263 GIMP_HANDLE_ANCHOR_EAST))
2264 {
2265 return GIMP_TOOL_RECTANGLE_RESIZING_RIGHT;
2266 }
2267 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2268 coords,
2269 GIMP_HANDLE_ANCHOR_NORTH))
2270 {
2271 return GIMP_TOOL_RECTANGLE_RESIZING_TOP;
2272 }
2273 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2274 coords,
2275 GIMP_HANDLE_ANCHOR_SOUTH))
2276 {
2277 return GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
2278 }
2279 else if (gimp_tool_rectangle_coord_on_handle (rectangle,
2280 coords,
2281 GIMP_HANDLE_ANCHOR_CENTER))
2282 {
2283 return GIMP_TOOL_RECTANGLE_MOVING;
2284 }
2285 else
2286 {
2287 return GIMP_TOOL_RECTANGLE_DEAD;
2288 }
2289 }
2290
2291 /* gimp_tool_rectangle_check_function() is needed to deal with
2292 * situations where the user drags a corner or edge across one of the
2293 * existing edges, thereby changing its function. Ugh.
2294 */
2295 static void
gimp_tool_rectangle_check_function(GimpToolRectangle * rectangle)2296 gimp_tool_rectangle_check_function (GimpToolRectangle *rectangle)
2297
2298 {
2299 GimpToolRectanglePrivate *private = rectangle->private;
2300 GimpRectangleFunction function = private->function;
2301
2302 if (private->x2 < private->x1)
2303 {
2304 swap_doubles (&private->x1, &private->x2);
2305
2306 switch (function)
2307 {
2308 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2309 function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
2310 break;
2311 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2312 function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
2313 break;
2314 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2315 function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
2316 break;
2317 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2318 function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
2319 break;
2320 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
2321 function = GIMP_TOOL_RECTANGLE_RESIZING_RIGHT;
2322 break;
2323 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
2324 function = GIMP_TOOL_RECTANGLE_RESIZING_LEFT;
2325 break;
2326 /* avoid annoying warnings about unhandled enums */
2327 default:
2328 break;
2329 }
2330 }
2331
2332 if (private->y2 < private->y1)
2333 {
2334 swap_doubles (&private->y1, &private->y2);
2335
2336 switch (function)
2337 {
2338 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2339 function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
2340 break;
2341 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2342 function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
2343 break;
2344 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2345 function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
2346 break;
2347 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2348 function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
2349 break;
2350 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
2351 function = GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
2352 break;
2353 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
2354 function = GIMP_TOOL_RECTANGLE_RESIZING_TOP;
2355 break;
2356 default:
2357 break;
2358 }
2359 }
2360
2361 gimp_tool_rectangle_set_function (rectangle, function);
2362 }
2363
2364 /**
2365 * gimp_tool_rectangle_coord_outside:
2366 *
2367 * Returns: %TRUE if the coord is outside the rectangle bounds
2368 * including any outside handles.
2369 */
2370 static gboolean
gimp_tool_rectangle_coord_outside(GimpToolRectangle * rectangle,const GimpCoords * coord)2371 gimp_tool_rectangle_coord_outside (GimpToolRectangle *rectangle,
2372 const GimpCoords *coord)
2373 {
2374 GimpToolRectanglePrivate *private = rectangle->private;
2375 GimpDisplayShell *shell;
2376 gboolean narrow_mode = private->narrow_mode;
2377 gdouble x1, y1, x2, y2;
2378 gdouble x1_b, y1_b, x2_b, y2_b;
2379
2380 shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
2381
2382 gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
2383
2384 x1_b = x1 - (narrow_mode ? private->corner_handle_w / shell->scale_x : 0);
2385 x2_b = x2 + (narrow_mode ? private->corner_handle_w / shell->scale_x : 0);
2386 y1_b = y1 - (narrow_mode ? private->corner_handle_h / shell->scale_y : 0);
2387 y2_b = y2 + (narrow_mode ? private->corner_handle_h / shell->scale_y : 0);
2388
2389 return (coord->x < x1_b ||
2390 coord->x > x2_b ||
2391 coord->y < y1_b ||
2392 coord->y > y2_b);
2393 }
2394
2395 /**
2396 * gimp_tool_rectangle_coord_on_handle:
2397 *
2398 * Returns: %TRUE if the coord is on the handle that corresponds to
2399 * @anchor.
2400 */
2401 static gboolean
gimp_tool_rectangle_coord_on_handle(GimpToolRectangle * rectangle,const GimpCoords * coords,GimpHandleAnchor anchor)2402 gimp_tool_rectangle_coord_on_handle (GimpToolRectangle *rectangle,
2403 const GimpCoords *coords,
2404 GimpHandleAnchor anchor)
2405 {
2406 GimpToolRectanglePrivate *private = rectangle->private;
2407 GimpDisplayShell *shell;
2408 gdouble x1, y1, x2, y2;
2409 gdouble rect_w, rect_h;
2410 gdouble handle_x = 0;
2411 gdouble handle_y = 0;
2412 gdouble handle_width = 0;
2413 gdouble handle_height = 0;
2414 gint narrow_mode_x_dir = 0;
2415 gint narrow_mode_y_dir = 0;
2416
2417 shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
2418
2419 gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
2420
2421 rect_w = x2 - x1;
2422 rect_h = y2 - y1;
2423
2424 switch (anchor)
2425 {
2426 case GIMP_HANDLE_ANCHOR_NORTH_WEST:
2427 handle_x = x1;
2428 handle_y = y1;
2429 handle_width = private->corner_handle_w;
2430 handle_height = private->corner_handle_h;
2431
2432 narrow_mode_x_dir = -1;
2433 narrow_mode_y_dir = -1;
2434 break;
2435
2436 case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
2437 handle_x = x2;
2438 handle_y = y2;
2439 handle_width = private->corner_handle_w;
2440 handle_height = private->corner_handle_h;
2441
2442 narrow_mode_x_dir = 1;
2443 narrow_mode_y_dir = 1;
2444 break;
2445
2446 case GIMP_HANDLE_ANCHOR_NORTH_EAST:
2447 handle_x = x2;
2448 handle_y = y1;
2449 handle_width = private->corner_handle_w;
2450 handle_height = private->corner_handle_h;
2451
2452 narrow_mode_x_dir = 1;
2453 narrow_mode_y_dir = -1;
2454 break;
2455
2456 case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
2457 handle_x = x1;
2458 handle_y = y2;
2459 handle_width = private->corner_handle_w;
2460 handle_height = private->corner_handle_h;
2461
2462 narrow_mode_x_dir = -1;
2463 narrow_mode_y_dir = 1;
2464 break;
2465
2466 case GIMP_HANDLE_ANCHOR_WEST:
2467 handle_x = x1;
2468 handle_y = y1 + rect_h / 2;
2469 handle_width = private->corner_handle_w;
2470 handle_height = private->left_and_right_handle_h;
2471
2472 narrow_mode_x_dir = -1;
2473 narrow_mode_y_dir = 0;
2474 break;
2475
2476 case GIMP_HANDLE_ANCHOR_EAST:
2477 handle_x = x2;
2478 handle_y = y1 + rect_h / 2;
2479 handle_width = private->corner_handle_w;
2480 handle_height = private->left_and_right_handle_h;
2481
2482 narrow_mode_x_dir = 1;
2483 narrow_mode_y_dir = 0;
2484 break;
2485
2486 case GIMP_HANDLE_ANCHOR_NORTH:
2487 handle_x = x1 + rect_w / 2;
2488 handle_y = y1;
2489 handle_width = private->top_and_bottom_handle_w;
2490 handle_height = private->corner_handle_h;
2491
2492 narrow_mode_x_dir = 0;
2493 narrow_mode_y_dir = -1;
2494 break;
2495
2496 case GIMP_HANDLE_ANCHOR_SOUTH:
2497 handle_x = x1 + rect_w / 2;
2498 handle_y = y2;
2499 handle_width = private->top_and_bottom_handle_w;
2500 handle_height = private->corner_handle_h;
2501
2502 narrow_mode_x_dir = 0;
2503 narrow_mode_y_dir = 1;
2504 break;
2505
2506 case GIMP_HANDLE_ANCHOR_CENTER:
2507 handle_x = x1 + rect_w / 2;
2508 handle_y = y1 + rect_h / 2;
2509
2510 if (private->narrow_mode)
2511 {
2512 handle_width = rect_w * shell->scale_x;
2513 handle_height = rect_h * shell->scale_y;
2514 }
2515 else
2516 {
2517 handle_width = rect_w * shell->scale_x - private->corner_handle_w * 2;
2518 handle_height = rect_h * shell->scale_y - private->corner_handle_h * 2;
2519 }
2520
2521 narrow_mode_x_dir = 0;
2522 narrow_mode_y_dir = 0;
2523 break;
2524 }
2525
2526 if (private->narrow_mode)
2527 {
2528 handle_x += narrow_mode_x_dir * handle_width / shell->scale_x;
2529 handle_y += narrow_mode_y_dir * handle_height / shell->scale_y;
2530 }
2531
2532 return gimp_canvas_item_on_handle (private->rectangle,
2533 coords->x, coords->y,
2534 GIMP_HANDLE_SQUARE,
2535 handle_x, handle_y,
2536 handle_width, handle_height,
2537 anchor);
2538 }
2539
2540 static GimpHandleAnchor
gimp_tool_rectangle_get_anchor(GimpRectangleFunction function)2541 gimp_tool_rectangle_get_anchor (GimpRectangleFunction function)
2542 {
2543 switch (function)
2544 {
2545 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2546 return GIMP_HANDLE_ANCHOR_NORTH_WEST;
2547
2548 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2549 return GIMP_HANDLE_ANCHOR_NORTH_EAST;
2550
2551 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2552 return GIMP_HANDLE_ANCHOR_SOUTH_WEST;
2553
2554 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2555 return GIMP_HANDLE_ANCHOR_SOUTH_EAST;
2556
2557 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
2558 return GIMP_HANDLE_ANCHOR_WEST;
2559
2560 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
2561 return GIMP_HANDLE_ANCHOR_EAST;
2562
2563 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
2564 return GIMP_HANDLE_ANCHOR_NORTH;
2565
2566 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
2567 return GIMP_HANDLE_ANCHOR_SOUTH;
2568
2569 default:
2570 return GIMP_HANDLE_ANCHOR_CENTER;
2571 }
2572 }
2573
2574 static gboolean
gimp_tool_rectangle_rect_rubber_banding_func(GimpToolRectangle * rectangle)2575 gimp_tool_rectangle_rect_rubber_banding_func (GimpToolRectangle *rectangle)
2576 {
2577 GimpToolRectanglePrivate *private = rectangle->private;
2578
2579 switch (private->function)
2580 {
2581 case GIMP_TOOL_RECTANGLE_CREATING:
2582 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
2583 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
2584 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
2585 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
2586 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2587 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2588 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2589 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2590 case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
2591 return TRUE;
2592
2593 case GIMP_TOOL_RECTANGLE_MOVING:
2594 case GIMP_TOOL_RECTANGLE_DEAD:
2595 default:
2596 break;
2597 }
2598
2599 return FALSE;
2600 }
2601
2602 /**
2603 * gimp_tool_rectangle_rect_adjusting_func:
2604 * @rectangle:
2605 *
2606 * Returns: %TRUE if the current function is a rectangle adjusting
2607 * function.
2608 */
2609 static gboolean
gimp_tool_rectangle_rect_adjusting_func(GimpToolRectangle * rectangle)2610 gimp_tool_rectangle_rect_adjusting_func (GimpToolRectangle *rectangle)
2611 {
2612 GimpToolRectanglePrivate *private = rectangle->private;
2613
2614 return (gimp_tool_rectangle_rect_rubber_banding_func (rectangle) ||
2615 private->function == GIMP_TOOL_RECTANGLE_MOVING);
2616 }
2617
2618 /**
2619 * gimp_tool_rectangle_get_other_side:
2620 * @rectangle: A #GimpToolRectangle.
2621 * @other_x: Pointer to double of the other-x double.
2622 * @other_y: Pointer to double of the other-y double.
2623 *
2624 * Calculates pointers to member variables that hold the coordinates
2625 * of the opposite side (either the opposite corner or literally the
2626 * opposite side), based on the current function. The opposite of a
2627 * corner needs two coordinates, the opposite of a side only needs
2628 * one.
2629 */
2630 static void
gimp_tool_rectangle_get_other_side(GimpToolRectangle * rectangle,gdouble ** other_x,gdouble ** other_y)2631 gimp_tool_rectangle_get_other_side (GimpToolRectangle *rectangle,
2632 gdouble **other_x,
2633 gdouble **other_y)
2634 {
2635 GimpToolRectanglePrivate *private = rectangle->private;
2636
2637 switch (private->function)
2638 {
2639 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2640 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2641 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
2642 *other_x = &private->x1;
2643 break;
2644
2645 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2646 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2647 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
2648 *other_x = &private->x2;
2649 break;
2650
2651 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
2652 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
2653 default:
2654 *other_x = NULL;
2655 break;
2656 }
2657
2658 switch (private->function)
2659 {
2660 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2661 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2662 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
2663 *other_y = &private->y1;
2664 break;
2665
2666 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2667 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2668 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
2669 *other_y = &private->y2;
2670 break;
2671
2672 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
2673 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
2674 default:
2675 *other_y = NULL;
2676 break;
2677 }
2678 }
2679
2680 static void
gimp_tool_rectangle_get_other_side_coord(GimpToolRectangle * rectangle,gdouble * other_side_x,gdouble * other_side_y)2681 gimp_tool_rectangle_get_other_side_coord (GimpToolRectangle *rectangle,
2682 gdouble *other_side_x,
2683 gdouble *other_side_y)
2684 {
2685 gdouble *other_x = NULL;
2686 gdouble *other_y = NULL;
2687
2688 gimp_tool_rectangle_get_other_side (rectangle, &other_x, &other_y);
2689
2690 if (other_x)
2691 *other_side_x = *other_x;
2692 if (other_y)
2693 *other_side_y = *other_y;
2694 }
2695
2696 static void
gimp_tool_rectangle_set_other_side_coord(GimpToolRectangle * rectangle,gdouble other_side_x,gdouble other_side_y)2697 gimp_tool_rectangle_set_other_side_coord (GimpToolRectangle *rectangle,
2698 gdouble other_side_x,
2699 gdouble other_side_y)
2700 {
2701 gdouble *other_x = NULL;
2702 gdouble *other_y = NULL;
2703
2704 gimp_tool_rectangle_get_other_side (rectangle, &other_x, &other_y);
2705
2706 if (other_x)
2707 *other_x = other_side_x;
2708 if (other_y)
2709 *other_y = other_side_y;
2710
2711 gimp_tool_rectangle_check_function (rectangle);
2712
2713 gimp_tool_rectangle_update_int_rect (rectangle);
2714 }
2715
2716 /**
2717 * gimp_tool_rectangle_apply_coord:
2718 * @param: A #GimpToolRectangle.
2719 * @coord_x: X of coord.
2720 * @coord_y: Y of coord.
2721 *
2722 * Adjust the rectangle to the new position specified by passed
2723 * coordinate, taking fixed_center into account, which means it
2724 * expands the rectangle around the center point.
2725 */
2726 static void
gimp_tool_rectangle_apply_coord(GimpToolRectangle * rectangle,gdouble coord_x,gdouble coord_y)2727 gimp_tool_rectangle_apply_coord (GimpToolRectangle *rectangle,
2728 gdouble coord_x,
2729 gdouble coord_y)
2730 {
2731 GimpToolRectanglePrivate *private = rectangle->private;
2732
2733 if (private->function == GIMP_TOOL_RECTANGLE_MOVING)
2734 {
2735 /* Preserve width and height while moving the grab-point to where the
2736 * cursor is.
2737 */
2738 gdouble w = private->x2 - private->x1;
2739 gdouble h = private->y2 - private->y1;
2740
2741 private->x1 = coord_x;
2742 private->y1 = coord_y;
2743
2744 private->x2 = private->x1 + w;
2745 private->y2 = private->y1 + h;
2746
2747 /* We are done already. */
2748 return;
2749 }
2750
2751 switch (private->function)
2752 {
2753 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2754 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2755 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
2756 private->x1 = coord_x;
2757
2758 if (private->fixed_center)
2759 private->x2 = 2 * private->center_x_on_fixed_center - private->x1;
2760
2761 break;
2762
2763 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2764 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2765 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
2766 private->x2 = coord_x;
2767
2768 if (private->fixed_center)
2769 private->x1 = 2 * private->center_x_on_fixed_center - private->x2;
2770
2771 break;
2772
2773 default:
2774 break;
2775 }
2776
2777 switch (private->function)
2778 {
2779 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2780 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2781 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
2782 private->y1 = coord_y;
2783
2784 if (private->fixed_center)
2785 private->y2 = 2 * private->center_y_on_fixed_center - private->y1;
2786
2787 break;
2788
2789 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2790 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2791 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
2792 private->y2 = coord_y;
2793
2794 if (private->fixed_center)
2795 private->y1 = 2 * private->center_y_on_fixed_center - private->y2;
2796
2797 break;
2798
2799 default:
2800 break;
2801 }
2802 }
2803
2804 static void
gimp_tool_rectangle_setup_snap_offsets(GimpToolRectangle * rectangle,const GimpCoords * coords)2805 gimp_tool_rectangle_setup_snap_offsets (GimpToolRectangle *rectangle,
2806 const GimpCoords *coords)
2807 {
2808 GimpToolWidget *widget = GIMP_TOOL_WIDGET (rectangle);
2809 GimpToolRectanglePrivate *private = rectangle->private;
2810 gdouble x1, y1, x2, y2;
2811 gdouble coord_x, coord_y;
2812
2813 gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
2814 gimp_tool_rectangle_adjust_coord (rectangle,
2815 coords->x, coords->y,
2816 &coord_x, &coord_y);
2817
2818 switch (private->function)
2819 {
2820 case GIMP_TOOL_RECTANGLE_CREATING:
2821 gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
2822 break;
2823
2824 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
2825 gimp_tool_widget_set_snap_offsets (widget,
2826 x1 - coord_x,
2827 y1 - coord_y,
2828 0, 0);
2829 break;
2830
2831 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
2832 gimp_tool_widget_set_snap_offsets (widget,
2833 x2 - coord_x,
2834 y1 - coord_y,
2835 0, 0);
2836 break;
2837
2838 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
2839 gimp_tool_widget_set_snap_offsets (widget,
2840 x1 - coord_x,
2841 y2 - coord_y,
2842 0, 0);
2843 break;
2844
2845 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
2846 gimp_tool_widget_set_snap_offsets (widget,
2847 x2 - coord_x,
2848 y2 - coord_y,
2849 0, 0);
2850 break;
2851
2852 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
2853 gimp_tool_widget_set_snap_offsets (widget,
2854 x1 - coord_x, 0,
2855 0, 0);
2856 break;
2857
2858 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
2859 gimp_tool_widget_set_snap_offsets (widget,
2860 x2 - coord_x, 0,
2861 0, 0);
2862 break;
2863
2864 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
2865 gimp_tool_widget_set_snap_offsets (widget,
2866 0, y1 - coord_y,
2867 0, 0);
2868 break;
2869
2870 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
2871 gimp_tool_widget_set_snap_offsets (widget,
2872 0, y2 - coord_y,
2873 0, 0);
2874 break;
2875
2876 case GIMP_TOOL_RECTANGLE_MOVING:
2877 gimp_tool_widget_set_snap_offsets (widget,
2878 x1 - coord_x,
2879 y1 - coord_y,
2880 x2 - x1,
2881 y2 - y1);
2882 break;
2883
2884 default:
2885 break;
2886 }
2887 }
2888
2889 /**
2890 * gimp_tool_rectangle_clamp:
2891 * @rectangle: A #GimpToolRectangle.
2892 * @clamped_sides: Where to put contrainment information.
2893 * @constraint: Constraint to use.
2894 * @symmetrically: Whether or not to clamp symmetrically.
2895 *
2896 * Clamps rectangle inside specified bounds, providing information of
2897 * where clamping was done. Can also clamp symmetrically.
2898 */
2899 static void
gimp_tool_rectangle_clamp(GimpToolRectangle * rectangle,ClampedSide * clamped_sides,GimpRectangleConstraint constraint,gboolean symmetrically)2900 gimp_tool_rectangle_clamp (GimpToolRectangle *rectangle,
2901 ClampedSide *clamped_sides,
2902 GimpRectangleConstraint constraint,
2903 gboolean symmetrically)
2904 {
2905 gimp_tool_rectangle_clamp_width (rectangle,
2906 clamped_sides,
2907 constraint,
2908 symmetrically);
2909
2910 gimp_tool_rectangle_clamp_height (rectangle,
2911 clamped_sides,
2912 constraint,
2913 symmetrically);
2914 }
2915
2916 /**
2917 * gimp_tool_rectangle_clamp_width:
2918 * @rectangle: A #GimpToolRectangle.
2919 * @clamped_sides: Where to put contrainment information.
2920 * @constraint: Constraint to use.
2921 * @symmetrically: Whether or not to clamp symmetrically.
2922 *
2923 * Clamps height of rectangle. Set symmetrically to true when using
2924 * for fixed_center:ed rectangles, since that will clamp symmetrically
2925 * which is just what is needed.
2926 *
2927 * When this function constrains, it puts what it constrains in
2928 * @constraint. This information is essential when an aspect ratio is
2929 * to be applied.
2930 */
2931 static void
gimp_tool_rectangle_clamp_width(GimpToolRectangle * rectangle,ClampedSide * clamped_sides,GimpRectangleConstraint constraint,gboolean symmetrically)2932 gimp_tool_rectangle_clamp_width (GimpToolRectangle *rectangle,
2933 ClampedSide *clamped_sides,
2934 GimpRectangleConstraint constraint,
2935 gboolean symmetrically)
2936 {
2937 GimpToolRectanglePrivate *private = rectangle->private;
2938 gint min_x;
2939 gint max_x;
2940
2941 if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
2942 return;
2943
2944 gimp_tool_rectangle_get_constraints (rectangle,
2945 &min_x, NULL,
2946 &max_x, NULL,
2947 constraint);
2948 if (private->x1 < min_x)
2949 {
2950 gdouble dx = min_x - private->x1;
2951
2952 private->x1 += dx;
2953
2954 if (symmetrically)
2955 private->x2 -= dx;
2956
2957 if (private->x2 < min_x)
2958 private->x2 = min_x;
2959
2960 if (clamped_sides)
2961 *clamped_sides |= CLAMPED_LEFT;
2962 }
2963
2964 if (private->x2 > max_x)
2965 {
2966 gdouble dx = max_x - private->x2;
2967
2968 private->x2 += dx;
2969
2970 if (symmetrically)
2971 private->x1 -= dx;
2972
2973 if (private->x1 > max_x)
2974 private->x1 = max_x;
2975
2976 if (clamped_sides)
2977 *clamped_sides |= CLAMPED_RIGHT;
2978 }
2979 }
2980
2981 /**
2982 * gimp_tool_rectangle_clamp_height:
2983 * @rectangle: A #GimpToolRectangle.
2984 * @clamped_sides: Where to put contrainment information.
2985 * @constraint: Constraint to use.
2986 * @symmetrically: Whether or not to clamp symmetrically.
2987 *
2988 * Clamps height of rectangle. Set symmetrically to true when using for
2989 * fixed_center:ed rectangles, since that will clamp symmetrically which is just
2990 * what is needed.
2991 *
2992 * When this function constrains, it puts what it constrains in
2993 * @constraint. This information is essential when an aspect ratio is to be
2994 * applied.
2995 */
2996 static void
gimp_tool_rectangle_clamp_height(GimpToolRectangle * rectangle,ClampedSide * clamped_sides,GimpRectangleConstraint constraint,gboolean symmetrically)2997 gimp_tool_rectangle_clamp_height (GimpToolRectangle *rectangle,
2998 ClampedSide *clamped_sides,
2999 GimpRectangleConstraint constraint,
3000 gboolean symmetrically)
3001 {
3002 GimpToolRectanglePrivate *private = rectangle->private;
3003 gint min_y;
3004 gint max_y;
3005
3006 if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
3007 return;
3008
3009 gimp_tool_rectangle_get_constraints (rectangle,
3010 NULL, &min_y,
3011 NULL, &max_y,
3012 constraint);
3013 if (private->y1 < min_y)
3014 {
3015 gdouble dy = min_y - private->y1;
3016
3017 private->y1 += dy;
3018
3019 if (symmetrically)
3020 private->y2 -= dy;
3021
3022 if (private->y2 < min_y)
3023 private->y2 = min_y;
3024
3025 if (clamped_sides)
3026 *clamped_sides |= CLAMPED_TOP;
3027 }
3028
3029 if (private->y2 > max_y)
3030 {
3031 gdouble dy = max_y - private->y2;
3032
3033 private->y2 += dy;
3034
3035 if (symmetrically)
3036 private->y1 -= dy;
3037
3038 if (private->y1 > max_y)
3039 private->y1 = max_y;
3040
3041 if (clamped_sides)
3042 *clamped_sides |= CLAMPED_BOTTOM;
3043 }
3044 }
3045
3046 /**
3047 * gimp_tool_rectangle_keep_inside:
3048 * @rectangle: A #GimpToolRectangle.
3049 *
3050 * If the rectangle is outside of the canvas, move it into it. If the rectangle is
3051 * larger than the canvas in any direction, make it fill the canvas in that direction.
3052 */
3053 static void
gimp_tool_rectangle_keep_inside(GimpToolRectangle * rectangle,GimpRectangleConstraint constraint)3054 gimp_tool_rectangle_keep_inside (GimpToolRectangle *rectangle,
3055 GimpRectangleConstraint constraint)
3056 {
3057 gimp_tool_rectangle_keep_inside_horizontally (rectangle, constraint);
3058 gimp_tool_rectangle_keep_inside_vertically (rectangle, constraint);
3059 }
3060
3061 /**
3062 * gimp_tool_rectangle_keep_inside_horizontally:
3063 * @rectangle: A #GimpToolRectangle.
3064 * @constraint: Constraint to use.
3065 *
3066 * If the rectangle is outside of the given constraint horizontally, move it
3067 * inside. If it is too big to fit inside, make it just as big as the width
3068 * limit.
3069 */
3070 static void
gimp_tool_rectangle_keep_inside_horizontally(GimpToolRectangle * rectangle,GimpRectangleConstraint constraint)3071 gimp_tool_rectangle_keep_inside_horizontally (GimpToolRectangle *rectangle,
3072 GimpRectangleConstraint constraint)
3073 {
3074 GimpToolRectanglePrivate *private = rectangle->private;
3075 gint min_x;
3076 gint max_x;
3077
3078 if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
3079 return;
3080
3081 gimp_tool_rectangle_get_constraints (rectangle,
3082 &min_x, NULL,
3083 &max_x, NULL,
3084 constraint);
3085
3086 if (max_x - min_x < private->x2 - private->x1)
3087 {
3088 private->x1 = min_x;
3089 private->x2 = max_x;
3090 }
3091 else
3092 {
3093 if (private->x1 < min_x)
3094 {
3095 gdouble dx = min_x - private->x1;
3096
3097 private->x1 += dx;
3098 private->x2 += dx;
3099 }
3100 if (private->x2 > max_x)
3101 {
3102 gdouble dx = max_x - private->x2;
3103
3104 private->x1 += dx;
3105 private->x2 += dx;
3106 }
3107 }
3108 }
3109
3110 /**
3111 * gimp_tool_rectangle_keep_inside_vertically:
3112 * @rectangle: A #GimpToolRectangle.
3113 * @constraint: Constraint to use.
3114 *
3115 * If the rectangle is outside of the given constraint vertically,
3116 * move it inside. If it is too big to fit inside, make it just as big
3117 * as the width limit.
3118 */
3119 static void
gimp_tool_rectangle_keep_inside_vertically(GimpToolRectangle * rectangle,GimpRectangleConstraint constraint)3120 gimp_tool_rectangle_keep_inside_vertically (GimpToolRectangle *rectangle,
3121 GimpRectangleConstraint constraint)
3122 {
3123 GimpToolRectanglePrivate *private = rectangle->private;
3124 gint min_y;
3125 gint max_y;
3126
3127 if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
3128 return;
3129
3130 gimp_tool_rectangle_get_constraints (rectangle,
3131 NULL, &min_y,
3132 NULL, &max_y,
3133 constraint);
3134
3135 if (max_y - min_y < private->y2 - private->y1)
3136 {
3137 private->y1 = min_y;
3138 private->y2 = max_y;
3139 }
3140 else
3141 {
3142 if (private->y1 < min_y)
3143 {
3144 gdouble dy = min_y - private->y1;
3145
3146 private->y1 += dy;
3147 private->y2 += dy;
3148 }
3149 if (private->y2 > max_y)
3150 {
3151 gdouble dy = max_y - private->y2;
3152
3153 private->y1 += dy;
3154 private->y2 += dy;
3155 }
3156 }
3157 }
3158
3159 /**
3160 * gimp_tool_rectangle_apply_fixed_width:
3161 * @rectangle: A #GimpToolRectangle.
3162 * @constraint: Constraint to use.
3163 * @width:
3164 *
3165 * Makes the rectangle have a fixed_width, following the constrainment
3166 * rules of fixed widths as well. Please refer to the rectangle tools
3167 * spec.
3168 */
3169 static void
gimp_tool_rectangle_apply_fixed_width(GimpToolRectangle * rectangle,GimpRectangleConstraint constraint,gdouble width)3170 gimp_tool_rectangle_apply_fixed_width (GimpToolRectangle *rectangle,
3171 GimpRectangleConstraint constraint,
3172 gdouble width)
3173 {
3174 GimpToolRectanglePrivate *private = rectangle->private;
3175
3176 switch (private->function)
3177 {
3178 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
3179 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
3180 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
3181 /* We always want to center around fixed_center here, since we want the
3182 * anchor point to be directly on the opposite side.
3183 */
3184 private->x1 = private->center_x_on_fixed_center -
3185 width / 2;
3186 private->x2 = private->x1 + width;
3187 break;
3188
3189 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
3190 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
3191 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
3192 /* We always want to center around fixed_center here, since we want the
3193 * anchor point to be directly on the opposite side.
3194 */
3195 private->x1 = private->center_x_on_fixed_center -
3196 width / 2;
3197 private->x2 = private->x1 + width;
3198 break;
3199
3200 default:
3201 break;
3202 }
3203
3204 /* Width shall be kept even after constraints, so we move the
3205 * rectangle sideways rather than adjusting a side.
3206 */
3207 gimp_tool_rectangle_keep_inside_horizontally (rectangle, constraint);
3208 }
3209
3210 /**
3211 * gimp_tool_rectangle_apply_fixed_height:
3212 * @rectangle: A #GimpToolRectangle.
3213 * @constraint: Constraint to use.
3214 * @height:
3215 *
3216 * Makes the rectangle have a fixed_height, following the
3217 * constrainment rules of fixed heights as well. Please refer to the
3218 * rectangle tools spec.
3219 */
3220 static void
gimp_tool_rectangle_apply_fixed_height(GimpToolRectangle * rectangle,GimpRectangleConstraint constraint,gdouble height)3221 gimp_tool_rectangle_apply_fixed_height (GimpToolRectangle *rectangle,
3222 GimpRectangleConstraint constraint,
3223 gdouble height)
3224
3225 {
3226 GimpToolRectanglePrivate *private = rectangle->private;
3227
3228 switch (private->function)
3229 {
3230 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
3231 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
3232 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
3233 /* We always want to center around fixed_center here, since we
3234 * want the anchor point to be directly on the opposite side.
3235 */
3236 private->y1 = private->center_y_on_fixed_center -
3237 height / 2;
3238 private->y2 = private->y1 + height;
3239 break;
3240
3241 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
3242 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
3243 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
3244 /* We always want to center around fixed_center here, since we
3245 * want the anchor point to be directly on the opposite side.
3246 */
3247 private->y1 = private->center_y_on_fixed_center -
3248 height / 2;
3249 private->y2 = private->y1 + height;
3250 break;
3251
3252 default:
3253 break;
3254 }
3255
3256 /* Width shall be kept even after constraints, so we move the
3257 * rectangle sideways rather than adjusting a side.
3258 */
3259 gimp_tool_rectangle_keep_inside_vertically (rectangle, constraint);
3260 }
3261
3262 /**
3263 * gimp_tool_rectangle_apply_aspect:
3264 * @rectangle: A #GimpToolRectangle.
3265 * @aspect: The desired aspect.
3266 * @clamped_sides: Bitfield of sides that have been clamped.
3267 *
3268 * Adjust the rectangle to the desired aspect.
3269 *
3270 * Sometimes, a side must not be moved outwards, for example if a the
3271 * RIGHT side has been clamped previously, we must not move the RIGHT
3272 * side to the right, since that would violate the constraint
3273 * again. The clamped_sides bitfield keeps track of sides that have
3274 * previously been clamped.
3275 *
3276 * If fixed_center is used, the function adjusts the aspect by
3277 * symmetrically adjusting the left and right, or top and bottom side.
3278 */
3279 static void
gimp_tool_rectangle_apply_aspect(GimpToolRectangle * rectangle,gdouble aspect,gint clamped_sides)3280 gimp_tool_rectangle_apply_aspect (GimpToolRectangle *rectangle,
3281 gdouble aspect,
3282 gint clamped_sides)
3283 {
3284 GimpToolRectanglePrivate *private = rectangle->private;
3285 gdouble current_w;
3286 gdouble current_h;
3287 gdouble current_aspect;
3288 SideToResize side_to_resize = SIDE_TO_RESIZE_NONE;
3289
3290 current_w = private->x2 - private->x1;
3291 current_h = private->y2 - private->y1;
3292
3293 current_aspect = (gdouble) current_w / (gdouble) current_h;
3294
3295 /* Do we have to do anything? */
3296 if (current_aspect == aspect)
3297 return;
3298
3299 if (private->fixed_center)
3300 {
3301 /* We may only adjust the sides symmetrically to get desired aspect. */
3302 if (current_aspect > aspect)
3303 {
3304 /* We prefer to use top and bottom (since that will make the
3305 * cursor remain on the rectangle edge), unless that is what
3306 * the user has grabbed.
3307 */
3308 switch (private->function)
3309 {
3310 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
3311 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
3312 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
3313 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
3314 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
3315 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
3316 if (! (clamped_sides & CLAMPED_TOP) &&
3317 ! (clamped_sides & CLAMPED_BOTTOM))
3318 {
3319 side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
3320 }
3321 else
3322 {
3323 side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
3324 }
3325 break;
3326
3327 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
3328 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
3329 default:
3330 side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
3331 break;
3332 }
3333 }
3334 else /* (current_aspect < aspect) */
3335 {
3336 /* We prefer to use left and right (since that will make the
3337 * cursor remain on the rectangle edge), unless that is what
3338 * the user has grabbed.
3339 */
3340 switch (private->function)
3341 {
3342 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
3343 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
3344 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
3345 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
3346 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
3347 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
3348 if (! (clamped_sides & CLAMPED_LEFT) &&
3349 ! (clamped_sides & CLAMPED_RIGHT))
3350 {
3351 side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
3352 }
3353 else
3354 {
3355 side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
3356 }
3357 break;
3358
3359 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
3360 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
3361 default:
3362 side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
3363 break;
3364 }
3365 }
3366 }
3367 else if (current_aspect > aspect)
3368 {
3369 /* We can safely pick LEFT or RIGHT, since using those sides
3370 * will make the rectangle smaller, so we don't need to check
3371 * for clamped_sides. We may only use TOP and BOTTOM if not
3372 * those sides have been clamped, since using them will make the
3373 * rectangle bigger.
3374 */
3375 switch (private->function)
3376 {
3377 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
3378 if (! (clamped_sides & CLAMPED_TOP))
3379 side_to_resize = SIDE_TO_RESIZE_TOP;
3380 else
3381 side_to_resize = SIDE_TO_RESIZE_LEFT;
3382 break;
3383
3384 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
3385 if (! (clamped_sides & CLAMPED_TOP))
3386 side_to_resize = SIDE_TO_RESIZE_TOP;
3387 else
3388 side_to_resize = SIDE_TO_RESIZE_RIGHT;
3389 break;
3390
3391 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
3392 if (! (clamped_sides & CLAMPED_BOTTOM))
3393 side_to_resize = SIDE_TO_RESIZE_BOTTOM;
3394 else
3395 side_to_resize = SIDE_TO_RESIZE_LEFT;
3396 break;
3397
3398 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
3399 if (! (clamped_sides & CLAMPED_BOTTOM))
3400 side_to_resize = SIDE_TO_RESIZE_BOTTOM;
3401 else
3402 side_to_resize = SIDE_TO_RESIZE_RIGHT;
3403 break;
3404
3405 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
3406 if (! (clamped_sides & CLAMPED_TOP) &&
3407 ! (clamped_sides & CLAMPED_BOTTOM))
3408 side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
3409 else
3410 side_to_resize = SIDE_TO_RESIZE_LEFT;
3411 break;
3412
3413 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
3414 if (! (clamped_sides & CLAMPED_TOP) &&
3415 ! (clamped_sides & CLAMPED_BOTTOM))
3416 side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
3417 else
3418 side_to_resize = SIDE_TO_RESIZE_RIGHT;
3419 break;
3420
3421 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
3422 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
3423 side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
3424 break;
3425
3426 case GIMP_TOOL_RECTANGLE_MOVING:
3427 default:
3428 if (! (clamped_sides & CLAMPED_BOTTOM))
3429 side_to_resize = SIDE_TO_RESIZE_BOTTOM;
3430 else if (! (clamped_sides & CLAMPED_RIGHT))
3431 side_to_resize = SIDE_TO_RESIZE_RIGHT;
3432 else if (! (clamped_sides & CLAMPED_TOP))
3433 side_to_resize = SIDE_TO_RESIZE_TOP;
3434 else if (! (clamped_sides & CLAMPED_LEFT))
3435 side_to_resize = SIDE_TO_RESIZE_LEFT;
3436 break;
3437 }
3438 }
3439 else /* (current_aspect < aspect) */
3440 {
3441 /* We can safely pick TOP or BOTTOM, since using those sides
3442 * will make the rectangle smaller, so we don't need to check
3443 * for clamped_sides. We may only use LEFT and RIGHT if not
3444 * those sides have been clamped, since using them will make the
3445 * rectangle bigger.
3446 */
3447 switch (private->function)
3448 {
3449 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
3450 if (! (clamped_sides & CLAMPED_LEFT))
3451 side_to_resize = SIDE_TO_RESIZE_LEFT;
3452 else
3453 side_to_resize = SIDE_TO_RESIZE_TOP;
3454 break;
3455
3456 case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
3457 if (! (clamped_sides & CLAMPED_RIGHT))
3458 side_to_resize = SIDE_TO_RESIZE_RIGHT;
3459 else
3460 side_to_resize = SIDE_TO_RESIZE_TOP;
3461 break;
3462
3463 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
3464 if (! (clamped_sides & CLAMPED_LEFT))
3465 side_to_resize = SIDE_TO_RESIZE_LEFT;
3466 else
3467 side_to_resize = SIDE_TO_RESIZE_BOTTOM;
3468 break;
3469
3470 case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
3471 if (! (clamped_sides & CLAMPED_RIGHT))
3472 side_to_resize = SIDE_TO_RESIZE_RIGHT;
3473 else
3474 side_to_resize = SIDE_TO_RESIZE_BOTTOM;
3475 break;
3476
3477 case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
3478 if (! (clamped_sides & CLAMPED_LEFT) &&
3479 ! (clamped_sides & CLAMPED_RIGHT))
3480 side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
3481 else
3482 side_to_resize = SIDE_TO_RESIZE_TOP;
3483 break;
3484
3485 case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
3486 if (! (clamped_sides & CLAMPED_LEFT) &&
3487 ! (clamped_sides & CLAMPED_RIGHT))
3488 side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
3489 else
3490 side_to_resize = SIDE_TO_RESIZE_BOTTOM;
3491 break;
3492
3493 case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
3494 case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
3495 side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
3496 break;
3497
3498 case GIMP_TOOL_RECTANGLE_MOVING:
3499 default:
3500 if (! (clamped_sides & CLAMPED_BOTTOM))
3501 side_to_resize = SIDE_TO_RESIZE_BOTTOM;
3502 else if (! (clamped_sides & CLAMPED_RIGHT))
3503 side_to_resize = SIDE_TO_RESIZE_RIGHT;
3504 else if (! (clamped_sides & CLAMPED_TOP))
3505 side_to_resize = SIDE_TO_RESIZE_TOP;
3506 else if (! (clamped_sides & CLAMPED_LEFT))
3507 side_to_resize = SIDE_TO_RESIZE_LEFT;
3508 break;
3509 }
3510 }
3511
3512 /* We now know what side(s) we should resize, so now we just solve
3513 * the aspect equation for that side(s).
3514 */
3515 switch (side_to_resize)
3516 {
3517 case SIDE_TO_RESIZE_NONE:
3518 return;
3519
3520 case SIDE_TO_RESIZE_LEFT:
3521 private->x1 = private->x2 - aspect * current_h;
3522 break;
3523
3524 case SIDE_TO_RESIZE_RIGHT:
3525 private->x2 = private->x1 + aspect * current_h;
3526 break;
3527
3528 case SIDE_TO_RESIZE_TOP:
3529 private->y1 = private->y2 - current_w / aspect;
3530 break;
3531
3532 case SIDE_TO_RESIZE_BOTTOM:
3533 private->y2 = private->y1 + current_w / aspect;
3534 break;
3535
3536 case SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY:
3537 {
3538 gdouble correct_h = current_w / aspect;
3539
3540 private->y1 = private->center_y_on_fixed_center - correct_h / 2;
3541 private->y2 = private->y1 + correct_h;
3542 }
3543 break;
3544
3545 case SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY:
3546 {
3547 gdouble correct_w = current_h * aspect;
3548
3549 private->x1 = private->center_x_on_fixed_center - correct_w / 2;
3550 private->x2 = private->x1 + correct_w;
3551 }
3552 break;
3553 }
3554 }
3555
3556 /**
3557 * gimp_tool_rectangle_update_with_coord:
3558 * @rectangle: A #GimpToolRectangle.
3559 * @new_x: New X-coordinate in the context of the current function.
3560 * @new_y: New Y-coordinate in the context of the current function.
3561 *
3562 * The core rectangle adjustment function. It updates the rectangle
3563 * for the passed cursor coordinate, taking current function and tool
3564 * options into account. It also updates the current
3565 * private->function if necessary.
3566 */
3567 static void
gimp_tool_rectangle_update_with_coord(GimpToolRectangle * rectangle,gdouble new_x,gdouble new_y)3568 gimp_tool_rectangle_update_with_coord (GimpToolRectangle *rectangle,
3569 gdouble new_x,
3570 gdouble new_y)
3571 {
3572 GimpToolRectanglePrivate *private = rectangle->private;
3573
3574 /* Move the corner or edge the user currently has grabbed. */
3575 gimp_tool_rectangle_apply_coord (rectangle, new_x, new_y);
3576
3577 /* Update private->function. The function changes if the user
3578 * "flips" the rectangle.
3579 */
3580 gimp_tool_rectangle_check_function (rectangle);
3581
3582 /* Clamp the rectangle if necessary */
3583 gimp_tool_rectangle_handle_general_clamping (rectangle);
3584
3585 /* If the rectangle is being moved, do not run through any further
3586 * rectangle adjusting functions since it's shape should not change
3587 * then.
3588 */
3589 if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
3590 {
3591 gimp_tool_rectangle_apply_fixed_rule (rectangle);
3592 }
3593
3594 gimp_tool_rectangle_update_int_rect (rectangle);
3595 }
3596
3597 static void
gimp_tool_rectangle_apply_fixed_rule(GimpToolRectangle * rectangle)3598 gimp_tool_rectangle_apply_fixed_rule (GimpToolRectangle *rectangle)
3599 {
3600 GimpToolRectanglePrivate *private = rectangle->private;
3601 GimpRectangleConstraint constraint_to_use;
3602 GimpDisplayShell *shell;
3603 GimpImage *image;
3604
3605 shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
3606 image = gimp_display_get_image (shell->display);
3607
3608 /* Calculate what constraint to use when needed. */
3609 constraint_to_use = gimp_tool_rectangle_get_constraint (rectangle);
3610
3611 if (private->fixed_rule_active &&
3612 private->fixed_rule == GIMP_RECTANGLE_FIXED_ASPECT)
3613 {
3614 gdouble aspect;
3615
3616 aspect = CLAMP (private->aspect_numerator /
3617 private->aspect_denominator,
3618 1.0 / gimp_image_get_height (image),
3619 gimp_image_get_width (image));
3620
3621 if (constraint_to_use == GIMP_RECTANGLE_CONSTRAIN_NONE)
3622 {
3623 gimp_tool_rectangle_apply_aspect (rectangle,
3624 aspect,
3625 CLAMPED_NONE);
3626 }
3627 else
3628 {
3629 if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
3630 {
3631 ClampedSide clamped_sides = CLAMPED_NONE;
3632
3633 gimp_tool_rectangle_apply_aspect (rectangle,
3634 aspect,
3635 clamped_sides);
3636
3637 /* After we have applied aspect, we might have taken the
3638 * rectangle outside of constraint, so clamp and apply
3639 * aspect again. We will get the right result this time,
3640 * since 'clamped_sides' will be setup correctly now.
3641 */
3642 gimp_tool_rectangle_clamp (rectangle,
3643 &clamped_sides,
3644 constraint_to_use,
3645 private->fixed_center);
3646
3647 gimp_tool_rectangle_apply_aspect (rectangle,
3648 aspect,
3649 clamped_sides);
3650 }
3651 else
3652 {
3653 gimp_tool_rectangle_apply_aspect (rectangle,
3654 aspect,
3655 CLAMPED_NONE);
3656
3657 gimp_tool_rectangle_keep_inside (rectangle,
3658 constraint_to_use);
3659 }
3660 }
3661 }
3662 else if (private->fixed_rule_active &&
3663 private->fixed_rule == GIMP_RECTANGLE_FIXED_SIZE)
3664 {
3665 gimp_tool_rectangle_apply_fixed_width (rectangle,
3666 constraint_to_use,
3667 private->desired_fixed_size_width);
3668 gimp_tool_rectangle_apply_fixed_height (rectangle,
3669 constraint_to_use,
3670 private->desired_fixed_size_height);
3671 }
3672 else if (private->fixed_rule_active &&
3673 private->fixed_rule == GIMP_RECTANGLE_FIXED_WIDTH)
3674 {
3675 gimp_tool_rectangle_apply_fixed_width (rectangle,
3676 constraint_to_use,
3677 private->desired_fixed_width);
3678 }
3679 else if (private->fixed_rule_active &&
3680 private->fixed_rule == GIMP_RECTANGLE_FIXED_HEIGHT)
3681 {
3682 gimp_tool_rectangle_apply_fixed_height (rectangle,
3683 constraint_to_use,
3684 private->desired_fixed_height);
3685 }
3686 }
3687
3688 /**
3689 * gimp_tool_rectangle_get_constraints:
3690 * @rectangle: A #GimpToolRectangle.
3691 * @min_x:
3692 * @min_y:
3693 * @max_x:
3694 * @max_y: Pointers of where to put constraints. NULL allowed.
3695 * @constraint: Whether to return image or layer constraints.
3696 *
3697 * Calculates constraint coordinates for image or layer.
3698 */
3699 static void
gimp_tool_rectangle_get_constraints(GimpToolRectangle * rectangle,gint * min_x,gint * min_y,gint * max_x,gint * max_y,GimpRectangleConstraint constraint)3700 gimp_tool_rectangle_get_constraints (GimpToolRectangle *rectangle,
3701 gint *min_x,
3702 gint *min_y,
3703 gint *max_x,
3704 gint *max_y,
3705 GimpRectangleConstraint constraint)
3706 {
3707 GimpDisplayShell *shell;
3708 GimpImage *image;
3709 gint min_x_dummy;
3710 gint min_y_dummy;
3711 gint max_x_dummy;
3712 gint max_y_dummy;
3713
3714 shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
3715 image = gimp_display_get_image (shell->display);
3716
3717 if (! min_x) min_x = &min_x_dummy;
3718 if (! min_y) min_y = &min_y_dummy;
3719 if (! max_x) max_x = &max_x_dummy;
3720 if (! max_y) max_y = &max_y_dummy;
3721
3722 *min_x = 0;
3723 *min_y = 0;
3724 *max_x = 0;
3725 *max_y = 0;
3726
3727 switch (constraint)
3728 {
3729 case GIMP_RECTANGLE_CONSTRAIN_IMAGE:
3730 if (image)
3731 {
3732 *min_x = 0;
3733 *min_y = 0;
3734 *max_x = gimp_image_get_width (image);
3735 *max_y = gimp_image_get_height (image);
3736 }
3737 break;
3738
3739 case GIMP_RECTANGLE_CONSTRAIN_DRAWABLE:
3740 if (image)
3741 {
3742 GimpItem *item = GIMP_ITEM (gimp_image_get_active_drawable (image));
3743
3744 if (item)
3745 {
3746 gimp_item_get_offset (item, min_x, min_y);
3747 *max_x = *min_x + gimp_item_get_width (item);
3748 *max_y = *min_y + gimp_item_get_height (item);
3749 }
3750 }
3751 break;
3752
3753 default:
3754 g_warning ("Invalid rectangle constraint.\n");
3755 return;
3756 }
3757 }
3758
3759 /**
3760 * gimp_tool_rectangle_handle_general_clamping:
3761 * @rectangle: A #GimpToolRectangle.
3762 *
3763 * Make sure that constraints are applied to the rectangle, either by
3764 * manually doing it, or by looking at the rectangle tool options and
3765 * concluding it will be done later.
3766 */
3767 static void
gimp_tool_rectangle_handle_general_clamping(GimpToolRectangle * rectangle)3768 gimp_tool_rectangle_handle_general_clamping (GimpToolRectangle *rectangle)
3769 {
3770 GimpToolRectanglePrivate *private = rectangle->private;
3771 GimpRectangleConstraint constraint;
3772
3773 constraint = gimp_tool_rectangle_get_constraint (rectangle);
3774
3775 /* fixed_aspect takes care of clamping by it self, so just return in
3776 * case that is in use. Also return if no constraints should be
3777 * enforced.
3778 */
3779 if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
3780 return;
3781
3782 if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
3783 {
3784 gimp_tool_rectangle_clamp (rectangle,
3785 NULL,
3786 constraint,
3787 private->fixed_center);
3788 }
3789 else
3790 {
3791 gimp_tool_rectangle_keep_inside (rectangle, constraint);
3792 }
3793 }
3794
3795 /**
3796 * gimp_tool_rectangle_update_int_rect:
3797 * @rectangle:
3798 *
3799 * Update integer representation of rectangle.
3800 **/
3801 static void
gimp_tool_rectangle_update_int_rect(GimpToolRectangle * rectangle)3802 gimp_tool_rectangle_update_int_rect (GimpToolRectangle *rectangle)
3803 {
3804 GimpToolRectanglePrivate *private = rectangle->private;
3805
3806 private->x1_int = SIGNED_ROUND (private->x1);
3807 private->y1_int = SIGNED_ROUND (private->y1);
3808
3809 if (gimp_tool_rectangle_rect_rubber_banding_func (rectangle))
3810 {
3811 private->width_int = (gint) SIGNED_ROUND (private->x2) - private->x1_int;
3812 private->height_int = (gint) SIGNED_ROUND (private->y2) - private->y1_int;
3813 }
3814 }
3815
3816 /**
3817 * gimp_tool_rectangle_adjust_coord:
3818 * @rectangle:
3819 * @ccoord_x_input:
3820 * @ccoord_x_input:
3821 * @ccoord_x_output:
3822 * @ccoord_x_output:
3823 *
3824 * Transforms a coordinate to better fit the public behaviour of the
3825 * rectangle.
3826 */
3827 static void
gimp_tool_rectangle_adjust_coord(GimpToolRectangle * rectangle,gdouble coord_x_input,gdouble coord_y_input,gdouble * coord_x_output,gdouble * coord_y_output)3828 gimp_tool_rectangle_adjust_coord (GimpToolRectangle *rectangle,
3829 gdouble coord_x_input,
3830 gdouble coord_y_input,
3831 gdouble *coord_x_output,
3832 gdouble *coord_y_output)
3833 {
3834 GimpToolRectanglePrivate *priv = rectangle->private;
3835
3836 switch (priv->precision)
3837 {
3838 case GIMP_RECTANGLE_PRECISION_INT:
3839 *coord_x_output = RINT (coord_x_input);
3840 *coord_y_output = RINT (coord_y_input);
3841 break;
3842
3843 case GIMP_RECTANGLE_PRECISION_DOUBLE:
3844 default:
3845 *coord_x_output = coord_x_input;
3846 *coord_y_output = coord_y_input;
3847 break;
3848 }
3849 }
3850
3851 static void
gimp_tool_rectangle_recalculate_center_xy(GimpToolRectangle * rectangle)3852 gimp_tool_rectangle_recalculate_center_xy (GimpToolRectangle *rectangle)
3853 {
3854 GimpToolRectanglePrivate *private = rectangle->private;
3855
3856 private->center_x_on_fixed_center = (private->x1 + private->x2) / 2;
3857 private->center_y_on_fixed_center = (private->y1 + private->y2) / 2;
3858 }
3859
3860
3861 /* public functions */
3862
3863 GimpToolWidget *
gimp_tool_rectangle_new(GimpDisplayShell * shell)3864 gimp_tool_rectangle_new (GimpDisplayShell *shell)
3865 {
3866 g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
3867
3868 return g_object_new (GIMP_TYPE_TOOL_RECTANGLE,
3869 "shell", shell,
3870 NULL);
3871 }
3872
3873 GimpRectangleFunction
gimp_tool_rectangle_get_function(GimpToolRectangle * rectangle)3874 gimp_tool_rectangle_get_function (GimpToolRectangle *rectangle)
3875 {
3876 g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle),
3877 GIMP_TOOL_RECTANGLE_DEAD);
3878
3879 return rectangle->private->function;
3880 }
3881
3882 void
gimp_tool_rectangle_set_function(GimpToolRectangle * rectangle,GimpRectangleFunction function)3883 gimp_tool_rectangle_set_function (GimpToolRectangle *rectangle,
3884 GimpRectangleFunction function)
3885 {
3886 GimpToolRectanglePrivate *private;
3887
3888 g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
3889
3890 private = rectangle->private;
3891
3892 if (private->function != function)
3893 {
3894 private->function = function;
3895
3896 gimp_tool_rectangle_changed (GIMP_TOOL_WIDGET (rectangle));
3897 }
3898 }
3899
3900 void
gimp_tool_rectangle_set_constraint(GimpToolRectangle * rectangle,GimpRectangleConstraint constraint)3901 gimp_tool_rectangle_set_constraint (GimpToolRectangle *rectangle,
3902 GimpRectangleConstraint constraint)
3903 {
3904 GimpToolRectanglePrivate *private;
3905
3906 g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
3907
3908 private = rectangle->private;
3909
3910 if (constraint != private->constraint)
3911 {
3912 g_object_freeze_notify (G_OBJECT (rectangle));
3913
3914 private->constraint = constraint;
3915 g_object_notify (G_OBJECT (rectangle), "constraint");
3916
3917 gimp_tool_rectangle_clamp (rectangle, NULL, constraint, FALSE);
3918
3919 g_object_thaw_notify (G_OBJECT (rectangle));
3920
3921 gimp_tool_rectangle_change_complete (rectangle);
3922 }
3923 }
3924
3925 GimpRectangleConstraint
gimp_tool_rectangle_get_constraint(GimpToolRectangle * rectangle)3926 gimp_tool_rectangle_get_constraint (GimpToolRectangle *rectangle)
3927 {
3928 g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), 0);
3929
3930 return rectangle->private->constraint;
3931 }
3932
3933 /**
3934 * gimp_tool_rectangle_get_public_rect:
3935 * @rectangle:
3936 * @x1:
3937 * @y1:
3938 * @x2:
3939 * @y2:
3940 *
3941 * This function returns the rectangle as it appears to be publicly
3942 * (based on integer or double precision-mode).
3943 **/
3944 void
gimp_tool_rectangle_get_public_rect(GimpToolRectangle * rectangle,gdouble * x1,gdouble * y1,gdouble * x2,gdouble * y2)3945 gimp_tool_rectangle_get_public_rect (GimpToolRectangle *rectangle,
3946 gdouble *x1,
3947 gdouble *y1,
3948 gdouble *x2,
3949 gdouble *y2)
3950 {
3951 GimpToolRectanglePrivate *priv;
3952
3953 g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
3954 g_return_if_fail (x1 != NULL);
3955 g_return_if_fail (y1 != NULL);
3956 g_return_if_fail (x2 != NULL);
3957 g_return_if_fail (y2 != NULL);
3958
3959 priv = rectangle->private;
3960
3961 switch (priv->precision)
3962 {
3963 case GIMP_RECTANGLE_PRECISION_INT:
3964 *x1 = priv->x1_int;
3965 *y1 = priv->y1_int;
3966 *x2 = priv->x1_int + priv->width_int;
3967 *y2 = priv->y1_int + priv->height_int;
3968 break;
3969
3970 case GIMP_RECTANGLE_PRECISION_DOUBLE:
3971 default:
3972 *x1 = priv->x1;
3973 *y1 = priv->y1;
3974 *x2 = priv->x2;
3975 *y2 = priv->y2;
3976 break;
3977 }
3978 }
3979
3980 /**
3981 * gimp_tool_rectangle_pending_size_set:
3982 * @width_property: Option property to set to pending rectangle width.
3983 * @height_property: Option property to set to pending rectangle height.
3984 *
3985 * Sets specified rectangle tool options properties to the width and
3986 * height of the current pending rectangle.
3987 */
3988 void
gimp_tool_rectangle_pending_size_set(GimpToolRectangle * rectangle,GObject * object,const gchar * width_property,const gchar * height_property)3989 gimp_tool_rectangle_pending_size_set (GimpToolRectangle *rectangle,
3990 GObject *object,
3991 const gchar *width_property,
3992 const gchar *height_property)
3993 {
3994 GimpToolRectanglePrivate *private;
3995
3996 g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
3997 g_return_if_fail (width_property != NULL);
3998 g_return_if_fail (height_property != NULL);
3999
4000 private = rectangle->private;
4001
4002 g_object_set (object,
4003 width_property, MAX (private->x2 - private->x1, 1.0),
4004 height_property, MAX (private->y2 - private->y1, 1.0),
4005 NULL);
4006 }
4007
4008 /**
4009 * gimp_tool_rectangle_constraint_size_set:
4010 * @width_property: Option property to set to current constraint width.
4011 * @height_property: Option property to set to current constraint height.
4012 *
4013 * Sets specified rectangle tool options properties to the width and
4014 * height of the current constraint size.
4015 */
4016 void
gimp_tool_rectangle_constraint_size_set(GimpToolRectangle * rectangle,GObject * object,const gchar * width_property,const gchar * height_property)4017 gimp_tool_rectangle_constraint_size_set (GimpToolRectangle *rectangle,
4018 GObject *object,
4019 const gchar *width_property,
4020 const gchar *height_property)
4021 {
4022 GimpDisplayShell *shell;
4023 GimpContext *context;
4024 GimpImage *image;
4025 gdouble width;
4026 gdouble height;
4027
4028 g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
4029
4030 shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
4031 context = gimp_get_user_context (shell->display->gimp);
4032 image = gimp_context_get_image (context);
4033
4034 if (! image)
4035 {
4036 width = 1.0;
4037 height = 1.0;
4038 }
4039 else
4040 {
4041 GimpRectangleConstraint constraint;
4042
4043 constraint = gimp_tool_rectangle_get_constraint (rectangle);
4044
4045 switch (constraint)
4046 {
4047 case GIMP_RECTANGLE_CONSTRAIN_DRAWABLE:
4048 {
4049 GimpItem *item = GIMP_ITEM (gimp_image_get_active_layer (image));
4050
4051 if (! item)
4052 {
4053 width = 1.0;
4054 height = 1.0;
4055 }
4056 else
4057 {
4058 width = gimp_item_get_width (item);
4059 height = gimp_item_get_height (item);
4060 }
4061 }
4062 break;
4063
4064 case GIMP_RECTANGLE_CONSTRAIN_IMAGE:
4065 default:
4066 {
4067 width = gimp_image_get_width (image);
4068 height = gimp_image_get_height (image);
4069 }
4070 break;
4071 }
4072 }
4073
4074 g_object_set (object,
4075 width_property, width,
4076 height_property, height,
4077 NULL);
4078 }
4079
4080 /**
4081 * gimp_tool_rectangle_rectangle_is_first:
4082 * @rectangle:
4083 *
4084 * Returns: %TRUE if the user is creating the first rectangle with
4085 * this instance from scratch, %FALSE if modifying an existing
4086 * rectangle, or creating a new rectangle, discarding the existing
4087 * one. This function is only meaningful in _motion and
4088 * _button_release.
4089 */
4090 gboolean
gimp_tool_rectangle_rectangle_is_first(GimpToolRectangle * rectangle)4091 gimp_tool_rectangle_rectangle_is_first (GimpToolRectangle *rectangle)
4092 {
4093 g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);
4094
4095 return rectangle->private->is_first;
4096 }
4097
4098 /**
4099 * gimp_tool_rectangle_rectangle_is_new:
4100 * @rectangle:
4101 *
4102 * Returns: %TRUE if the user is creating a new rectangle from
4103 * scratch, %FALSE if modifying n previously existing rectangle. This
4104 * function is only meaningful in _motion and _button_release.
4105 */
4106 gboolean
gimp_tool_rectangle_rectangle_is_new(GimpToolRectangle * rectangle)4107 gimp_tool_rectangle_rectangle_is_new (GimpToolRectangle *rectangle)
4108 {
4109 g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);
4110
4111 return rectangle->private->is_new;
4112 }
4113
4114 /**
4115 * gimp_tool_rectangle_point_in_rectangle:
4116 * @rectangle:
4117 * @x: X-coord of point to test (in image coordinates)
4118 * @y: Y-coord of point to test (in image coordinates)
4119 *
4120 * Returns: %TRUE if the passed point was within the rectangle
4121 **/
4122 gboolean
gimp_tool_rectangle_point_in_rectangle(GimpToolRectangle * rectangle,gdouble x,gdouble y)4123 gimp_tool_rectangle_point_in_rectangle (GimpToolRectangle *rectangle,
4124 gdouble x,
4125 gdouble y)
4126 {
4127 gdouble x1, y1, x2, y2;
4128
4129 g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);
4130
4131 gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
4132
4133 return (x >= x1 && x <= x2 &&
4134 y >= y1 && y <= y2);
4135 }
4136
4137 /**
4138 * gimp_tool_rectangle_frame_item:
4139 * @rectangle: a #GimpToolRectangle interface
4140 * @item: a #GimpItem attached to the image on which a
4141 * rectangle is being shown.
4142 *
4143 * Convenience function to set the corners of the rectangle to
4144 * match the bounds of the specified item. The rectangle interface
4145 * must be active (i.e., showing a rectangle), and the item must be
4146 * attached to the image on which the rectangle is active.
4147 **/
4148 void
gimp_tool_rectangle_frame_item(GimpToolRectangle * rectangle,GimpItem * item)4149 gimp_tool_rectangle_frame_item (GimpToolRectangle *rectangle,
4150 GimpItem *item)
4151 {
4152 GimpDisplayShell *shell;
4153 gint offset_x;
4154 gint offset_y;
4155 gint width;
4156 gint height;
4157
4158 g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
4159 g_return_if_fail (GIMP_IS_ITEM (item));
4160 g_return_if_fail (gimp_item_is_attached (item));
4161
4162 shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
4163
4164 g_return_if_fail (gimp_display_get_image (shell->display) ==
4165 gimp_item_get_image (item));
4166
4167 width = gimp_item_get_width (item);
4168 height = gimp_item_get_height (item);
4169
4170 gimp_item_get_offset (item, &offset_x, &offset_y);
4171
4172 gimp_tool_rectangle_set_function (rectangle, GIMP_TOOL_RECTANGLE_CREATING);
4173
4174 g_object_set (rectangle,
4175 "x1", (gdouble) offset_x,
4176 "y1", (gdouble) offset_y,
4177 "x2", (gdouble) (offset_x + width),
4178 "y2", (gdouble) (offset_y + height),
4179 NULL);
4180
4181 /* kludge to force handle sizes to update. This call may be harmful
4182 * if this function is ever moved out of the text tool code.
4183 */
4184 gimp_tool_rectangle_set_constraint (rectangle, GIMP_RECTANGLE_CONSTRAIN_NONE);
4185 }
4186
4187 void
gimp_tool_rectangle_auto_shrink(GimpToolRectangle * rectangle,gboolean shrink_merged)4188 gimp_tool_rectangle_auto_shrink (GimpToolRectangle *rectangle,
4189 gboolean shrink_merged)
4190 {
4191 GimpToolRectanglePrivate *private;
4192 GimpDisplayShell *shell;
4193 GimpImage *image;
4194 GimpPickable *pickable;
4195 gint offset_x = 0;
4196 gint offset_y = 0;
4197 gint x1, y1;
4198 gint x2, y2;
4199 gint shrunk_x;
4200 gint shrunk_y;
4201 gint shrunk_width;
4202 gint shrunk_height;
4203
4204 g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
4205
4206 private = rectangle->private;
4207
4208 shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
4209 image = gimp_display_get_image (shell->display);
4210
4211 if (shrink_merged)
4212 {
4213 pickable = GIMP_PICKABLE (image);
4214
4215 x1 = private->x1;
4216 y1 = private->y1;
4217 x2 = private->x2;
4218 y2 = private->y2;
4219 }
4220 else
4221 {
4222 pickable = GIMP_PICKABLE (gimp_image_get_active_drawable (image));
4223
4224 if (! pickable)
4225 return;
4226
4227 gimp_item_get_offset (GIMP_ITEM (pickable), &offset_x, &offset_y);
4228
4229 x1 = private->x1 - offset_x;
4230 y1 = private->y1 - offset_y;
4231 x2 = private->x2 - offset_x;
4232 y2 = private->y2 - offset_y;
4233 }
4234
4235 switch (gimp_pickable_auto_shrink (pickable,
4236 x1, y1, x2 - x1, y2 - y1,
4237 &shrunk_x,
4238 &shrunk_y,
4239 &shrunk_width,
4240 &shrunk_height))
4241 {
4242 case GIMP_AUTO_SHRINK_SHRINK:
4243 {
4244 GimpRectangleFunction original_function = private->function;
4245
4246 private->function = GIMP_TOOL_RECTANGLE_AUTO_SHRINK;
4247
4248 private->x1 = offset_x + shrunk_x;
4249 private->y1 = offset_y + shrunk_y;
4250 private->x2 = offset_x + shrunk_x + shrunk_width;
4251 private->y2 = offset_y + shrunk_y + shrunk_height;
4252
4253 gimp_tool_rectangle_update_int_rect (rectangle);
4254
4255 gimp_tool_rectangle_change_complete (rectangle);
4256
4257 private->function = original_function;
4258
4259 gimp_tool_rectangle_update_options (rectangle);
4260 }
4261 break;
4262
4263 default:
4264 break;
4265 }
4266 }
4267