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