1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimptoolcompass.c
5 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
6 *
7 * Measure tool
8 * Copyright (C) 1999-2003 Sven Neumann <sven@gimp.org>
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
29 #include "libgimpbase/gimpbase.h"
30 #include "libgimpmath/gimpmath.h"
31
32 #include "display-types.h"
33
34 #include "core/gimp-utils.h"
35 #include "core/gimpimage.h"
36 #include "core/gimpmarshal.h"
37
38 #include "widgets/gimpwidgets-utils.h"
39
40 #include "gimpcanvashandle.h"
41 #include "gimpcanvasline.h"
42 #include "gimpdisplay.h"
43 #include "gimpdisplayshell.h"
44 #include "gimpdisplayshell-appearance.h"
45 #include "gimpdisplayshell-transform.h"
46 #include "gimpdisplayshell-utils.h"
47 #include "gimptoolcompass.h"
48
49 #include "gimp-intl.h"
50
51
52 #define ARC_RADIUS 30
53 #define ARC_GAP (ARC_RADIUS / 2)
54 #define EPSILON 1e-6
55
56
57 /* possible measure functions */
58 typedef enum
59 {
60 CREATING,
61 ADDING,
62 MOVING,
63 MOVING_ALL,
64 GUIDING,
65 FINISHED
66 } CompassFunction;
67
68 enum
69 {
70 PROP_0,
71 PROP_ORIENTATION,
72 PROP_N_POINTS,
73 PROP_X1,
74 PROP_Y1,
75 PROP_X2,
76 PROP_Y2,
77 PROP_X3,
78 PROP_Y3,
79 PROP_PIXEL_ANGLE,
80 PROP_UNIT_ANGLE,
81 PROP_EFFECTIVE_ORIENTATION
82 };
83
84 enum
85 {
86 CREATE_GUIDES,
87 LAST_SIGNAL
88 };
89
90 struct _GimpToolCompassPrivate
91 {
92 GimpCompassOrientation orientation;
93 gint n_points;
94 gint x[3];
95 gint y[3];
96
97 GimpVector2 radius1;
98 GimpVector2 radius2;
99 gdouble display_angle;
100 gdouble pixel_angle;
101 gdouble unit_angle;
102 GimpCompassOrientation effective_orientation;
103
104 CompassFunction function;
105 gdouble mouse_x;
106 gdouble mouse_y;
107 gint last_x;
108 gint last_y;
109 gint point;
110
111 GimpCanvasItem *line1;
112 GimpCanvasItem *line2;
113 GimpCanvasItem *arc;
114 GimpCanvasItem *arc_line;
115 GimpCanvasItem *handles[3];
116 };
117
118
119 /* local function prototypes */
120
121 static void gimp_tool_compass_constructed (GObject *object);
122 static void gimp_tool_compass_set_property (GObject *object,
123 guint property_id,
124 const GValue *value,
125 GParamSpec *pspec);
126 static void gimp_tool_compass_get_property (GObject *object,
127 guint property_id,
128 GValue *value,
129 GParamSpec *pspec);
130
131 static void gimp_tool_compass_changed (GimpToolWidget *widget);
132 static gint gimp_tool_compass_button_press (GimpToolWidget *widget,
133 const GimpCoords *coords,
134 guint32 time,
135 GdkModifierType state,
136 GimpButtonPressType press_type);
137 static void gimp_tool_compass_button_release (GimpToolWidget *widget,
138 const GimpCoords *coords,
139 guint32 time,
140 GdkModifierType state,
141 GimpButtonReleaseType release_type);
142 static void gimp_tool_compass_motion (GimpToolWidget *widget,
143 const GimpCoords *coords,
144 guint32 time,
145 GdkModifierType state);
146 static GimpHit gimp_tool_compass_hit (GimpToolWidget *widget,
147 const GimpCoords *coords,
148 GdkModifierType state,
149 gboolean proximity);
150 static void gimp_tool_compass_hover (GimpToolWidget *widget,
151 const GimpCoords *coords,
152 GdkModifierType state,
153 gboolean proximity);
154 static void gimp_tool_compass_leave_notify (GimpToolWidget *widget);
155 static void gimp_tool_compass_motion_modifier (GimpToolWidget *widget,
156 GdkModifierType key,
157 gboolean press,
158 GdkModifierType state);
159 static gboolean gimp_tool_compass_get_cursor (GimpToolWidget *widget,
160 const GimpCoords *coords,
161 GdkModifierType state,
162 GimpCursorType *cursor,
163 GimpToolCursorType *tool_cursor,
164 GimpCursorModifier *modifier);
165
166 static gint gimp_tool_compass_get_point (GimpToolCompass *compass,
167 const GimpCoords *coords);
168 static void gimp_tool_compass_update_hilight (GimpToolCompass *compass);
169 static void gimp_tool_compass_update_angle (GimpToolCompass *compass,
170 GimpCompassOrientation orientation,
171 gboolean flip);
172
173
174 G_DEFINE_TYPE_WITH_PRIVATE (GimpToolCompass, gimp_tool_compass,
175 GIMP_TYPE_TOOL_WIDGET)
176
177 #define parent_class gimp_tool_compass_parent_class
178
179 static guint compass_signals[LAST_SIGNAL] = { 0 };
180
181
182 static void
gimp_tool_compass_class_init(GimpToolCompassClass * klass)183 gimp_tool_compass_class_init (GimpToolCompassClass *klass)
184 {
185 GObjectClass *object_class = G_OBJECT_CLASS (klass);
186 GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
187
188 object_class->constructed = gimp_tool_compass_constructed;
189 object_class->set_property = gimp_tool_compass_set_property;
190 object_class->get_property = gimp_tool_compass_get_property;
191
192 widget_class->changed = gimp_tool_compass_changed;
193 widget_class->button_press = gimp_tool_compass_button_press;
194 widget_class->button_release = gimp_tool_compass_button_release;
195 widget_class->motion = gimp_tool_compass_motion;
196 widget_class->hit = gimp_tool_compass_hit;
197 widget_class->hover = gimp_tool_compass_hover;
198 widget_class->leave_notify = gimp_tool_compass_leave_notify;
199 widget_class->motion_modifier = gimp_tool_compass_motion_modifier;
200 widget_class->get_cursor = gimp_tool_compass_get_cursor;
201 widget_class->update_on_scale = TRUE;
202 widget_class->update_on_rotate = TRUE;
203
204 compass_signals[CREATE_GUIDES] =
205 g_signal_new ("create-guides",
206 G_TYPE_FROM_CLASS (klass),
207 G_SIGNAL_RUN_FIRST,
208 G_STRUCT_OFFSET (GimpToolCompassClass, create_guides),
209 NULL, NULL,
210 gimp_marshal_VOID__INT_INT_BOOLEAN_BOOLEAN,
211 G_TYPE_NONE, 4,
212 G_TYPE_INT,
213 G_TYPE_INT,
214 G_TYPE_BOOLEAN,
215 G_TYPE_BOOLEAN);
216
217 g_object_class_install_property (object_class, PROP_ORIENTATION,
218 g_param_spec_enum ("orientation", NULL, NULL,
219 GIMP_TYPE_COMPASS_ORIENTATION,
220 GIMP_COMPASS_ORIENTATION_AUTO,
221 GIMP_PARAM_READWRITE |
222 G_PARAM_CONSTRUCT));
223
224 g_object_class_install_property (object_class, PROP_N_POINTS,
225 g_param_spec_int ("n-points", NULL, NULL,
226 1, 3, 1,
227 GIMP_PARAM_READWRITE |
228 G_PARAM_CONSTRUCT));
229
230 g_object_class_install_property (object_class, PROP_X1,
231 g_param_spec_int ("x1", NULL, NULL,
232 -GIMP_MAX_IMAGE_SIZE,
233 GIMP_MAX_IMAGE_SIZE, 0,
234 GIMP_PARAM_READWRITE |
235 G_PARAM_CONSTRUCT));
236
237 g_object_class_install_property (object_class, PROP_Y1,
238 g_param_spec_int ("y1", NULL, NULL,
239 -GIMP_MAX_IMAGE_SIZE,
240 GIMP_MAX_IMAGE_SIZE, 0,
241 GIMP_PARAM_READWRITE |
242 G_PARAM_CONSTRUCT));
243
244 g_object_class_install_property (object_class, PROP_X2,
245 g_param_spec_int ("x2", NULL, NULL,
246 -GIMP_MAX_IMAGE_SIZE,
247 GIMP_MAX_IMAGE_SIZE, 0,
248 GIMP_PARAM_READWRITE |
249 G_PARAM_CONSTRUCT));
250
251 g_object_class_install_property (object_class, PROP_Y2,
252 g_param_spec_int ("y2", NULL, NULL,
253 -GIMP_MAX_IMAGE_SIZE,
254 GIMP_MAX_IMAGE_SIZE, 0,
255 GIMP_PARAM_READWRITE |
256 G_PARAM_CONSTRUCT));
257
258 g_object_class_install_property (object_class, PROP_X3,
259 g_param_spec_int ("x3", NULL, NULL,
260 -GIMP_MAX_IMAGE_SIZE,
261 GIMP_MAX_IMAGE_SIZE, 0,
262 GIMP_PARAM_READWRITE |
263 G_PARAM_CONSTRUCT));
264
265 g_object_class_install_property (object_class, PROP_Y3,
266 g_param_spec_int ("y3", NULL, NULL,
267 -GIMP_MAX_IMAGE_SIZE,
268 GIMP_MAX_IMAGE_SIZE, 0,
269 GIMP_PARAM_READWRITE |
270 G_PARAM_CONSTRUCT));
271
272 g_object_class_install_property (object_class, PROP_PIXEL_ANGLE,
273 g_param_spec_double ("pixel-angle", NULL, NULL,
274 -G_PI, G_PI, 0.0,
275 GIMP_PARAM_READABLE));
276
277 g_object_class_install_property (object_class, PROP_UNIT_ANGLE,
278 g_param_spec_double ("unit-angle", NULL, NULL,
279 -G_PI, G_PI, 0.0,
280 GIMP_PARAM_READABLE));
281
282 g_object_class_install_property (object_class, PROP_EFFECTIVE_ORIENTATION,
283 g_param_spec_enum ("effective-orientation", NULL, NULL,
284 GIMP_TYPE_COMPASS_ORIENTATION,
285 GIMP_COMPASS_ORIENTATION_AUTO,
286 GIMP_PARAM_READABLE));
287 }
288
289 static void
gimp_tool_compass_init(GimpToolCompass * compass)290 gimp_tool_compass_init (GimpToolCompass *compass)
291 {
292 compass->private = gimp_tool_compass_get_instance_private (compass);
293
294 compass->private->point = -1;
295 }
296
297 static void
gimp_tool_compass_constructed(GObject * object)298 gimp_tool_compass_constructed (GObject *object)
299 {
300 GimpToolCompass *compass = GIMP_TOOL_COMPASS (object);
301 GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
302 GimpToolCompassPrivate *private = compass->private;
303 GimpCanvasGroup *stroke_group;
304 gint i;
305
306 G_OBJECT_CLASS (parent_class)->constructed (object);
307
308 stroke_group = gimp_tool_widget_add_stroke_group (widget);
309
310 gimp_tool_widget_push_group (widget, stroke_group);
311
312 private->line1 = gimp_tool_widget_add_line (widget,
313 private->x[0],
314 private->y[0],
315 private->x[1],
316 private->y[1]);
317
318 private->line2 = gimp_tool_widget_add_line (widget,
319 private->x[0],
320 private->y[0],
321 private->x[2],
322 private->y[2]);
323
324 private->arc = gimp_tool_widget_add_handle (widget,
325 GIMP_HANDLE_CIRCLE,
326 private->x[0],
327 private->y[0],
328 ARC_RADIUS * 2 + 1,
329 ARC_RADIUS * 2 + 1,
330 GIMP_HANDLE_ANCHOR_CENTER);
331
332 private->arc_line = gimp_tool_widget_add_line (widget,
333 private->x[0],
334 private->y[0],
335 private->x[0] + 10,
336 private->y[0]);
337
338 gimp_tool_widget_pop_group (widget);
339
340 for (i = 0; i < 3; i++)
341 {
342 private->handles[i] =
343 gimp_tool_widget_add_handle (widget,
344 i == 0 ?
345 GIMP_HANDLE_CIRCLE : GIMP_HANDLE_CROSS,
346 private->x[i],
347 private->y[i],
348 GIMP_CANVAS_HANDLE_SIZE_CROSS,
349 GIMP_CANVAS_HANDLE_SIZE_CROSS,
350 GIMP_HANDLE_ANCHOR_CENTER);
351 }
352
353 gimp_tool_compass_changed (widget);
354 }
355
356 static void
gimp_tool_compass_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)357 gimp_tool_compass_set_property (GObject *object,
358 guint property_id,
359 const GValue *value,
360 GParamSpec *pspec)
361 {
362 GimpToolCompass *compass = GIMP_TOOL_COMPASS (object);
363 GimpToolCompassPrivate *private = compass->private;
364
365 switch (property_id)
366 {
367 case PROP_ORIENTATION:
368 private->orientation = g_value_get_enum (value);
369 break;
370 case PROP_N_POINTS:
371 private->n_points = g_value_get_int (value);
372 break;
373 case PROP_X1:
374 private->x[0] = g_value_get_int (value);
375 break;
376 case PROP_Y1:
377 private->y[0] = g_value_get_int (value);
378 break;
379 case PROP_X2:
380 private->x[1] = g_value_get_int (value);
381 break;
382 case PROP_Y2:
383 private->y[1] = g_value_get_int (value);
384 break;
385 case PROP_X3:
386 private->x[2] = g_value_get_int (value);
387 break;
388 case PROP_Y3:
389 private->y[2] = g_value_get_int (value);
390 break;
391
392 default:
393 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
394 break;
395 }
396 }
397
398 static void
gimp_tool_compass_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)399 gimp_tool_compass_get_property (GObject *object,
400 guint property_id,
401 GValue *value,
402 GParamSpec *pspec)
403 {
404 GimpToolCompass *compass = GIMP_TOOL_COMPASS (object);
405 GimpToolCompassPrivate *private = compass->private;
406
407 switch (property_id)
408 {
409 case PROP_ORIENTATION:
410 g_value_set_enum (value, private->orientation);
411 break;
412 case PROP_N_POINTS:
413 g_value_set_int (value, private->n_points);
414 break;
415 case PROP_X1:
416 g_value_set_int (value, private->x[0]);
417 break;
418 case PROP_Y1:
419 g_value_set_int (value, private->y[0]);
420 break;
421 case PROP_X2:
422 g_value_set_int (value, private->x[1]);
423 break;
424 case PROP_Y2:
425 g_value_set_int (value, private->y[1]);
426 break;
427 case PROP_X3:
428 g_value_set_int (value, private->x[2]);
429 break;
430 case PROP_Y3:
431 g_value_set_int (value, private->y[2]);
432 break;
433 case PROP_PIXEL_ANGLE:
434 g_value_set_double (value, private->pixel_angle);
435 break;
436 case PROP_UNIT_ANGLE:
437 g_value_set_double (value, private->unit_angle);
438 break;
439 case PROP_EFFECTIVE_ORIENTATION:
440 g_value_set_enum (value, private->effective_orientation);
441 break;
442
443 default:
444 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
445 break;
446 }
447 }
448
449 static void
gimp_tool_compass_changed(GimpToolWidget * widget)450 gimp_tool_compass_changed (GimpToolWidget *widget)
451 {
452 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
453 GimpToolCompassPrivate *private = compass->private;
454 GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
455 gdouble angle1;
456 gdouble angle2;
457 gint draw_arc = 0;
458 gboolean draw_arc_line = FALSE;
459 gdouble arc_line_display_length;
460 gdouble arc_line_length;
461
462 gimp_tool_compass_update_angle (compass, private->orientation, FALSE);
463
464 angle1 = -atan2 (private->radius1.y * shell->scale_y,
465 private->radius1.x * shell->scale_x);
466 angle2 = -private->display_angle;
467
468 gimp_canvas_line_set (private->line1,
469 private->x[0],
470 private->y[0],
471 private->x[1],
472 private->y[1]);
473 gimp_canvas_item_set_visible (private->line1, private->n_points > 1);
474 if (private->n_points > 1 &&
475 gimp_canvas_item_transform_distance (private->line1,
476 private->x[0],
477 private->y[0],
478 private->x[1],
479 private->y[1]) > ARC_RADIUS)
480 {
481 draw_arc++;
482 }
483
484
485 arc_line_display_length = ARC_RADIUS +
486 (GIMP_CANVAS_HANDLE_SIZE_CROSS >> 1) +
487 ARC_GAP;
488 arc_line_length = arc_line_display_length /
489 hypot (private->radius2.x * shell->scale_x,
490 private->radius2.y * shell->scale_y);
491
492 if (private->n_points > 2)
493 {
494 gdouble length = gimp_canvas_item_transform_distance (private->line2,
495 private->x[0],
496 private->y[0],
497 private->x[2],
498 private->y[2]);
499
500 if (length > ARC_RADIUS)
501 {
502 draw_arc++;
503 draw_arc_line = TRUE;
504
505 if (length > arc_line_display_length)
506 {
507 gimp_canvas_line_set (
508 private->line2,
509 private->x[0] + private->radius2.x * arc_line_length,
510 private->y[0] + private->radius2.y * arc_line_length,
511 private->x[2],
512 private->y[2]);
513 gimp_canvas_item_set_visible (private->line2, TRUE);
514 }
515 else
516 {
517 gimp_canvas_item_set_visible (private->line2, FALSE);
518 }
519 }
520 else
521 {
522 gimp_canvas_line_set (private->line2,
523 private->x[0],
524 private->y[0],
525 private->x[2],
526 private->y[2]);
527 gimp_canvas_item_set_visible (private->line2, TRUE);
528 }
529 }
530 else
531 {
532 gimp_canvas_item_set_visible (private->line2, FALSE);
533 }
534
535 gimp_canvas_handle_set_position (private->arc,
536 private->x[0], private->y[0]);
537 gimp_canvas_handle_set_angles (private->arc, angle1, angle2);
538 gimp_canvas_item_set_visible (private->arc,
539 private->n_points > 1 &&
540 draw_arc == private->n_points - 1 &&
541 fabs (angle2) > EPSILON);
542
543 arc_line_length = (ARC_RADIUS + (GIMP_CANVAS_HANDLE_SIZE_CROSS >> 1)) /
544 hypot (private->radius2.x * shell->scale_x,
545 private->radius2.y * shell->scale_y);
546
547 gimp_canvas_line_set (private->arc_line,
548 private->x[0],
549 private->y[0],
550 private->x[0] + private->radius2.x * arc_line_length,
551 private->y[0] + private->radius2.y * arc_line_length);
552 gimp_canvas_item_set_visible (private->arc_line,
553 (private->n_points == 2 || draw_arc_line) &&
554 fabs (angle2) > EPSILON);
555
556 gimp_canvas_handle_set_position (private->handles[0],
557 private->x[0], private->y[0]);
558 gimp_canvas_item_set_visible (private->handles[0],
559 private->n_points > 0);
560
561 gimp_canvas_handle_set_position (private->handles[1],
562 private->x[1], private->y[1]);
563 gimp_canvas_item_set_visible (private->handles[1],
564 private->n_points > 1);
565
566 gimp_canvas_handle_set_position (private->handles[2],
567 private->x[2], private->y[2]);
568 gimp_canvas_item_set_visible (private->handles[2],
569 private->n_points > 2);
570
571 gimp_tool_compass_update_hilight (compass);
572 }
573
574 gint
gimp_tool_compass_button_press(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonPressType press_type)575 gimp_tool_compass_button_press (GimpToolWidget *widget,
576 const GimpCoords *coords,
577 guint32 time,
578 GdkModifierType state,
579 GimpButtonPressType press_type)
580 {
581 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
582 GimpToolCompassPrivate *private = compass->private;
583
584 private->function = CREATING;
585
586 private->mouse_x = coords->x;
587 private->mouse_y = coords->y;
588
589 /* if the cursor is in one of the handles, the new function will be
590 * moving or adding a new point or guide
591 */
592 if (private->point != -1)
593 {
594 GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
595 GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
596
597 if (state & (toggle_mask | GDK_MOD1_MASK))
598 {
599 gboolean create_hguide = (state & toggle_mask);
600 gboolean create_vguide = (state & GDK_MOD1_MASK);
601
602 g_signal_emit (compass, compass_signals[CREATE_GUIDES], 0,
603 private->x[private->point],
604 private->y[private->point],
605 create_hguide,
606 create_vguide);
607
608 private->function = GUIDING;
609 }
610 else
611 {
612 if (private->n_points == 1 || (state & extend_mask))
613 private->function = ADDING;
614 else
615 private->function = MOVING;
616 }
617 }
618
619 /* adding to the middle point makes no sense */
620 if (private->point == 0 &&
621 private->function == ADDING &&
622 private->n_points == 3)
623 {
624 private->function = MOVING;
625 }
626
627 /* if the function is still CREATING, we are outside the handles */
628 if (private->function == CREATING)
629 {
630 if (private->n_points > 1 && (state & GDK_MOD1_MASK))
631 {
632 private->function = MOVING_ALL;
633
634 private->last_x = coords->x;
635 private->last_y = coords->y;
636 }
637 }
638
639 if (private->function == CREATING)
640 {
641 /* set the first point and go into ADDING mode */
642 g_object_set (compass,
643 "n-points", 1,
644 "x1", (gint) (coords->x + 0.5),
645 "y1", (gint) (coords->y + 0.5),
646 "x2", 0,
647 "y2", 0,
648 "x3", 0,
649 "y3", 0,
650 NULL);
651
652 private->point = 0;
653 private->function = ADDING;
654 }
655
656 return 1;
657 }
658
659 void
gimp_tool_compass_button_release(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonReleaseType release_type)660 gimp_tool_compass_button_release (GimpToolWidget *widget,
661 const GimpCoords *coords,
662 guint32 time,
663 GdkModifierType state,
664 GimpButtonReleaseType release_type)
665 {
666 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
667 GimpToolCompassPrivate *private = compass->private;
668
669 private->function = FINISHED;
670 }
671
672 void
gimp_tool_compass_motion(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state)673 gimp_tool_compass_motion (GimpToolWidget *widget,
674 const GimpCoords *coords,
675 guint32 time,
676 GdkModifierType state)
677 {
678 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
679 GimpToolCompassPrivate *private = compass->private;
680 gint new_n_points;
681 gint new_x[3];
682 gint new_y[3];
683 gint dx, dy;
684 gint tmp;
685
686 private->mouse_x = coords->x;
687 private->mouse_y = coords->y;
688
689 /* A few comments here, because this routine looks quite weird at first ...
690 *
691 * The goal is to keep point 0, called the start point, to be
692 * always the one in the middle or, if there are only two points,
693 * the one that is fixed. The angle is then always measured at
694 * this point.
695 */
696
697 new_n_points = private->n_points;
698 new_x[0] = private->x[0];
699 new_y[0] = private->y[0];
700 new_x[1] = private->x[1];
701 new_y[1] = private->y[1];
702 new_x[2] = private->x[2];
703 new_y[2] = private->y[2];
704
705 switch (private->function)
706 {
707 case ADDING:
708 switch (private->point)
709 {
710 case 0:
711 /* we are adding to the start point */
712 break;
713
714 case 1:
715 /* we are adding to the end point, make it the new start point */
716 new_x[0] = private->x[1];
717 new_y[0] = private->y[1];
718
719 new_x[1] = private->x[0];
720 new_y[1] = private->y[0];
721 break;
722
723 case 2:
724 /* we are adding to the third point, make it the new start point */
725 new_x[1] = private->x[0];
726 new_y[1] = private->y[0];
727 new_x[0] = private->x[2];
728 new_y[0] = private->y[2];
729 break;
730
731 default:
732 break;
733 }
734
735 new_n_points = MIN (new_n_points + 1, 3);
736
737 private->point = new_n_points - 1;
738 private->function = MOVING;
739 /* don't break here! */
740
741 case MOVING:
742 /* if we are moving the start point and only have two, make it
743 * the end point
744 */
745 if (new_n_points == 2 && private->point == 0)
746 {
747 tmp = new_x[0];
748 new_x[0] = new_x[1];
749 new_x[1] = tmp;
750
751 tmp = new_y[0];
752 new_y[0] = new_y[1];
753 new_y[1] = tmp;
754
755 private->point = 1;
756 }
757
758 new_x[private->point] = ROUND (coords->x);
759 new_y[private->point] = ROUND (coords->y);
760
761 if (state & gimp_get_constrain_behavior_mask ())
762 {
763 gdouble x = new_x[private->point];
764 gdouble y = new_y[private->point];
765
766 gimp_display_shell_constrain_line (gimp_tool_widget_get_shell (widget),
767 new_x[0], new_y[0],
768 &x, &y,
769 GIMP_CONSTRAIN_LINE_15_DEGREES);
770
771 new_x[private->point] = ROUND (x);
772 new_y[private->point] = ROUND (y);
773 }
774
775 g_object_set (compass,
776 "n-points", new_n_points,
777 "x1", new_x[0],
778 "y1", new_y[0],
779 "x2", new_x[1],
780 "y2", new_y[1],
781 "x3", new_x[2],
782 "y3", new_y[2],
783 NULL);
784 break;
785
786 case MOVING_ALL:
787 dx = ROUND (coords->x) - private->last_x;
788 dy = ROUND (coords->y) - private->last_y;
789
790 g_object_set (compass,
791 "x1", new_x[0] + dx,
792 "y1", new_y[0] + dy,
793 "x2", new_x[1] + dx,
794 "y2", new_y[1] + dy,
795 "x3", new_x[2] + dx,
796 "y3", new_y[2] + dy,
797 NULL);
798
799 private->last_x = ROUND (coords->x);
800 private->last_y = ROUND (coords->y);
801 break;
802
803 default:
804 break;
805 }
806 }
807
808 GimpHit
gimp_tool_compass_hit(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)809 gimp_tool_compass_hit (GimpToolWidget *widget,
810 const GimpCoords *coords,
811 GdkModifierType state,
812 gboolean proximity)
813 {
814 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
815
816 if (gimp_tool_compass_get_point (compass, coords) >= 0)
817 return GIMP_HIT_DIRECT;
818 else
819 return GIMP_HIT_INDIRECT;
820 }
821
822 void
gimp_tool_compass_hover(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)823 gimp_tool_compass_hover (GimpToolWidget *widget,
824 const GimpCoords *coords,
825 GdkModifierType state,
826 gboolean proximity)
827 {
828 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
829 GimpToolCompassPrivate *private = compass->private;
830 gint point;
831
832 private->mouse_x = coords->x;
833 private->mouse_y = coords->y;
834
835 point = gimp_tool_compass_get_point (compass, coords);
836
837 if (point >= 0)
838 {
839 GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
840 GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
841 gchar *status;
842
843 if (state & toggle_mask)
844 {
845 if (state & GDK_MOD1_MASK)
846 {
847 status = gimp_suggest_modifiers (_("Click to place "
848 "vertical and "
849 "horizontal guides"),
850 0,
851 NULL, NULL, NULL);
852 }
853 else
854 {
855 status = gimp_suggest_modifiers (_("Click to place a "
856 "horizontal guide"),
857 GDK_MOD1_MASK & ~state,
858 NULL, NULL, NULL);
859 }
860 }
861 else if (state & GDK_MOD1_MASK)
862 {
863 status = gimp_suggest_modifiers (_("Click to place a "
864 "vertical guide"),
865 toggle_mask & ~state,
866 NULL, NULL, NULL);
867 }
868 else if ((state & extend_mask) &&
869 ! ((point == 0) && (private->n_points == 3)))
870 {
871 status = gimp_suggest_modifiers (_("Click-Drag to add a "
872 "new point"),
873 (toggle_mask |
874 GDK_MOD1_MASK) & ~state,
875 NULL, NULL, NULL);
876 }
877 else
878 {
879 if ((point == 0) && (private->n_points == 3))
880 state |= extend_mask;
881
882 status = gimp_suggest_modifiers (_("Click-Drag to move this "
883 "point"),
884 (extend_mask |
885 toggle_mask |
886 GDK_MOD1_MASK) & ~state,
887 NULL, NULL, NULL);
888 }
889
890 gimp_tool_widget_set_status (widget, status);
891
892 g_free (status);
893 }
894 else
895 {
896 if ((private->n_points > 1) && (state & GDK_MOD1_MASK))
897 {
898 gimp_tool_widget_set_status (widget,
899 _("Click-Drag to move all points"));
900 }
901 else
902 {
903 gimp_tool_widget_set_status (widget, NULL);
904 }
905 }
906
907 if (point != private->point)
908 {
909 private->point = point;
910
911 gimp_tool_compass_update_hilight (compass);
912 }
913 }
914
915 void
gimp_tool_compass_leave_notify(GimpToolWidget * widget)916 gimp_tool_compass_leave_notify (GimpToolWidget *widget)
917 {
918 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
919 GimpToolCompassPrivate *private = compass->private;
920
921 if (private->point != -1)
922 {
923 private->point = -1;
924
925 gimp_tool_compass_update_hilight (compass);
926 }
927
928 GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
929 }
930
931 static void
gimp_tool_compass_motion_modifier(GimpToolWidget * widget,GdkModifierType key,gboolean press,GdkModifierType state)932 gimp_tool_compass_motion_modifier (GimpToolWidget *widget,
933 GdkModifierType key,
934 gboolean press,
935 GdkModifierType state)
936 {
937 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
938 GimpToolCompassPrivate *private = compass->private;
939
940 if (key == gimp_get_constrain_behavior_mask () &&
941 private->function == MOVING)
942 {
943 gint new_x[3];
944 gint new_y[3];
945 gdouble x = private->mouse_x;
946 gdouble y = private->mouse_y;
947
948 new_x[0] = private->x[0];
949 new_y[0] = private->y[0];
950 new_x[1] = private->x[1];
951 new_y[1] = private->y[1];
952 new_x[2] = private->x[2];
953 new_y[2] = private->y[2];
954
955 if (press)
956 {
957 gimp_display_shell_constrain_line (gimp_tool_widget_get_shell (widget),
958 private->x[0], private->y[0],
959 &x, &y,
960 GIMP_CONSTRAIN_LINE_15_DEGREES);
961 }
962
963 new_x[private->point] = ROUND (x);
964 new_y[private->point] = ROUND (y);
965
966 g_object_set (compass,
967 "x1", new_x[0],
968 "y1", new_y[0],
969 "x2", new_x[1],
970 "y2", new_y[1],
971 "x3", new_x[2],
972 "y3", new_y[2],
973 NULL);
974 }
975 }
976
977 static gboolean
gimp_tool_compass_get_cursor(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,GimpCursorType * cursor,GimpToolCursorType * tool_cursor,GimpCursorModifier * modifier)978 gimp_tool_compass_get_cursor (GimpToolWidget *widget,
979 const GimpCoords *coords,
980 GdkModifierType state,
981 GimpCursorType *cursor,
982 GimpToolCursorType *tool_cursor,
983 GimpCursorModifier *modifier)
984 {
985 GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
986 GimpToolCompassPrivate *private = compass->private;
987
988 if (private->point != -1)
989 {
990 GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
991 GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
992
993 if (state & toggle_mask)
994 {
995 if (state & GDK_MOD1_MASK)
996 {
997 *cursor = GIMP_CURSOR_CORNER_BOTTOM_RIGHT;
998 return TRUE;
999 }
1000 else
1001 {
1002 *cursor = GIMP_CURSOR_SIDE_BOTTOM;
1003 return TRUE;
1004 }
1005 }
1006 else if (state & GDK_MOD1_MASK)
1007 {
1008 *cursor = GIMP_CURSOR_SIDE_RIGHT;
1009 return TRUE;
1010 }
1011 else if ((state & extend_mask) &&
1012 ! ((private->point == 0) &&
1013 (private->n_points == 3)))
1014 {
1015 *modifier = GIMP_CURSOR_MODIFIER_PLUS;
1016 return TRUE;
1017 }
1018 else
1019 {
1020 *modifier = GIMP_CURSOR_MODIFIER_MOVE;
1021 return TRUE;
1022 }
1023 }
1024 else
1025 {
1026 if ((private->n_points > 1) && (state & GDK_MOD1_MASK))
1027 {
1028 *modifier = GIMP_CURSOR_MODIFIER_MOVE;
1029 return TRUE;
1030 }
1031 }
1032
1033 return FALSE;
1034 }
1035
1036 static gint
gimp_tool_compass_get_point(GimpToolCompass * compass,const GimpCoords * coords)1037 gimp_tool_compass_get_point (GimpToolCompass *compass,
1038 const GimpCoords *coords)
1039 {
1040 GimpToolCompassPrivate *private = compass->private;
1041 gint i;
1042
1043 for (i = 0; i < private->n_points; i++)
1044 {
1045 if (gimp_canvas_item_hit (private->handles[i],
1046 coords->x, coords->y))
1047 {
1048 return i;
1049 }
1050 }
1051
1052 return -1;
1053 }
1054
1055 static void
gimp_tool_compass_update_hilight(GimpToolCompass * compass)1056 gimp_tool_compass_update_hilight (GimpToolCompass *compass)
1057 {
1058 GimpToolCompassPrivate *private = compass->private;
1059 gint i;
1060
1061 for (i = 0; i < private->n_points; i++)
1062 {
1063 if (private->handles[i])
1064 {
1065 gimp_canvas_item_set_highlight (private->handles[i],
1066 private->point == i);
1067 }
1068 }
1069 }
1070
1071 static void
gimp_tool_compass_update_angle(GimpToolCompass * compass,GimpCompassOrientation orientation,gboolean flip)1072 gimp_tool_compass_update_angle (GimpToolCompass *compass,
1073 GimpCompassOrientation orientation,
1074 gboolean flip)
1075 {
1076 GimpToolWidget *widget = GIMP_TOOL_WIDGET (compass);
1077 GimpToolCompassPrivate *private = compass->private;
1078 GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
1079 GimpImage *image = gimp_display_get_image (shell->display);
1080 GimpVector2 radius1;
1081 GimpVector2 radius2;
1082 gdouble pixel_angle;
1083 gdouble unit_angle;
1084 gdouble xres;
1085 gdouble yres;
1086
1087 gimp_image_get_resolution (image, &xres, &yres);
1088
1089 private->radius1.x = private->x[1] - private->x[0];
1090 private->radius1.y = private->y[1] - private->y[0];
1091
1092 if (private->n_points == 3)
1093 {
1094 orientation = GIMP_COMPASS_ORIENTATION_AUTO;
1095
1096 private->radius2.x = private->x[2] - private->x[0];
1097 private->radius2.y = private->y[2] - private->y[0];
1098 }
1099 else
1100 {
1101 gdouble angle = -shell->rotate_angle * G_PI / 180.0;
1102
1103 if (orientation == GIMP_COMPASS_ORIENTATION_VERTICAL)
1104 angle -= G_PI / 2.0;
1105
1106 if (flip)
1107 angle += G_PI;
1108
1109 if (shell->flip_horizontally)
1110 angle = G_PI - angle;
1111 if (shell->flip_vertically)
1112 angle = -angle;
1113
1114 private->radius2.x = cos (angle);
1115 private->radius2.y = sin (angle);
1116
1117 if (! shell->dot_for_dot)
1118 {
1119 private->radius2.x *= xres;
1120 private->radius2.y *= yres;
1121
1122 gimp_vector2_normalize (&private->radius2);
1123 }
1124 }
1125
1126 radius1 = private->radius1;
1127 radius2 = private->radius2;
1128
1129 pixel_angle = atan2 (gimp_vector2_cross_product (&radius1, &radius2).x,
1130 gimp_vector2_inner_product (&radius1, &radius2));
1131
1132 radius1.x /= xres;
1133 radius1.y /= yres;
1134
1135 radius2.x /= xres;
1136 radius2.y /= yres;
1137
1138 unit_angle = atan2 (gimp_vector2_cross_product (&radius1, &radius2).x,
1139 gimp_vector2_inner_product (&radius1, &radius2));
1140
1141 if (shell->dot_for_dot)
1142 private->display_angle = pixel_angle;
1143 else
1144 private->display_angle = unit_angle;
1145
1146 if (private->n_points == 2)
1147 {
1148 if (! flip && fabs (private->display_angle) > G_PI / 2.0 + EPSILON)
1149 {
1150 gimp_tool_compass_update_angle (compass, orientation, TRUE);
1151
1152 return;
1153 }
1154 else if (orientation == GIMP_COMPASS_ORIENTATION_AUTO)
1155 {
1156 if (fabs (private->display_angle) <= G_PI / 4.0 + EPSILON)
1157 {
1158 orientation = GIMP_COMPASS_ORIENTATION_HORIZONTAL;
1159 }
1160 else
1161 {
1162 gimp_tool_compass_update_angle (compass,
1163 GIMP_COMPASS_ORIENTATION_VERTICAL,
1164 FALSE);
1165
1166 return;
1167 }
1168 }
1169 }
1170
1171 if (fabs (pixel_angle - private->pixel_angle) > EPSILON)
1172 {
1173 private->pixel_angle = pixel_angle;
1174
1175 g_object_notify (G_OBJECT (compass), "pixel-angle");
1176 }
1177
1178 if (fabs (unit_angle - private->unit_angle) > EPSILON)
1179 {
1180 private->unit_angle = unit_angle;
1181
1182 g_object_notify (G_OBJECT (compass), "unit-angle");
1183 }
1184
1185 if (orientation != private->effective_orientation)
1186 {
1187 private->effective_orientation = orientation;
1188
1189 g_object_notify (G_OBJECT (compass), "effective-orientation");
1190 }
1191 }
1192
1193
1194 /* public functions */
1195
1196 GimpToolWidget *
gimp_tool_compass_new(GimpDisplayShell * shell,GimpCompassOrientation orientation,gint n_points,gint x1,gint y1,gint x2,gint y2,gint x3,gint y3)1197 gimp_tool_compass_new (GimpDisplayShell *shell,
1198 GimpCompassOrientation orientation,
1199 gint n_points,
1200 gint x1,
1201 gint y1,
1202 gint x2,
1203 gint y2,
1204 gint x3,
1205 gint y3)
1206 {
1207 g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
1208
1209 return g_object_new (GIMP_TYPE_TOOL_COMPASS,
1210 "shell", shell,
1211 "orientation", orientation,
1212 "n-points", n_points,
1213 "x1", x1,
1214 "y1", y1,
1215 "x2", x2,
1216 "y2", y2,
1217 NULL);
1218 }
1219