1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimptoolhandlegrid.c
5  * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
6  *
7  * Based on GimpHandleTransformTool
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
21  */
22 
23 #include "config.h"
24 
25 #include <gegl.h>
26 #include <gtk/gtk.h>
27 
28 #include "libgimpbase/gimpbase.h"
29 #include "libgimpmath/gimpmath.h"
30 
31 #include "display-types.h"
32 
33 #include "core/gimp-transform-utils.h"
34 #include "core/gimp-utils.h"
35 
36 #include "widgets/gimpwidgets-utils.h"
37 
38 #include "gimpcanvashandle.h"
39 #include "gimpdisplayshell.h"
40 #include "gimptoolhandlegrid.h"
41 
42 #include "gimp-intl.h"
43 
44 
45 enum
46 {
47   PROP_0,
48   PROP_HANDLE_MODE,
49   PROP_N_HANDLES,
50   PROP_ORIG_X1,
51   PROP_ORIG_Y1,
52   PROP_ORIG_X2,
53   PROP_ORIG_Y2,
54   PROP_ORIG_X3,
55   PROP_ORIG_Y3,
56   PROP_ORIG_X4,
57   PROP_ORIG_Y4,
58   PROP_TRANS_X1,
59   PROP_TRANS_Y1,
60   PROP_TRANS_X2,
61   PROP_TRANS_Y2,
62   PROP_TRANS_X3,
63   PROP_TRANS_Y3,
64   PROP_TRANS_X4,
65   PROP_TRANS_Y4
66 };
67 
68 
69 struct _GimpToolHandleGridPrivate
70 {
71   GimpTransformHandleMode  handle_mode; /* enum to be renamed */
72 
73   gint        n_handles;
74   GimpVector2 orig[4];
75   GimpVector2 trans[4];
76 
77   gint     handle;
78   gdouble  last_x;
79   gdouble  last_y;
80 
81   gboolean hover;
82   gdouble  mouse_x;
83   gdouble  mouse_y;
84 
85   GimpCanvasItem *handles[5];
86 };
87 
88 
89 /*  local function prototypes  */
90 
91 static void   gimp_tool_handle_grid_constructed    (GObject               *object);
92 static void   gimp_tool_handle_grid_set_property   (GObject               *object,
93                                                     guint                  property_id,
94                                                     const GValue          *value,
95                                                     GParamSpec            *pspec);
96 static void   gimp_tool_handle_grid_get_property   (GObject               *object,
97                                                     guint                  property_id,
98                                                     GValue                *value,
99                                                     GParamSpec            *pspec);
100 
101 static void   gimp_tool_handle_grid_changed        (GimpToolWidget        *widget);
102 static gint   gimp_tool_handle_grid_button_press   (GimpToolWidget        *widget,
103                                                     const GimpCoords      *coords,
104                                                     guint32                time,
105                                                     GdkModifierType        state,
106                                                     GimpButtonPressType    press_type);
107 static void   gimp_tool_handle_grid_button_release (GimpToolWidget        *widget,
108                                                     const GimpCoords      *coords,
109                                                     guint32                time,
110                                                     GdkModifierType        state,
111                                                     GimpButtonReleaseType  release_type);
112 static void   gimp_tool_handle_grid_motion         (GimpToolWidget        *widget,
113                                                     const GimpCoords      *coords,
114                                                     guint32                time,
115                                                     GdkModifierType        state);
116 static GimpHit  gimp_tool_handle_grid_hit          (GimpToolWidget        *widget,
117                                                     const GimpCoords      *coords,
118                                                     GdkModifierType        state,
119                                                     gboolean               proximity);
120 static void     gimp_tool_handle_grid_hover        (GimpToolWidget        *widget,
121                                                     const GimpCoords      *coords,
122                                                     GdkModifierType        state,
123                                                     gboolean               proximity);
124 static void     gimp_tool_handle_grid_leave_notify (GimpToolWidget        *widget);
125 static gboolean gimp_tool_handle_grid_get_cursor   (GimpToolWidget        *widget,
126                                                     const GimpCoords      *coords,
127                                                     GdkModifierType        state,
128                                                     GimpCursorType        *cursor,
129                                                     GimpToolCursorType    *tool_cursor,
130                                                     GimpCursorModifier    *modifier);
131 
132 static gint     gimp_tool_handle_grid_get_handle     (GimpToolHandleGrid  *grid,
133                                                       const GimpCoords    *coords);
134 static void     gimp_tool_handle_grid_update_hilight (GimpToolHandleGrid  *grid);
135 static void     gimp_tool_handle_grid_update_matrix  (GimpToolHandleGrid  *grid);
136 
137 static gboolean is_handle_position_valid           (GimpToolHandleGrid    *grid,
138                                                     gint                   handle);
139 static void     handle_micro_move                  (GimpToolHandleGrid    *grid,
140                                                     gint                   handle);
141 
142 static inline gdouble calc_angle               (gdouble ax,
143                                                 gdouble ay,
144                                                 gdouble bx,
145                                                 gdouble by);
146 static inline gdouble calc_len                 (gdouble a,
147                                                 gdouble b);
148 static inline gdouble calc_lineintersect_ratio (gdouble p1x,
149                                                 gdouble p1y,
150                                                 gdouble p2x,
151                                                 gdouble p2y,
152                                                 gdouble q1x,
153                                                 gdouble q1y,
154                                                 gdouble q2x,
155                                                 gdouble q2y);
156 
157 
G_DEFINE_TYPE_WITH_PRIVATE(GimpToolHandleGrid,gimp_tool_handle_grid,GIMP_TYPE_TOOL_TRANSFORM_GRID)158 G_DEFINE_TYPE_WITH_PRIVATE (GimpToolHandleGrid, gimp_tool_handle_grid,
159                             GIMP_TYPE_TOOL_TRANSFORM_GRID)
160 
161 #define parent_class gimp_tool_handle_grid_parent_class
162 
163 
164 static void
165 gimp_tool_handle_grid_class_init (GimpToolHandleGridClass *klass)
166 {
167   GObjectClass        *object_class = G_OBJECT_CLASS (klass);
168   GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
169 
170   object_class->constructed     = gimp_tool_handle_grid_constructed;
171   object_class->set_property    = gimp_tool_handle_grid_set_property;
172   object_class->get_property    = gimp_tool_handle_grid_get_property;
173 
174   widget_class->changed         = gimp_tool_handle_grid_changed;
175   widget_class->button_press    = gimp_tool_handle_grid_button_press;
176   widget_class->button_release  = gimp_tool_handle_grid_button_release;
177   widget_class->motion          = gimp_tool_handle_grid_motion;
178   widget_class->hit             = gimp_tool_handle_grid_hit;
179   widget_class->hover           = gimp_tool_handle_grid_hover;
180   widget_class->leave_notify    = gimp_tool_handle_grid_leave_notify;
181   widget_class->get_cursor      = gimp_tool_handle_grid_get_cursor;
182 
183   g_object_class_install_property (object_class, PROP_HANDLE_MODE,
184                                    g_param_spec_enum ("handle-mode",
185                                                       NULL, NULL,
186                                                       GIMP_TYPE_TRANSFORM_HANDLE_MODE,
187                                                       GIMP_HANDLE_MODE_ADD_TRANSFORM,
188                                                       GIMP_PARAM_READWRITE |
189                                                       G_PARAM_CONSTRUCT));
190 
191   g_object_class_install_property (object_class, PROP_N_HANDLES,
192                                    g_param_spec_int ("n-handles",
193                                                      NULL, NULL,
194                                                      0, 4, 0,
195                                                      GIMP_PARAM_READWRITE |
196                                                      G_PARAM_CONSTRUCT));
197 
198   g_object_class_install_property (object_class, PROP_ORIG_X1,
199                                    g_param_spec_double ("orig-x1",
200                                                         NULL, NULL,
201                                                         -GIMP_MAX_IMAGE_SIZE,
202                                                         GIMP_MAX_IMAGE_SIZE,
203                                                         0.0,
204                                                         GIMP_PARAM_READWRITE |
205                                                         G_PARAM_CONSTRUCT));
206 
207   g_object_class_install_property (object_class, PROP_ORIG_Y1,
208                                    g_param_spec_double ("orig-y1",
209                                                         NULL, NULL,
210                                                         -GIMP_MAX_IMAGE_SIZE,
211                                                         GIMP_MAX_IMAGE_SIZE,
212                                                         0.0,
213                                                         GIMP_PARAM_READWRITE |
214                                                         G_PARAM_CONSTRUCT));
215 
216   g_object_class_install_property (object_class, PROP_ORIG_X2,
217                                    g_param_spec_double ("orig-x2",
218                                                         NULL, NULL,
219                                                         -GIMP_MAX_IMAGE_SIZE,
220                                                         GIMP_MAX_IMAGE_SIZE,
221                                                         0.0,
222                                                         GIMP_PARAM_READWRITE |
223                                                         G_PARAM_CONSTRUCT));
224 
225   g_object_class_install_property (object_class, PROP_ORIG_Y2,
226                                    g_param_spec_double ("orig-y2",
227                                                         NULL, NULL,
228                                                         -GIMP_MAX_IMAGE_SIZE,
229                                                         GIMP_MAX_IMAGE_SIZE,
230                                                         0.0,
231                                                         GIMP_PARAM_READWRITE |
232                                                         G_PARAM_CONSTRUCT));
233 
234   g_object_class_install_property (object_class, PROP_ORIG_X3,
235                                    g_param_spec_double ("orig-x3",
236                                                         NULL, NULL,
237                                                         -GIMP_MAX_IMAGE_SIZE,
238                                                         GIMP_MAX_IMAGE_SIZE,
239                                                         0.0,
240                                                         GIMP_PARAM_READWRITE |
241                                                         G_PARAM_CONSTRUCT));
242 
243   g_object_class_install_property (object_class, PROP_ORIG_Y3,
244                                    g_param_spec_double ("orig-y3",
245                                                         NULL, NULL,
246                                                         -GIMP_MAX_IMAGE_SIZE,
247                                                         GIMP_MAX_IMAGE_SIZE,
248                                                         0.0,
249                                                         GIMP_PARAM_READWRITE |
250                                                         G_PARAM_CONSTRUCT));
251 
252   g_object_class_install_property (object_class, PROP_ORIG_X4,
253                                    g_param_spec_double ("orig-x4",
254                                                         NULL, NULL,
255                                                         -GIMP_MAX_IMAGE_SIZE,
256                                                         GIMP_MAX_IMAGE_SIZE,
257                                                         0.0,
258                                                         GIMP_PARAM_READWRITE |
259                                                         G_PARAM_CONSTRUCT));
260 
261   g_object_class_install_property (object_class, PROP_ORIG_Y4,
262                                    g_param_spec_double ("orig-y4",
263                                                         NULL, NULL,
264                                                         -GIMP_MAX_IMAGE_SIZE,
265                                                         GIMP_MAX_IMAGE_SIZE,
266                                                         0.0,
267                                                         GIMP_PARAM_READWRITE |
268                                                         G_PARAM_CONSTRUCT));
269 
270   g_object_class_install_property (object_class, PROP_TRANS_X1,
271                                    g_param_spec_double ("trans-x1",
272                                                         NULL, NULL,
273                                                         -GIMP_MAX_IMAGE_SIZE,
274                                                         GIMP_MAX_IMAGE_SIZE,
275                                                         0.0,
276                                                         GIMP_PARAM_READWRITE |
277                                                         G_PARAM_CONSTRUCT));
278 
279   g_object_class_install_property (object_class, PROP_TRANS_Y1,
280                                    g_param_spec_double ("trans-y1",
281                                                         NULL, NULL,
282                                                         -GIMP_MAX_IMAGE_SIZE,
283                                                         GIMP_MAX_IMAGE_SIZE,
284                                                         0.0,
285                                                         GIMP_PARAM_READWRITE |
286                                                         G_PARAM_CONSTRUCT));
287 
288   g_object_class_install_property (object_class, PROP_TRANS_X2,
289                                    g_param_spec_double ("trans-x2",
290                                                         NULL, NULL,
291                                                         -GIMP_MAX_IMAGE_SIZE,
292                                                         GIMP_MAX_IMAGE_SIZE,
293                                                         0.0,
294                                                         GIMP_PARAM_READWRITE |
295                                                         G_PARAM_CONSTRUCT));
296 
297   g_object_class_install_property (object_class, PROP_TRANS_Y2,
298                                    g_param_spec_double ("trans-y2",
299                                                         NULL, NULL,
300                                                         -GIMP_MAX_IMAGE_SIZE,
301                                                         GIMP_MAX_IMAGE_SIZE,
302                                                         0.0,
303                                                         GIMP_PARAM_READWRITE |
304                                                         G_PARAM_CONSTRUCT));
305 
306   g_object_class_install_property (object_class, PROP_TRANS_X3,
307                                    g_param_spec_double ("trans-x3",
308                                                         NULL, NULL,
309                                                         -GIMP_MAX_IMAGE_SIZE,
310                                                         GIMP_MAX_IMAGE_SIZE,
311                                                         0.0,
312                                                         GIMP_PARAM_READWRITE |
313                                                         G_PARAM_CONSTRUCT));
314 
315   g_object_class_install_property (object_class, PROP_TRANS_Y3,
316                                    g_param_spec_double ("trans-y3",
317                                                         NULL, NULL,
318                                                         -GIMP_MAX_IMAGE_SIZE,
319                                                         GIMP_MAX_IMAGE_SIZE,
320                                                         0.0,
321                                                         GIMP_PARAM_READWRITE |
322                                                         G_PARAM_CONSTRUCT));
323 
324   g_object_class_install_property (object_class, PROP_TRANS_X4,
325                                    g_param_spec_double ("trans-x4",
326                                                         NULL, NULL,
327                                                         -GIMP_MAX_IMAGE_SIZE,
328                                                         GIMP_MAX_IMAGE_SIZE,
329                                                         0.0,
330                                                         GIMP_PARAM_READWRITE |
331                                                         G_PARAM_CONSTRUCT));
332 
333   g_object_class_install_property (object_class, PROP_TRANS_Y4,
334                                    g_param_spec_double ("trans-y4",
335                                                         NULL, NULL,
336                                                         -GIMP_MAX_IMAGE_SIZE,
337                                                         GIMP_MAX_IMAGE_SIZE,
338                                                         0.0,
339                                                         GIMP_PARAM_READWRITE |
340                                                         G_PARAM_CONSTRUCT));
341 }
342 
343 static void
gimp_tool_handle_grid_init(GimpToolHandleGrid * grid)344 gimp_tool_handle_grid_init (GimpToolHandleGrid *grid)
345 {
346   grid->private = gimp_tool_handle_grid_get_instance_private (grid);
347 }
348 
349 static void
gimp_tool_handle_grid_constructed(GObject * object)350 gimp_tool_handle_grid_constructed (GObject *object)
351 {
352   GimpToolHandleGrid        *grid    = GIMP_TOOL_HANDLE_GRID (object);
353   GimpToolWidget            *widget  = GIMP_TOOL_WIDGET (object);
354   GimpToolHandleGridPrivate *private = grid->private;
355   gint                       i;
356 
357   G_OBJECT_CLASS (parent_class)->constructed (object);
358 
359   for (i = 0; i < 4; i++)
360     {
361       private->handles[i + 1] =
362         gimp_tool_widget_add_handle (widget,
363                                      GIMP_HANDLE_CIRCLE,
364                                      0, 0,
365                                      GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
366                                      GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
367                                      GIMP_HANDLE_ANCHOR_CENTER);
368     }
369 
370   gimp_tool_handle_grid_changed (widget);
371 }
372 
373 static void
gimp_tool_handle_grid_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)374 gimp_tool_handle_grid_set_property (GObject      *object,
375                                     guint         property_id,
376                                     const GValue *value,
377                                     GParamSpec   *pspec)
378 {
379   GimpToolHandleGrid        *grid    = GIMP_TOOL_HANDLE_GRID (object);
380   GimpToolHandleGridPrivate *private = grid->private;
381 
382   switch (property_id)
383     {
384     case PROP_HANDLE_MODE:
385       private->handle_mode = g_value_get_enum (value);
386       break;
387 
388     case PROP_N_HANDLES:
389       private->n_handles = g_value_get_int (value);
390       break;
391 
392     case PROP_ORIG_X1:
393       private->orig[0].x = g_value_get_double (value);
394       break;
395     case PROP_ORIG_Y1:
396       private->orig[0].y = g_value_get_double (value);
397       break;
398     case PROP_ORIG_X2:
399       private->orig[1].x = g_value_get_double (value);
400       break;
401     case PROP_ORIG_Y2:
402       private->orig[1].y = g_value_get_double (value);
403       break;
404     case PROP_ORIG_X3:
405       private->orig[2].x = g_value_get_double (value);
406       break;
407     case PROP_ORIG_Y3:
408       private->orig[2].y = g_value_get_double (value);
409       break;
410     case PROP_ORIG_X4:
411       private->orig[3].x = g_value_get_double (value);
412       break;
413     case PROP_ORIG_Y4:
414       private->orig[3].y = g_value_get_double (value);
415       break;
416 
417     case PROP_TRANS_X1:
418       private->trans[0].x = g_value_get_double (value);
419       break;
420     case PROP_TRANS_Y1:
421       private->trans[0].y = g_value_get_double (value);
422       break;
423     case PROP_TRANS_X2:
424       private->trans[1].x = g_value_get_double (value);
425       break;
426     case PROP_TRANS_Y2:
427       private->trans[1].y = g_value_get_double (value);
428       break;
429     case PROP_TRANS_X3:
430       private->trans[2].x = g_value_get_double (value);
431       break;
432     case PROP_TRANS_Y3:
433       private->trans[2].y = g_value_get_double (value);
434       break;
435     case PROP_TRANS_X4:
436       private->trans[3].x = g_value_get_double (value);
437       break;
438     case PROP_TRANS_Y4:
439       private->trans[3].y = g_value_get_double (value);
440       break;
441 
442     default:
443       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
444       break;
445     }
446 }
447 
448 static void
gimp_tool_handle_grid_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)449 gimp_tool_handle_grid_get_property (GObject    *object,
450                                     guint       property_id,
451                                     GValue     *value,
452                                     GParamSpec *pspec)
453 {
454   GimpToolHandleGrid        *grid    = GIMP_TOOL_HANDLE_GRID (object);
455   GimpToolHandleGridPrivate *private = grid->private;
456 
457   switch (property_id)
458     {
459     case PROP_N_HANDLES:
460       g_value_set_int (value, private->n_handles);
461       break;
462 
463     case PROP_HANDLE_MODE:
464       g_value_set_enum (value, private->handle_mode);
465       break;
466 
467     case PROP_ORIG_X1:
468       g_value_set_double (value, private->orig[0].x);
469       break;
470     case PROP_ORIG_Y1:
471       g_value_set_double (value, private->orig[0].y);
472       break;
473     case PROP_ORIG_X2:
474       g_value_set_double (value, private->orig[1].x);
475       break;
476     case PROP_ORIG_Y2:
477       g_value_set_double (value, private->orig[1].y);
478       break;
479     case PROP_ORIG_X3:
480       g_value_set_double (value, private->orig[2].x);
481       break;
482     case PROP_ORIG_Y3:
483       g_value_set_double (value, private->orig[2].y);
484       break;
485     case PROP_ORIG_X4:
486       g_value_set_double (value, private->orig[3].x);
487       break;
488     case PROP_ORIG_Y4:
489       g_value_set_double (value, private->orig[3].y);
490       break;
491 
492     case PROP_TRANS_X1:
493       g_value_set_double (value, private->trans[0].x);
494       break;
495     case PROP_TRANS_Y1:
496       g_value_set_double (value, private->trans[0].y);
497       break;
498     case PROP_TRANS_X2:
499       g_value_set_double (value, private->trans[1].x);
500       break;
501     case PROP_TRANS_Y2:
502       g_value_set_double (value, private->trans[1].y);
503       break;
504     case PROP_TRANS_X3:
505       g_value_set_double (value, private->trans[2].x);
506       break;
507     case PROP_TRANS_Y3:
508       g_value_set_double (value, private->trans[2].y);
509       break;
510     case PROP_TRANS_X4:
511       g_value_set_double (value, private->trans[3].x);
512       break;
513     case PROP_TRANS_Y4:
514       g_value_set_double (value, private->trans[3].y);
515       break;
516 
517     default:
518       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
519       break;
520     }
521 }
522 
523 static void
gimp_tool_handle_grid_changed(GimpToolWidget * widget)524 gimp_tool_handle_grid_changed (GimpToolWidget *widget)
525 {
526   GimpToolHandleGrid        *grid    = GIMP_TOOL_HANDLE_GRID (widget);
527   GimpToolHandleGridPrivate *private = grid->private;
528   gint                       i;
529 
530   GIMP_TOOL_WIDGET_CLASS (parent_class)->changed (widget);
531 
532   for (i = 0; i < 4; i++)
533     {
534       gimp_canvas_handle_set_position (private->handles[i + 1],
535                                        private->trans[i].x,
536                                        private->trans[i].y);
537       gimp_canvas_item_set_visible (private->handles[i + 1],
538                                     i < private->n_handles);
539     }
540 
541   gimp_tool_handle_grid_update_hilight (grid);
542 }
543 
544 static gint
gimp_tool_handle_grid_button_press(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonPressType press_type)545 gimp_tool_handle_grid_button_press (GimpToolWidget      *widget,
546                                     const GimpCoords    *coords,
547                                     guint32              time,
548                                     GdkModifierType      state,
549                                     GimpButtonPressType  press_type)
550 {
551   GimpToolHandleGrid        *grid          = GIMP_TOOL_HANDLE_GRID (widget);
552   GimpToolHandleGridPrivate *private       = grid->private;
553   gint                       n_handles     = private->n_handles;
554   gint                       active_handle = private->handle - 1;
555 
556   switch (private->handle_mode)
557     {
558     case GIMP_HANDLE_MODE_ADD_TRANSFORM:
559       if (n_handles < 4 && active_handle == -1)
560         {
561           /* add handle */
562 
563           GimpMatrix3 *matrix;
564 
565           active_handle = n_handles;
566 
567           private->trans[active_handle].x = coords->x;
568           private->trans[active_handle].y = coords->y;
569           private->n_handles++;
570 
571           if (! is_handle_position_valid (grid, active_handle))
572             {
573               handle_micro_move (grid, active_handle);
574             }
575 
576           /* handle was added, calculate new original position */
577           g_object_get (grid,
578                         "transform", &matrix,
579                         NULL);
580 
581           gimp_matrix3_invert (matrix);
582           gimp_matrix3_transform_point (matrix,
583                                         private->trans[active_handle].x,
584                                         private->trans[active_handle].y,
585                                         &private->orig[active_handle].x,
586                                         &private->orig[active_handle].y);
587 
588           g_free (matrix);
589 
590           private->handle = active_handle + 1;
591 
592           g_object_notify (G_OBJECT (grid), "n-handles");
593         }
594       break;
595 
596     case GIMP_HANDLE_MODE_MOVE:
597       /* check for valid position and calculating of OX0...OY3 is
598        * done on button release
599        */
600       break;
601 
602     case GIMP_HANDLE_MODE_REMOVE:
603       if (n_handles > 0      &&
604           active_handle >= 0 &&
605           active_handle < 4)
606         {
607           /* remove handle */
608 
609           GimpVector2 temp  = private->trans[active_handle];
610           GimpVector2 tempo = private->orig[active_handle];
611           gint        i;
612 
613           n_handles--;
614           private->n_handles--;
615 
616           for (i = active_handle; i < n_handles; i++)
617             {
618               private->trans[i] = private->trans[i + 1];
619               private->orig[i]  = private->orig[i + 1];
620             }
621 
622           private->trans[n_handles] = temp;
623           private->orig[n_handles]  = tempo;
624 
625           g_object_notify (G_OBJECT (grid), "n-handles");
626         }
627       break;
628     }
629 
630   private->last_x = coords->x;
631   private->last_y = coords->y;
632 
633   return private->handle;
634 }
635 
636 static void
gimp_tool_handle_grid_button_release(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonReleaseType release_type)637 gimp_tool_handle_grid_button_release (GimpToolWidget        *widget,
638                                       const GimpCoords      *coords,
639                                       guint32                time,
640                                       GdkModifierType        state,
641                                       GimpButtonReleaseType  release_type)
642 {
643   GimpToolHandleGrid        *grid          = GIMP_TOOL_HANDLE_GRID (widget);
644   GimpToolHandleGridPrivate *private       = grid->private;
645   gint                       active_handle = private->handle - 1;
646 
647   if (private->handle_mode == GIMP_HANDLE_MODE_MOVE &&
648       active_handle >= 0 &&
649       active_handle < 4)
650     {
651       GimpMatrix3 *matrix;
652 
653       if (! is_handle_position_valid (grid, active_handle))
654         {
655           handle_micro_move (grid, active_handle);
656         }
657 
658       /* handle was moved, calculate new original position */
659       g_object_get (grid,
660                     "transform", &matrix,
661                     NULL);
662 
663       gimp_matrix3_invert (matrix);
664       gimp_matrix3_transform_point (matrix,
665                                     private->trans[active_handle].x,
666                                     private->trans[active_handle].y,
667                                     &private->orig[active_handle].x,
668                                     &private->orig[active_handle].y);
669 
670       g_free (matrix);
671 
672       gimp_tool_handle_grid_update_matrix (grid);
673     }
674 }
675 
676 void
gimp_tool_handle_grid_motion(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state)677 gimp_tool_handle_grid_motion (GimpToolWidget   *widget,
678                               const GimpCoords *coords,
679                               guint32           time,
680                               GdkModifierType   state)
681 {
682   GimpToolHandleGrid        *grid          = GIMP_TOOL_HANDLE_GRID (widget);
683   GimpToolHandleGridPrivate *private       = grid->private;
684   gint                       n_handles     = private->n_handles;
685   gint                       active_handle = private->handle - 1;
686   gdouble                    diff_x        = coords->x - private->last_x;
687   gdouble                    diff_y        = coords->y - private->last_y;
688 
689   private->mouse_x = coords->x;
690   private->mouse_y = coords->y;
691 
692   if (active_handle >= 0 && active_handle < 4)
693     {
694       if (private->handle_mode == GIMP_HANDLE_MODE_MOVE)
695         {
696           private->trans[active_handle].x += diff_x;
697           private->trans[active_handle].y += diff_y;
698 
699           /* check for valid position and calculating of OX0...OY3 is
700            * done on button release hopefully this makes the code run
701            * faster Moving could be even faster if there was caching
702            * for the image preview
703            */
704           gimp_canvas_handle_set_position (private->handles[active_handle + 1],
705                                            private->trans[active_handle].x,
706                                            private->trans[active_handle].y);
707         }
708       else if (private->handle_mode == GIMP_HANDLE_MODE_ADD_TRANSFORM)
709         {
710           gdouble angle, angle_sin, angle_cos, scale;
711           GimpVector2 fixed_handles[3];
712           GimpVector2 oldpos[4];
713           GimpVector2 newpos[4];
714           gint    i, j;
715 
716           for (i = 0, j = 0; i < 4; i++)
717             {
718               /* Find all visible handles that are not being moved */
719               if (i < n_handles && i != active_handle)
720                 {
721                   fixed_handles[j] = private->trans[i];
722                   j++;
723                 }
724 
725               newpos[i] = oldpos[i] = private->trans[i];
726             }
727 
728           newpos[active_handle].x = oldpos[active_handle].x + diff_x;
729           newpos[active_handle].y = oldpos[active_handle].y + diff_y;
730 
731           switch (n_handles)
732             {
733             case 1:
734               /* move */
735               for (i = 0; i < 4; i++)
736                 {
737                   newpos[i].x = oldpos[i].x + diff_x;
738                   newpos[i].y = oldpos[i].y + diff_y;
739                 }
740               break;
741 
742             case 2:
743               /* rotate and keep-aspect-scale */
744               scale =
745                 calc_len (newpos[active_handle].x - fixed_handles[0].x,
746                           newpos[active_handle].y - fixed_handles[0].y) /
747                 calc_len (oldpos[active_handle].x - fixed_handles[0].x,
748                           oldpos[active_handle].y - fixed_handles[0].y);
749 
750               angle = calc_angle (oldpos[active_handle].x - fixed_handles[0].x,
751                                   oldpos[active_handle].y - fixed_handles[0].y,
752                                   newpos[active_handle].x - fixed_handles[0].x,
753                                   newpos[active_handle].y - fixed_handles[0].y);
754 
755               angle_sin = sin (angle);
756               angle_cos = cos (angle);
757 
758               for (i = 2; i < 4; i++)
759                 {
760                   newpos[i].x =
761                     fixed_handles[0].x +
762                     scale * (angle_cos * (oldpos[i].x - fixed_handles[0].x) +
763                              angle_sin * (oldpos[i].y - fixed_handles[0].y));
764 
765                   newpos[i].y =
766                     fixed_handles[0].y +
767                     scale * (-angle_sin * (oldpos[i].x - fixed_handles[0].x) +
768                               angle_cos * (oldpos[i].y - fixed_handles[0].y));
769                 }
770               break;
771 
772             case 3:
773               /* shear and non-aspect-scale */
774               scale = calc_lineintersect_ratio (oldpos[3].x,
775                                                 oldpos[3].y,
776                                                 oldpos[active_handle].x,
777                                                 oldpos[active_handle].y,
778                                                 fixed_handles[0].x,
779                                                 fixed_handles[0].y,
780                                                 fixed_handles[1].x,
781                                                 fixed_handles[1].y);
782 
783               newpos[3].x = oldpos[3].x + scale * diff_x;
784               newpos[3].y = oldpos[3].y + scale * diff_y;
785               break;
786             }
787 
788           for (i = 0; i < 4; i++)
789             {
790               private->trans[i] = newpos[i];
791             }
792 
793           gimp_tool_handle_grid_update_matrix (grid);
794         }
795     }
796 
797   private->last_x = coords->x;
798   private->last_y = coords->y;
799 }
800 
801 static GimpHit
gimp_tool_handle_grid_hit(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)802 gimp_tool_handle_grid_hit (GimpToolWidget   *widget,
803                            const GimpCoords *coords,
804                            GdkModifierType   state,
805                            gboolean          proximity)
806 {
807   GimpToolHandleGrid        *grid    = GIMP_TOOL_HANDLE_GRID (widget);
808   GimpToolHandleGridPrivate *private = grid->private;
809 
810   if (proximity)
811     {
812       gint handle = gimp_tool_handle_grid_get_handle (grid, coords);
813 
814       switch (private->handle_mode)
815         {
816         case GIMP_HANDLE_MODE_ADD_TRANSFORM:
817           if (handle > 0)
818             return GIMP_HIT_DIRECT;
819           else
820             return GIMP_HIT_INDIRECT;
821           break;
822 
823         case GIMP_HANDLE_MODE_MOVE:
824         case GIMP_HANDLE_MODE_REMOVE:
825           if (private->handle > 0)
826             return GIMP_HIT_DIRECT;
827           break;
828         }
829     }
830 
831   return GIMP_HIT_NONE;
832 }
833 
834 static void
gimp_tool_handle_grid_hover(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)835 gimp_tool_handle_grid_hover (GimpToolWidget   *widget,
836                              const GimpCoords *coords,
837                              GdkModifierType   state,
838                              gboolean          proximity)
839 {
840   GimpToolHandleGrid        *grid    = GIMP_TOOL_HANDLE_GRID (widget);
841   GimpToolHandleGridPrivate *private = grid->private;
842   gchar                     *status  = NULL;
843 
844   private->hover   = TRUE;
845   private->mouse_x = coords->x;
846   private->mouse_y = coords->y;
847 
848   private->handle = gimp_tool_handle_grid_get_handle (grid, coords);
849 
850   if (proximity)
851     {
852       GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
853       GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
854 
855       switch (private->handle_mode)
856         {
857         case GIMP_HANDLE_MODE_ADD_TRANSFORM:
858           if (private->handle > 0)
859             {
860               const gchar *s = NULL;
861 
862               switch (private->n_handles)
863                 {
864                 case 1:
865                   s = _("Click-Drag to move");
866                   break;
867                 case 2:
868                   s = _("Click-Drag to rotate and scale");
869                   break;
870                 case 3:
871                   s = _("Click-Drag to shear and scale");
872                   break;
873                 case 4:
874                   s = _("Click-Drag to change perspective");
875                   break;
876                 }
877 
878               status = gimp_suggest_modifiers (s,
879                                                extend_mask | toggle_mask,
880                                                NULL, NULL, NULL);
881             }
882           else
883             {
884               if (private->n_handles < 4)
885                 status = g_strdup (_("Click to add a handle"));
886             }
887           break;
888 
889         case GIMP_HANDLE_MODE_MOVE:
890           if (private->handle > 0)
891             status = g_strdup (_("Click-Drag to move this handle"));
892           break;
893 
894         case GIMP_HANDLE_MODE_REMOVE:
895           if (private->handle > 0)
896             status = g_strdup (_("Click-Drag to remove this handle"));
897           break;
898         }
899     }
900 
901   gimp_tool_widget_set_status (widget, status);
902   g_free (status);
903 
904   gimp_tool_handle_grid_update_hilight (grid);
905 }
906 
907 static void
gimp_tool_handle_grid_leave_notify(GimpToolWidget * widget)908 gimp_tool_handle_grid_leave_notify (GimpToolWidget *widget)
909 {
910   GimpToolHandleGrid        *grid    = GIMP_TOOL_HANDLE_GRID (widget);
911   GimpToolHandleGridPrivate *private = grid->private;
912 
913   private->hover  = FALSE;
914   private->handle = 0;
915 
916   gimp_tool_handle_grid_update_hilight (grid);
917 
918   GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
919 }
920 
921 static gboolean
gimp_tool_handle_grid_get_cursor(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,GimpCursorType * cursor,GimpToolCursorType * tool_cursor,GimpCursorModifier * modifier)922 gimp_tool_handle_grid_get_cursor (GimpToolWidget     *widget,
923                                   const GimpCoords   *coords,
924                                   GdkModifierType     state,
925                                   GimpCursorType     *cursor,
926                                   GimpToolCursorType *tool_cursor,
927                                   GimpCursorModifier *modifier)
928 {
929   GimpToolHandleGrid        *grid    = GIMP_TOOL_HANDLE_GRID (widget);
930   GimpToolHandleGridPrivate *private = grid->private;
931 
932   *cursor      = GIMP_CURSOR_CROSSHAIR_SMALL;
933   *tool_cursor = GIMP_TOOL_CURSOR_NONE;
934   *modifier    = GIMP_CURSOR_MODIFIER_NONE;
935 
936   switch (private->handle_mode)
937     {
938     case GIMP_HANDLE_MODE_ADD_TRANSFORM:
939       if (private->handle > 0)
940         {
941           switch (private->n_handles)
942             {
943             case 1:
944               *tool_cursor = GIMP_TOOL_CURSOR_MOVE;
945               break;
946             case 2:
947               *tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
948               break;
949             case 3:
950               *tool_cursor = GIMP_TOOL_CURSOR_SHEAR;
951               break;
952             case 4:
953               *tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE;
954               break;
955             }
956         }
957       else
958         {
959           if (private->n_handles < 4)
960             *modifier = GIMP_CURSOR_MODIFIER_PLUS;
961           else
962             *modifier = GIMP_CURSOR_MODIFIER_BAD;
963         }
964       break;
965 
966     case GIMP_HANDLE_MODE_MOVE:
967       if (private->handle > 0)
968         *modifier = GIMP_CURSOR_MODIFIER_MOVE;
969       else
970         *modifier = GIMP_CURSOR_MODIFIER_BAD;
971       break;
972 
973     case GIMP_HANDLE_MODE_REMOVE:
974       if (private->handle > 0)
975         *modifier = GIMP_CURSOR_MODIFIER_MINUS;
976       else
977         *modifier = GIMP_CURSOR_MODIFIER_BAD;
978       break;
979     }
980 
981   return TRUE;
982 }
983 
984 static gint
gimp_tool_handle_grid_get_handle(GimpToolHandleGrid * grid,const GimpCoords * coords)985 gimp_tool_handle_grid_get_handle (GimpToolHandleGrid *grid,
986                                   const GimpCoords   *coords)
987 {
988   GimpToolHandleGridPrivate *private = grid->private;
989   gint                       i;
990 
991   for (i = 0; i < 4; i++)
992     {
993       if (private->handles[i + 1] &&
994           gimp_canvas_item_hit (private->handles[i + 1],
995                                 coords->x, coords->y))
996         {
997           return i + 1;
998         }
999     }
1000 
1001   return 0;
1002 }
1003 
1004 static void
gimp_tool_handle_grid_update_hilight(GimpToolHandleGrid * grid)1005 gimp_tool_handle_grid_update_hilight (GimpToolHandleGrid *grid)
1006 {
1007   GimpToolHandleGridPrivate *private = grid->private;
1008   gint                       i;
1009 
1010   for (i = 0; i < 4; i++)
1011     {
1012       GimpCanvasItem *item = private->handles[i + 1];
1013 
1014       if (item)
1015         {
1016           gdouble diameter = GIMP_CANVAS_HANDLE_SIZE_CIRCLE;
1017 
1018           if (private->hover)
1019             {
1020               diameter = gimp_canvas_handle_calc_size (
1021                 item,
1022                 private->mouse_x,
1023                 private->mouse_y,
1024                 GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
1025                 2 * GIMP_CANVAS_HANDLE_SIZE_CIRCLE);
1026             }
1027 
1028           gimp_canvas_handle_set_size (item, diameter, diameter);
1029           gimp_canvas_item_set_highlight (item, (i + 1) == private->handle);
1030         }
1031     }
1032 }
1033 
1034 static void
gimp_tool_handle_grid_update_matrix(GimpToolHandleGrid * grid)1035 gimp_tool_handle_grid_update_matrix (GimpToolHandleGrid *grid)
1036 {
1037   GimpToolHandleGridPrivate *private = grid->private;
1038   GimpMatrix3                transform;
1039   gboolean                   transform_valid;
1040 
1041   gimp_matrix3_identity (&transform);
1042   transform_valid = gimp_transform_matrix_generic (&transform,
1043                                                    private->orig,
1044                                                    private->trans);
1045 
1046   g_object_set (grid,
1047                 "transform",   &transform,
1048                 "show-guides", transform_valid,
1049                 NULL);
1050 }
1051 
1052 /* check if a handle is not on the connection line of two other handles */
1053 static gboolean
is_handle_position_valid(GimpToolHandleGrid * grid,gint handle)1054 is_handle_position_valid (GimpToolHandleGrid *grid,
1055                           gint                handle)
1056 {
1057   GimpToolHandleGridPrivate *private = grid->private;
1058   gint                       i, j, k;
1059 
1060   for (i = 0; i < 2; i++)
1061     {
1062       for (j = i + 1; j < 3; j++)
1063         {
1064           for (k = j + 1; i < 4; i++)
1065             {
1066               if (handle == i ||
1067                   handle == j ||
1068                   handle == k)
1069                 {
1070                   if ((private->trans[i].x -
1071                        private->trans[j].x) *
1072                       (private->trans[j].y -
1073                        private->trans[k].y) ==
1074 
1075                       (private->trans[j].x -
1076                        private->trans[k].x) *
1077                       (private->trans[i].y -
1078                        private->trans[j].y))
1079                     {
1080                       return FALSE;
1081                     }
1082                 }
1083             }
1084         }
1085     }
1086 
1087   return TRUE;
1088 }
1089 
1090 /* three handles on a line causes problems.
1091  * Let's move the new handle around a bit to find a better position */
1092 static void
handle_micro_move(GimpToolHandleGrid * grid,gint handle)1093 handle_micro_move (GimpToolHandleGrid *grid,
1094                    gint                handle)
1095 {
1096   GimpToolHandleGridPrivate *private = grid->private;
1097   gdouble                    posx    = private->trans[handle].x;
1098   gdouble                    posy    = private->trans[handle].y;
1099   gdouble                    dx, dy;
1100 
1101   for (dx = -0.1; dx < 0.11; dx += 0.1)
1102     {
1103       private->trans[handle].x = posx + dx;
1104 
1105       for (dy = -0.1; dy < 0.11; dy += 0.1)
1106         {
1107           private->trans[handle].y = posy + dy;
1108 
1109           if (is_handle_position_valid (grid, handle))
1110             {
1111               return;
1112             }
1113         }
1114     }
1115 }
1116 
1117 /* finds the clockwise angle between the vectors given, 0-2π */
1118 static inline gdouble
calc_angle(gdouble ax,gdouble ay,gdouble bx,gdouble by)1119 calc_angle (gdouble ax,
1120             gdouble ay,
1121             gdouble bx,
1122             gdouble by)
1123 {
1124   gdouble angle;
1125   gdouble direction;
1126   gdouble length = sqrt ((ax * ax + ay * ay) * (bx * bx + by * by));
1127 
1128   angle = acos ((ax * bx + ay * by) / length);
1129   direction = ax * by - ay * bx;
1130 
1131   return ((direction < 0) ? angle : 2 * G_PI - angle);
1132 }
1133 
1134 static inline gdouble
calc_len(gdouble a,gdouble b)1135 calc_len  (gdouble a,
1136            gdouble b)
1137 {
1138   return sqrt (a * a + b * b);
1139 }
1140 
1141 /* imagine two lines, one through the points p1 and p2, the other one
1142  * through the points q1 and q2. Find the intersection point r.
1143  * Calculate (distance p1 to r)/(distance p2 to r)
1144  */
1145 static inline gdouble
calc_lineintersect_ratio(gdouble p1x,gdouble p1y,gdouble p2x,gdouble p2y,gdouble q1x,gdouble q1y,gdouble q2x,gdouble q2y)1146 calc_lineintersect_ratio (gdouble p1x, gdouble p1y,
1147                           gdouble p2x, gdouble p2y,
1148                           gdouble q1x, gdouble q1y,
1149                           gdouble q2x, gdouble q2y)
1150 {
1151   gdouble denom, u;
1152 
1153   denom = (q2y - q1y) * (p2x - p1x) - (q2x - q1x) * (p2y - p1y);
1154   if (denom == 0.0)
1155     {
1156       /* u is infinite, so u/(u-1) is 1 */
1157       return 1.0;
1158     }
1159 
1160   u = (q2y - q1y) * (q1x - p1x) - (q1y - p1y) * (q2x - q1x);
1161   u /= denom;
1162 
1163   return u / (u - 1);
1164 }
1165 
1166 
1167 /*  public functions  */
1168 
1169 GimpToolWidget *
gimp_tool_handle_grid_new(GimpDisplayShell * shell,gdouble x1,gdouble y1,gdouble x2,gdouble y2)1170 gimp_tool_handle_grid_new (GimpDisplayShell *shell,
1171                            gdouble           x1,
1172                            gdouble           y1,
1173                            gdouble           x2,
1174                            gdouble           y2)
1175 {
1176   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
1177 
1178   return g_object_new (GIMP_TYPE_TOOL_HANDLE_GRID,
1179                        "shell",       shell,
1180                        "x1",          x1,
1181                        "y1",          y1,
1182                        "x2",          x2,
1183                        "y2",          y2,
1184                        "clip-guides", TRUE,
1185                        NULL);
1186 }
1187