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