1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * GThumb
5 *
6 * Copyright (C) 2009 Free Software Foundation, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <config.h>
23 #include <stdlib.h>
24 #include <math.h>
25 #include "cairo-utils.h"
26 #include "glib-utils.h"
27 #include "gth-image-selector.h"
28 #include "gth-marshal.h"
29 #include "gtk-utils.h"
30
31
32 #define DEFAULT_BORDER 40
33 #define MIN_BORDER 3
34 #define DRAG_THRESHOLD 1
35 #define STEP_INCREMENT 20.0 /* scroll increment. */
36 #define SCROLL_TIMEOUT 30 /* autoscroll timeout in milliseconds */
37
38
39 typedef struct {
40 int ref_count;
41 int id;
42 cairo_rectangle_int_t area;
43 GdkCursor *cursor;
44 } EventArea;
45
46
47 static EventArea *
event_area_new(GtkWidget * widget,int id,GdkCursorType cursor_type)48 event_area_new (GtkWidget *widget,
49 int id,
50 GdkCursorType cursor_type)
51 {
52 EventArea *event_area;
53
54 event_area = g_new0 (EventArea, 1);
55
56 event_area->ref_count = 1;
57 event_area->id = id;
58 event_area->area.x = 0;
59 event_area->area.y = 0;
60 event_area->area.width = 0;
61 event_area->area.height = 0;
62 event_area->cursor = _gdk_cursor_new_for_widget (widget, cursor_type);
63
64 return event_area;
65 }
66
67
68 G_GNUC_UNUSED
69 static void
event_area_ref(EventArea * event_area)70 event_area_ref (EventArea *event_area)
71 {
72 event_area->ref_count++;
73 }
74
75
76 static void
event_area_unref(EventArea * event_area)77 event_area_unref (EventArea *event_area)
78 {
79 event_area->ref_count--;
80
81 if (event_area->ref_count > 0)
82 return;
83
84 if (event_area->cursor != NULL)
85 g_object_unref (event_area->cursor);
86 g_free (event_area);
87 }
88
89
90 /**/
91
92
93 typedef enum {
94 C_SELECTION_AREA,
95 C_TOP_AREA,
96 C_BOTTOM_AREA,
97 C_LEFT_AREA,
98 C_RIGHT_AREA,
99 C_TOP_LEFT_AREA,
100 C_TOP_RIGHT_AREA,
101 C_BOTTOM_LEFT_AREA,
102 C_BOTTOM_RIGHT_AREA
103 } GthEventAreaType;
104
105
106 static GthEventAreaType
get_opposite_event_area_on_x(GthEventAreaType type)107 get_opposite_event_area_on_x (GthEventAreaType type)
108 {
109 GthEventAreaType opposite_type = C_SELECTION_AREA;
110 switch (type) {
111 case C_SELECTION_AREA:
112 opposite_type = C_SELECTION_AREA;
113 break;
114 case C_TOP_AREA:
115 opposite_type = C_TOP_AREA;
116 break;
117 case C_BOTTOM_AREA:
118 opposite_type = C_BOTTOM_AREA;
119 break;
120 case C_LEFT_AREA:
121 opposite_type = C_RIGHT_AREA;
122 break;
123 case C_RIGHT_AREA:
124 opposite_type = C_LEFT_AREA;
125 break;
126 case C_TOP_LEFT_AREA:
127 opposite_type = C_TOP_RIGHT_AREA;
128 break;
129 case C_TOP_RIGHT_AREA:
130 opposite_type = C_TOP_LEFT_AREA;
131 break;
132 case C_BOTTOM_LEFT_AREA:
133 opposite_type = C_BOTTOM_RIGHT_AREA;
134 break;
135 case C_BOTTOM_RIGHT_AREA:
136 opposite_type = C_BOTTOM_LEFT_AREA;
137 break;
138 }
139 return opposite_type;
140 }
141
142
143 static GthEventAreaType
get_opposite_event_area_on_y(GthEventAreaType type)144 get_opposite_event_area_on_y (GthEventAreaType type)
145 {
146 GthEventAreaType opposite_type = C_SELECTION_AREA;
147 switch (type) {
148 case C_SELECTION_AREA:
149 opposite_type = C_SELECTION_AREA;
150 break;
151 case C_TOP_AREA:
152 opposite_type = C_BOTTOM_AREA;
153 break;
154 case C_BOTTOM_AREA:
155 opposite_type = C_TOP_AREA;
156 break;
157 case C_LEFT_AREA:
158 opposite_type = C_LEFT_AREA;
159 break;
160 case C_RIGHT_AREA:
161 opposite_type = C_RIGHT_AREA;
162 break;
163 case C_TOP_LEFT_AREA:
164 opposite_type = C_BOTTOM_LEFT_AREA;
165 break;
166 case C_TOP_RIGHT_AREA:
167 opposite_type = C_BOTTOM_RIGHT_AREA;
168 break;
169 case C_BOTTOM_LEFT_AREA:
170 opposite_type = C_TOP_LEFT_AREA;
171 break;
172 case C_BOTTOM_RIGHT_AREA:
173 opposite_type = C_TOP_RIGHT_AREA;
174 break;
175 }
176 return opposite_type;
177 }
178
179
180 enum {
181 SELECTION_CHANGED,
182 SELECTED,
183 MOTION_NOTIFY,
184 MASK_VISIBILITY_CHANGED,
185 GRID_VISIBILITY_CHANGED,
186 LAST_SIGNAL
187 };
188
189
190 static guint signals[LAST_SIGNAL] = { 0 };
191
192
193 struct _GthImageSelectorPrivate {
194 GthImageViewer *viewer;
195 GthSelectorType type;
196
197 cairo_surface_t *surface;
198 cairo_rectangle_int_t surface_area;
199
200 gboolean use_ratio;
201 double ratio;
202 gboolean mask_visible;
203 GthGridType grid_type;
204 gboolean active;
205 gboolean bind_dimensions;
206 int bind_factor;
207
208 cairo_rectangle_int_t drag_start_selection_area;
209 cairo_rectangle_int_t selection_area;
210 cairo_rectangle_int_t selection;
211
212 GdkCursor *default_cursor;
213 GdkCursor *current_cursor;
214 GList *event_list;
215 EventArea *current_area;
216
217 guint timer_id; /* Timeout ID for
218 * autoscrolling */
219 double x_value_diff; /* Change the adjustment value
220 * by this
221 * amount when autoscrolling */
222 double y_value_diff;
223 };
224
225
226 static void gth_image_selector_gth_image_tool_interface_init (GthImageViewerToolInterface *iface);
227
228
G_DEFINE_TYPE_WITH_CODE(GthImageSelector,gth_image_selector,G_TYPE_OBJECT,G_ADD_PRIVATE (GthImageSelector)G_IMPLEMENT_INTERFACE (GTH_TYPE_IMAGE_VIEWER_TOOL,gth_image_selector_gth_image_tool_interface_init))229 G_DEFINE_TYPE_WITH_CODE (GthImageSelector,
230 gth_image_selector,
231 G_TYPE_OBJECT,
232 G_ADD_PRIVATE (GthImageSelector)
233 G_IMPLEMENT_INTERFACE (GTH_TYPE_IMAGE_VIEWER_TOOL,
234 gth_image_selector_gth_image_tool_interface_init))
235
236
237 static gboolean
238 point_in_rectangle (int x,
239 int y,
240 cairo_rectangle_int_t rect)
241 {
242 return ((x >= rect.x)
243 && (x <= rect.x + rect.width)
244 && (y >= rect.y)
245 && (y <= rect.y + rect.height));
246 }
247
248
249 static gboolean
rectangle_in_rectangle(cairo_rectangle_int_t r1,cairo_rectangle_int_t r2)250 rectangle_in_rectangle (cairo_rectangle_int_t r1,
251 cairo_rectangle_int_t r2)
252 {
253 return (point_in_rectangle (r1.x, r1.y, r2)
254 && point_in_rectangle (r1.x + r1.width,
255 r1.y + r1.height,
256 r2));
257 }
258
259
260 static gboolean
rectangle_equal(cairo_rectangle_int_t r1,cairo_rectangle_int_t r2)261 rectangle_equal (cairo_rectangle_int_t r1,
262 cairo_rectangle_int_t r2)
263 {
264 return ((r1.x == r2.x)
265 && (r1.y == r2.y)
266 && (r1.width == r2.width)
267 && (r1.height == r2.height));
268 }
269
270
271 static int
real_to_selector(GthImageSelector * self,int value)272 real_to_selector (GthImageSelector *self,
273 int value)
274 {
275 return IROUND (gth_image_viewer_get_zoom (self->priv->viewer) * value);
276 }
277
278
279 static void
convert_to_selection_area(GthImageSelector * self,cairo_rectangle_int_t real_area,cairo_rectangle_int_t * selection_area)280 convert_to_selection_area (GthImageSelector *self,
281 cairo_rectangle_int_t real_area,
282 cairo_rectangle_int_t *selection_area)
283 {
284 selection_area->x = real_to_selector (self, real_area.x);
285 selection_area->y = real_to_selector (self, real_area.y);
286 selection_area->width = real_to_selector (self, real_area.width);
287 selection_area->height = real_to_selector (self, real_area.height);
288 }
289
290
291 static void
add_event_area(GthImageSelector * self,int area_id,GdkCursorType cursor_type)292 add_event_area (GthImageSelector *self,
293 int area_id,
294 GdkCursorType cursor_type)
295 {
296 EventArea *event_area;
297
298 event_area = event_area_new (GTK_WIDGET (self->priv->viewer), area_id, cursor_type);
299 self->priv->event_list = g_list_prepend (self->priv->event_list, event_area);
300 }
301
302
303 static void
free_event_area_list(GthImageSelector * self)304 free_event_area_list (GthImageSelector *self)
305 {
306 if (self->priv->event_list != NULL) {
307 g_list_foreach (self->priv->event_list, (GFunc) event_area_unref, NULL);
308 g_list_free (self->priv->event_list);
309 self->priv->event_list = NULL;
310 }
311 }
312
313
314 static EventArea *
get_event_area_from_position(GthImageSelector * self,int x,int y)315 get_event_area_from_position (GthImageSelector *self,
316 int x,
317 int y)
318 {
319 GList *scan;
320
321 for (scan = self->priv->event_list; scan; scan = scan->next) {
322 EventArea *event_area = scan->data;
323 cairo_rectangle_int_t widget_area;
324
325 widget_area = event_area->area;
326 widget_area.x += MAX (self->priv->viewer->image_area.x, gth_image_viewer_get_frame_border (self->priv->viewer));
327 widget_area.y += MAX (self->priv->viewer->image_area.y, gth_image_viewer_get_frame_border (self->priv->viewer));
328
329 if (point_in_rectangle (x, y, widget_area))
330 return event_area;
331 }
332
333 return NULL;
334 }
335
336
337 static EventArea *
get_event_area_from_id(GthImageSelector * self,int event_id)338 get_event_area_from_id (GthImageSelector *self,
339 int event_id)
340 {
341 GList *scan;
342
343 for (scan = self->priv->event_list; scan; scan = scan->next) {
344 EventArea *event_area = scan->data;
345 if (event_area->id == event_id)
346 return event_area;
347 }
348
349 return NULL;
350 }
351
352
353 typedef enum {
354 SIDE_NONE = 0,
355 SIDE_TOP = 1 << 0,
356 SIDE_RIGHT = 1 << 1,
357 SIDE_BOTTOM = 1 << 2,
358 SIDE_LEFT = 1 << 3
359 } Sides;
360
361
362 static void
_cairo_rectangle_partial(cairo_t * cr,double x,double y,double w,double h,Sides sides)363 _cairo_rectangle_partial (cairo_t *cr,
364 double x,
365 double y,
366 double w,
367 double h,
368 Sides sides)
369 {
370 if (sides & SIDE_TOP) {
371 cairo_move_to (cr, x, y);
372 cairo_rel_line_to (cr, w, 0);
373 }
374
375 if (sides & SIDE_RIGHT) {
376 cairo_move_to (cr, x + w, y);
377 cairo_rel_line_to (cr, 0, h);
378 }
379
380 if (sides & SIDE_BOTTOM) {
381 cairo_move_to (cr, x, y + h);
382 cairo_rel_line_to (cr, w, 0);
383 }
384
385 if (sides & SIDE_LEFT) {
386 cairo_move_to (cr, x, y);
387 cairo_rel_line_to (cr, 0, h);
388 }
389 }
390
391
392 static void
event_area_paint(GthImageSelector * self,EventArea * event_area,cairo_t * cr)393 event_area_paint (GthImageSelector *self,
394 EventArea *event_area,
395 cairo_t *cr)
396 {
397 Sides sides = SIDE_NONE;
398
399 switch (event_area->id) {
400 case C_SELECTION_AREA:
401 break;
402 case C_TOP_AREA:
403 sides = SIDE_RIGHT | SIDE_BOTTOM | SIDE_LEFT;
404 break;
405 case C_BOTTOM_AREA:
406 sides = SIDE_TOP | SIDE_RIGHT | SIDE_LEFT;
407 break;
408 case C_LEFT_AREA:
409 sides = SIDE_TOP | SIDE_RIGHT | SIDE_BOTTOM;
410 break;
411 case C_RIGHT_AREA:
412 sides = SIDE_TOP | SIDE_BOTTOM | SIDE_LEFT;
413 break;
414 case C_TOP_LEFT_AREA:
415 sides = SIDE_RIGHT | SIDE_BOTTOM;
416 break;
417 case C_TOP_RIGHT_AREA:
418 sides = SIDE_BOTTOM | SIDE_LEFT;
419 break;
420 case C_BOTTOM_LEFT_AREA:
421 sides = SIDE_TOP | SIDE_RIGHT;
422 break;
423 case C_BOTTOM_RIGHT_AREA:
424 sides = SIDE_TOP | SIDE_LEFT;
425 break;
426 }
427
428 _cairo_rectangle_partial (cr,
429 self->priv->viewer->image_area.x + event_area->area.x - self->priv->viewer->visible_area.x + 0.5,
430 self->priv->viewer->image_area.y + event_area->area.y - self->priv->viewer->visible_area.y + 0.5,
431 event_area->area.width - 1.0,
432 event_area->area.height - 1.0,
433 sides);
434 }
435
436
437 /**/
438
439
440 static void
update_event_areas(GthImageSelector * self)441 update_event_areas (GthImageSelector *self)
442 {
443 EventArea *event_area;
444 int x, y, width, height;
445 int border;
446 int border2;
447
448 if (! gtk_widget_get_realized (GTK_WIDGET (self->priv->viewer)))
449 return;
450
451 border = DEFAULT_BORDER;
452 if (self->priv->selection_area.width < DEFAULT_BORDER * 3)
453 border = self->priv->selection_area.width / 3;
454 if (self->priv->selection_area.height < DEFAULT_BORDER * 3)
455 border = self->priv->selection_area.height / 3;
456 if (border < MIN_BORDER)
457 border = MIN_BORDER;
458
459 border2 = border * 2;
460
461 x = self->priv->selection_area.x - 1;
462 y = self->priv->selection_area.y - 1;
463 width = self->priv->selection_area.width + 1;
464 height = self->priv->selection_area.height + 1;
465
466 event_area = get_event_area_from_id (self, C_SELECTION_AREA);
467 event_area->area.x = x;
468 event_area->area.y = y;
469 event_area->area.width = width;
470 event_area->area.height = height;
471
472 event_area = get_event_area_from_id (self, C_TOP_AREA);
473 event_area->area.x = x + border;
474 event_area->area.y = y;
475 event_area->area.width = width - border2;
476 event_area->area.height = border;
477
478 event_area = get_event_area_from_id (self, C_BOTTOM_AREA);
479 event_area->area.x = x + border;
480 event_area->area.y = y + height - border;
481 event_area->area.width = width - border2;
482 event_area->area.height = border;
483
484 event_area = get_event_area_from_id (self, C_LEFT_AREA);
485 event_area->area.x = x;
486 event_area->area.y = y + border;
487 event_area->area.width = border;
488 event_area->area.height = height - border2;
489
490 event_area = get_event_area_from_id (self, C_RIGHT_AREA);
491 event_area->area.x = x + width - border;
492 event_area->area.y = y + border;
493 event_area->area.width = border;
494 event_area->area.height = height - border2;
495
496 event_area = get_event_area_from_id (self, C_TOP_LEFT_AREA);
497 event_area->area.x = x;
498 event_area->area.y = y;
499 event_area->area.width = border;
500 event_area->area.height = border;
501
502 event_area = get_event_area_from_id (self, C_TOP_RIGHT_AREA);
503 event_area->area.x = x + width - border;
504 event_area->area.y = y;
505 event_area->area.width = border;
506 event_area->area.height = border;
507
508 event_area = get_event_area_from_id (self, C_BOTTOM_LEFT_AREA);
509 event_area->area.x = x;
510 event_area->area.y = y + height - border;
511 event_area->area.width = border;
512 event_area->area.height = border;
513
514 event_area = get_event_area_from_id (self, C_BOTTOM_RIGHT_AREA);
515 event_area->area.x = x + width - border;
516 event_area->area.y = y + height - border;
517 event_area->area.width = border;
518 event_area->area.height = border;
519 }
520
521
522 static void
queue_draw(GthImageSelector * self,cairo_rectangle_int_t area)523 queue_draw (GthImageSelector *self,
524 cairo_rectangle_int_t area)
525 {
526 if (! gtk_widget_get_realized (GTK_WIDGET (self->priv->viewer)))
527 return;
528
529 gtk_widget_queue_draw_area (GTK_WIDGET (self->priv->viewer),
530 self->priv->viewer->image_area.x + area.x - self->priv->viewer->visible_area.x,
531 self->priv->viewer->image_area.y + area.y - self->priv->viewer->visible_area.y,
532 area.width,
533 area.height);
534 }
535
536
537 static void
selection_changed(GthImageSelector * self)538 selection_changed (GthImageSelector *self)
539 {
540 update_event_areas (self);
541 g_signal_emit (G_OBJECT (self), signals[SELECTION_CHANGED], 0);
542 }
543
544
545 static void
set_selection_area(GthImageSelector * self,cairo_rectangle_int_t new_selection,gboolean force_update)546 set_selection_area (GthImageSelector *self,
547 cairo_rectangle_int_t new_selection,
548 gboolean force_update)
549 {
550 cairo_rectangle_int_t old_selection_area;
551 cairo_rectangle_int_t dirty_region;
552
553 if (! force_update && rectangle_equal (self->priv->selection_area, new_selection))
554 return;
555
556 old_selection_area = self->priv->selection_area;
557 self->priv->selection_area = new_selection;
558 gdk_rectangle_union (&old_selection_area, &self->priv->selection_area, &dirty_region);
559 queue_draw (self, dirty_region);
560
561 selection_changed (self);
562 }
563
564
565 static void
set_selection(GthImageSelector * self,cairo_rectangle_int_t new_selection,gboolean force_update)566 set_selection (GthImageSelector *self,
567 cairo_rectangle_int_t new_selection,
568 gboolean force_update)
569 {
570 cairo_rectangle_int_t new_area;
571
572 if (! force_update && rectangle_equal (self->priv->selection, new_selection))
573 return;
574
575 self->priv->selection = new_selection;
576 convert_to_selection_area (self, new_selection, &new_area);
577 set_selection_area (self, new_area, force_update);
578 }
579
580
581 static void
init_selection(GthImageSelector * self)582 init_selection (GthImageSelector *self)
583 {
584 cairo_rectangle_int_t area;
585
586 /*
587 if (! self->priv->use_ratio) {
588 area.width = IROUND (self->priv->surface_area.width * 0.5);
589 area.height = IROUND (self->priv->surface_area.height * 0.5);
590 }
591 else {
592 if (self->priv->ratio > 1.0) {
593 area.width = IROUND (self->priv->surface_area.width * 0.5);
594 area.height = IROUND (area.width / self->priv->ratio);
595 }
596 else {
597 area.height = IROUND (self->priv->surface_area.height * 0.5);
598 area.width = IROUND (area.height * self->priv->ratio);
599 }
600 }
601 area.x = IROUND ((self->priv->surface_area.width - area.width) / 2.0);
602 area.y = IROUND ((self->priv->surface_area.height - area.height) / 2.0);
603 */
604
605 area.x = 0;
606 area.y = 0;
607 area.height = 0;
608 area.width = 0;
609 set_selection (self, area, FALSE);
610 }
611
612
613 static void
gth_image_selector_set_viewer(GthImageViewerTool * base,GthImageViewer * image_viewer)614 gth_image_selector_set_viewer (GthImageViewerTool *base,
615 GthImageViewer *image_viewer)
616 {
617 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
618
619 self->priv->viewer = image_viewer;
620 gth_image_viewer_show_frame (self->priv->viewer, 25);
621 }
622
623
624 static void
gth_image_selector_unset_viewer(GthImageViewerTool * base,GthImageViewer * image_viewer)625 gth_image_selector_unset_viewer (GthImageViewerTool *base,
626 GthImageViewer *image_viewer)
627 {
628 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
629
630 if (self->priv->viewer != NULL)
631 gth_image_viewer_hide_frame (self->priv->viewer);
632 self->priv->viewer = NULL;
633 }
634
635
636 static void
gth_image_selector_realize(GthImageViewerTool * base)637 gth_image_selector_realize (GthImageViewerTool *base)
638 {
639 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
640
641 if (self->priv->type == GTH_SELECTOR_TYPE_REGION)
642 self->priv->default_cursor = _gdk_cursor_new_for_widget (GTK_WIDGET (self->priv->viewer), GDK_CROSSHAIR /*GDK_LEFT_PTR*/);
643 else if (self->priv->type == GTH_SELECTOR_TYPE_POINT)
644 self->priv->default_cursor = _gdk_cursor_new_for_widget (GTK_WIDGET (self->priv->viewer), GDK_CROSSHAIR);
645 gth_image_viewer_set_cursor (self->priv->viewer, self->priv->default_cursor);
646
647 add_event_area (self, C_SELECTION_AREA, GDK_FLEUR);
648 add_event_area (self, C_TOP_AREA, GDK_TOP_SIDE);
649 add_event_area (self, C_BOTTOM_AREA, GDK_BOTTOM_SIDE);
650 add_event_area (self, C_LEFT_AREA, GDK_LEFT_SIDE);
651 add_event_area (self, C_RIGHT_AREA, GDK_RIGHT_SIDE);
652 add_event_area (self, C_TOP_LEFT_AREA, GDK_TOP_LEFT_CORNER);
653 add_event_area (self, C_TOP_RIGHT_AREA, GDK_TOP_RIGHT_CORNER);
654 add_event_area (self, C_BOTTOM_LEFT_AREA, GDK_BOTTOM_LEFT_CORNER);
655 add_event_area (self, C_BOTTOM_RIGHT_AREA, GDK_BOTTOM_RIGHT_CORNER);
656
657 init_selection (self);
658 update_event_areas (self);
659 }
660
661
662 static void
gth_image_selector_unrealize(GthImageViewerTool * base)663 gth_image_selector_unrealize (GthImageViewerTool *base)
664 {
665 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
666
667 if (self->priv->default_cursor != NULL) {
668 g_object_unref (self->priv->default_cursor);
669 self->priv->default_cursor = NULL;
670 }
671
672 free_event_area_list (self);
673 }
674
675
676 static void
gth_image_selector_size_allocate(GthImageViewerTool * base,GtkAllocation * allocation)677 gth_image_selector_size_allocate (GthImageViewerTool *base,
678 GtkAllocation *allocation)
679 {
680 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
681
682 if (self->priv->surface != NULL)
683 selection_changed (self);
684 }
685
686
687 static void
gth_image_selector_map(GthImageViewerTool * base)688 gth_image_selector_map (GthImageViewerTool *base)
689 {
690 /* void */
691 }
692
693
694 static void
gth_image_selector_unmap(GthImageViewerTool * base)695 gth_image_selector_unmap (GthImageViewerTool *base)
696 {
697 /* void */
698 }
699
700
701 static void
paint_background(GthImageSelector * self,cairo_t * cr)702 paint_background (GthImageSelector *self,
703 cairo_t *cr)
704 {
705 gth_image_viewer_paint_region (self->priv->viewer,
706 cr,
707 self->priv->surface,
708 self->priv->viewer->image_area.x,
709 self->priv->viewer->image_area.y,
710 &self->priv->viewer->visible_area,
711 gth_image_viewer_get_zoom_quality_filter (self->priv->viewer));
712
713 /* make the background darker */
714
715 cairo_save (cr);
716 cairo_rectangle (cr,
717 self->priv->viewer->image_area.x - self->priv->viewer->visible_area.x,
718 self->priv->viewer->image_area.y - self->priv->viewer->visible_area.y,
719 self->priv->viewer->image_area.width,
720 self->priv->viewer->image_area.height);
721 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
722 cairo_fill (cr);
723 cairo_restore (cr);
724 }
725
726
727 static void
paint_selection(GthImageSelector * self,cairo_t * cr)728 paint_selection (GthImageSelector *self,
729 cairo_t *cr)
730 {
731 cairo_rectangle_int_t selection_area;
732
733 if (! self->priv->viewer->dragging) {
734 int frame_border = gth_image_viewer_get_frame_border (self->priv->viewer);
735
736 selection_area = self->priv->selection_area;
737 selection_area.x += frame_border;
738 selection_area.y += frame_border;
739 gth_image_viewer_paint_region (self->priv->viewer,
740 cr,
741 self->priv->surface,
742 selection_area.x + self->priv->viewer->image_area.x - self->priv->viewer->visible_area.x,
743 selection_area.y + self->priv->viewer->image_area.y - self->priv->viewer->visible_area.y,
744 &selection_area,
745 gth_image_viewer_get_zoom_quality_filter (self->priv->viewer));
746 }
747
748 cairo_save (cr);
749
750 selection_area = self->priv->selection_area;
751 selection_area.x += self->priv->viewer->image_area.x - self->priv->viewer->visible_area.x;
752 selection_area.y += self->priv->viewer->image_area.y - self->priv->viewer->visible_area.y;
753 cairo_rectangle (cr,
754 selection_area.x + 0.5,
755 selection_area.y + 0.5,
756 selection_area.width,
757 selection_area.height);
758 cairo_clip (cr);
759
760 _cairo_paint_grid (cr, &selection_area, self->priv->grid_type);
761
762 if ((self->priv->current_area != NULL) && (self->priv->current_area->id != C_SELECTION_AREA)) {
763 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 9, 2)
764 cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE);
765 #endif
766 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
767 event_area_paint (self, self->priv->current_area, cr);
768 }
769 cairo_stroke (cr);
770
771 cairo_restore (cr);
772 }
773
774
775 static void
paint_image(GthImageSelector * self,cairo_t * cr)776 paint_image (GthImageSelector *self,
777 cairo_t *cr)
778 {
779 gth_image_viewer_paint_region (self->priv->viewer,
780 cr,
781 self->priv->surface,
782 self->priv->viewer->image_area.x,
783 self->priv->viewer->image_area.y,
784 &self->priv->viewer->visible_area,
785 gth_image_viewer_get_zoom_quality_filter (self->priv->viewer));
786 }
787
788
789 static void
gth_image_selector_draw(GthImageViewerTool * base,cairo_t * cr)790 gth_image_selector_draw (GthImageViewerTool *base,
791 cairo_t *cr)
792 {
793 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
794
795 gth_image_viewer_paint_background (self->priv->viewer, cr);
796
797 if (self->priv->surface == NULL)
798 return;
799
800 gth_image_viewer_paint_frame (self->priv->viewer, cr);
801
802 if (self->priv->mask_visible) {
803 if (self->priv->viewer->dragging)
804 paint_image (self, cr);
805 else
806 paint_background (self, cr);
807 paint_selection (self, cr);
808 }
809 else
810 paint_image (self, cr);
811 }
812
813
814 static int
selector_to_real(GthImageSelector * self,int value)815 selector_to_real (GthImageSelector *self,
816 int value)
817 {
818 return IROUND ((double) value / gth_image_viewer_get_zoom (self->priv->viewer));
819 }
820
821
822 static void
convert_to_real_selection(GthImageSelector * self,cairo_rectangle_int_t selection_area,cairo_rectangle_int_t * real_area)823 convert_to_real_selection (GthImageSelector *self,
824 cairo_rectangle_int_t selection_area,
825 cairo_rectangle_int_t *real_area)
826 {
827 real_area->x = selector_to_real (self, selection_area.x);
828 real_area->y = selector_to_real (self, selection_area.y);
829 real_area->width = selector_to_real (self, selection_area.width);
830 real_area->height = selector_to_real (self, selection_area.height);
831 }
832
833
834 static void
set_active_area(GthImageSelector * self,EventArea * event_area)835 set_active_area (GthImageSelector *self,
836 EventArea *event_area)
837 {
838 if (self->priv->active != (event_area != NULL))
839 self->priv->active = ! self->priv->active;
840
841 if (self->priv->current_area != event_area)
842 self->priv->current_area = event_area;
843
844 if (self->priv->current_area == NULL)
845 gth_image_viewer_set_cursor (self->priv->viewer, self->priv->default_cursor);
846 else
847 gth_image_viewer_set_cursor (self->priv->viewer, self->priv->current_area->cursor);
848
849 queue_draw (self, self->priv->selection_area);
850 }
851
852
853 static void
update_cursor(GthImageSelector * self,int x,int y)854 update_cursor (GthImageSelector *self,
855 int x,
856 int y)
857 {
858 if (! self->priv->mask_visible)
859 return;
860 set_active_area (self, get_event_area_from_position (self, x, y));
861 }
862
863
864 static gboolean
gth_image_selector_button_release(GthImageViewerTool * base,GdkEventButton * event)865 gth_image_selector_button_release (GthImageViewerTool *base,
866 GdkEventButton *event)
867 {
868 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
869
870 if (self->priv->timer_id != 0) {
871 g_source_remove (self->priv->timer_id);
872 self->priv->timer_id = 0;
873 }
874
875 update_cursor (self,
876 event->x + self->priv->viewer->visible_area.x,
877 event->y + self->priv->viewer->visible_area.y);
878 gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
879
880 return FALSE;
881 }
882
883
884 static void
grow_upward(cairo_rectangle_int_t * bound,cairo_rectangle_int_t * r,int dy,gboolean check)885 grow_upward (cairo_rectangle_int_t *bound,
886 cairo_rectangle_int_t *r,
887 int dy,
888 gboolean check)
889 {
890 if (check && (r->y + dy < 0))
891 dy = -r->y;
892 r->y += dy;
893 r->height -= dy;
894 }
895
896
897 static void
grow_downward(cairo_rectangle_int_t * bound,cairo_rectangle_int_t * r,int dy,gboolean check)898 grow_downward (cairo_rectangle_int_t *bound,
899 cairo_rectangle_int_t *r,
900 int dy,
901 gboolean check)
902 {
903 if (check && (r->y + r->height + dy > bound->height))
904 dy = bound->height - (r->y + r->height);
905 r->height += dy;
906 }
907
908
909 static void
grow_leftward(cairo_rectangle_int_t * bound,cairo_rectangle_int_t * r,int dx,gboolean check)910 grow_leftward (cairo_rectangle_int_t *bound,
911 cairo_rectangle_int_t *r,
912 int dx,
913 gboolean check)
914 {
915 if (check && (r->x + dx < 0))
916 dx = -r->x;
917 r->x += dx;
918 r->width -= dx;
919 }
920
921
922 static void
grow_rightward(cairo_rectangle_int_t * bound,cairo_rectangle_int_t * r,int dx,gboolean check)923 grow_rightward (cairo_rectangle_int_t *bound,
924 cairo_rectangle_int_t *r,
925 int dx,
926 gboolean check)
927 {
928 if (check && (r->x + r->width + dx > bound->width))
929 dx = bound->width - (r->x + r->width);
930 r->width += dx;
931 }
932
933
934 static int
get_semiplane_no(int x1,int y1,int x2,int y2,int px,int py)935 get_semiplane_no (int x1,
936 int y1,
937 int x2,
938 int y2,
939 int px,
940 int py)
941 {
942 double a, b;
943
944 a = atan ((double) (y1 - y2) / (x2 - x1));
945 b = atan ((double) (y1 - py) / (px - x1));
946
947 return (a <= b) && (b <= a + G_PI);
948 }
949
950
951 static int
bind_dimension(int dimension,int factor)952 bind_dimension (int dimension,
953 int factor)
954 {
955 int d;
956 int d_next;
957
958 d = (dimension / factor) * factor;
959 d_next = d + factor;
960 if (dimension - d <= d_next - dimension)
961 dimension = d;
962 else
963 dimension = d_next;
964
965 return dimension;
966 }
967
968
969 static gboolean
check_and_set_new_selection(GthImageSelector * self,cairo_rectangle_int_t new_selection)970 check_and_set_new_selection (GthImageSelector *self,
971 cairo_rectangle_int_t new_selection)
972 {
973 new_selection.width = MAX (0, new_selection.width);
974 new_selection.height = MAX (0, new_selection.height);
975
976 if (self->priv->bind_dimensions && (self->priv->bind_factor > 1)) {
977 new_selection.width = bind_dimension (new_selection.width, self->priv->bind_factor);
978 new_selection.height = bind_dimension (new_selection.height, self->priv->bind_factor);
979 }
980
981 if (((self->priv->current_area == NULL)
982 || (self->priv->current_area->id != C_SELECTION_AREA))
983 && self->priv->use_ratio)
984 {
985 if (! rectangle_in_rectangle (new_selection, self->priv->surface_area))
986 return FALSE;
987
988 set_selection (self, new_selection, FALSE);
989 return TRUE;
990 }
991
992 /* self->priv->current_area->id == C_SELECTION_AREA */
993
994 if (new_selection.x < 0)
995 new_selection.x = 0;
996 if (new_selection.y < 0)
997 new_selection.y = 0;
998 if (new_selection.width > self->priv->surface_area.width)
999 new_selection.width = self->priv->surface_area.width;
1000 if (new_selection.height > self->priv->surface_area.height)
1001 new_selection.height = self->priv->surface_area.height;
1002
1003 if (new_selection.x + new_selection.width > self->priv->surface_area.width)
1004 new_selection.x = self->priv->surface_area.width - new_selection.width;
1005 if (new_selection.y + new_selection.height > self->priv->surface_area.height)
1006 new_selection.y = self->priv->surface_area.height - new_selection.height;
1007
1008 set_selection (self, new_selection, FALSE);
1009
1010 return TRUE;
1011 }
1012
1013
1014 static gboolean
gth_image_selector_button_press(GthImageViewerTool * base,GdkEventButton * event)1015 gth_image_selector_button_press (GthImageViewerTool *base,
1016 GdkEventButton *event)
1017 {
1018 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
1019 gboolean retval = FALSE;
1020 int x, y;
1021
1022 if (event->button != 1)
1023 return FALSE;
1024
1025 if (! point_in_rectangle (event->x, event->y, self->priv->viewer->image_area))
1026 return FALSE;
1027
1028 x = event->x + self->priv->viewer->visible_area.x;
1029 y = event->y + self->priv->viewer->visible_area.y;
1030
1031 if (self->priv->current_area == NULL) {
1032 cairo_rectangle_int_t new_selection;
1033
1034 new_selection.x = selector_to_real (self, x - self->priv->viewer->image_area.x);
1035 new_selection.y = selector_to_real (self, y - self->priv->viewer->image_area.y);
1036 new_selection.width = selector_to_real (self, 1);
1037 new_selection.height = selector_to_real (self, 1);
1038
1039 if (self->priv->type == GTH_SELECTOR_TYPE_REGION) {
1040 check_and_set_new_selection (self, new_selection);
1041 set_active_area (self, get_event_area_from_id (self, C_BOTTOM_RIGHT_AREA));
1042 }
1043 else if (self->priv->type == GTH_SELECTOR_TYPE_POINT) {
1044 g_signal_emit (G_OBJECT (self),
1045 signals[SELECTED],
1046 0,
1047 new_selection.x,
1048 new_selection.y);
1049 return TRUE;
1050 }
1051 }
1052
1053 if (self->priv->current_area != NULL) {
1054 self->priv->viewer->pressed = TRUE;
1055 self->priv->viewer->dragging = TRUE;
1056 self->priv->drag_start_selection_area = self->priv->selection_area;
1057 gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
1058 retval = TRUE;
1059 }
1060
1061 return retval;
1062 }
1063
1064
1065 static void
update_mouse_selection(GthImageSelector * self)1066 update_mouse_selection (GthImageSelector *self)
1067 {
1068 gboolean check = ! self->priv->use_ratio;
1069 int dx, dy;
1070 cairo_rectangle_int_t new_selection, tmp;
1071 int semiplane;
1072 GthEventAreaType area_type = self->priv->current_area->id;
1073 EventArea *event_area;
1074
1075 dx = selector_to_real (self, self->priv->viewer->drag_x - self->priv->viewer->drag_x_start);
1076 dy = selector_to_real (self, self->priv->viewer->drag_y - self->priv->viewer->drag_y_start);
1077
1078 convert_to_real_selection (self,
1079 self->priv->drag_start_selection_area,
1080 &new_selection);
1081
1082 if (((area_type == C_LEFT_AREA)
1083 || (area_type == C_TOP_LEFT_AREA)
1084 || (area_type == C_BOTTOM_LEFT_AREA))
1085 && (dx > new_selection.width))
1086 {
1087 new_selection.x += new_selection.width;
1088 dx = - (2 * new_selection.width) + dx;
1089 area_type = get_opposite_event_area_on_x (area_type);
1090 }
1091 else if (((area_type == C_RIGHT_AREA)
1092 || (area_type == C_TOP_RIGHT_AREA)
1093 || (area_type == C_BOTTOM_RIGHT_AREA))
1094 && (-dx > new_selection.width))
1095 {
1096 new_selection.x -= new_selection.width;
1097 dx = (2 * new_selection.width) + dx;
1098 area_type = get_opposite_event_area_on_x (area_type);
1099 }
1100
1101 if (((area_type == C_TOP_AREA)
1102 || (area_type == C_TOP_LEFT_AREA)
1103 || (area_type == C_TOP_RIGHT_AREA))
1104 && (dy > new_selection.height))
1105 {
1106 new_selection.y += new_selection.height;
1107 dy = - (2 * new_selection.height) + dy;
1108 area_type = get_opposite_event_area_on_y (area_type);
1109 }
1110 else if (((area_type == C_BOTTOM_AREA)
1111 || (area_type == C_BOTTOM_LEFT_AREA)
1112 || (area_type == C_BOTTOM_RIGHT_AREA))
1113 && (-dy > new_selection.height))
1114 {
1115 new_selection.y -= new_selection.height;
1116 dy = (2 * new_selection.height) + dy;
1117 area_type = get_opposite_event_area_on_y (area_type);
1118 }
1119
1120 event_area = get_event_area_from_id (self, area_type);
1121 if (event_area != NULL)
1122 gth_image_viewer_set_cursor (self->priv->viewer, event_area->cursor);
1123
1124 switch (area_type) {
1125 case C_SELECTION_AREA:
1126 new_selection.x += dx;
1127 new_selection.y += dy;
1128 break;
1129
1130 case C_TOP_AREA:
1131 grow_upward (&self->priv->surface_area, &new_selection, dy, check);
1132 if (self->priv->use_ratio)
1133 grow_rightward (&self->priv->surface_area,
1134 &new_selection,
1135 IROUND (-dy * self->priv->ratio),
1136 check);
1137 break;
1138
1139 case C_BOTTOM_AREA:
1140 grow_downward (&self->priv->surface_area, &new_selection, dy, check);
1141 if (self->priv->use_ratio)
1142 grow_rightward (&self->priv->surface_area,
1143 &new_selection,
1144 IROUND (dy * self->priv->ratio),
1145 check);
1146 break;
1147
1148 case C_LEFT_AREA:
1149 grow_leftward (&self->priv->surface_area, &new_selection, dx, check);
1150 if (self->priv->use_ratio)
1151 grow_downward (&self->priv->surface_area,
1152 &new_selection,
1153 IROUND (-dx / self->priv->ratio),
1154 check);
1155 break;
1156
1157 case C_RIGHT_AREA:
1158 grow_rightward (&self->priv->surface_area, &new_selection, dx, check);
1159 if (self->priv->use_ratio)
1160 grow_downward (&self->priv->surface_area,
1161 &new_selection,
1162 IROUND (dx / self->priv->ratio),
1163 check);
1164 break;
1165
1166 case C_TOP_LEFT_AREA:
1167 if (self->priv->use_ratio) {
1168 tmp = self->priv->selection_area;
1169 semiplane = get_semiplane_no (tmp.x + tmp.width,
1170 tmp.y + tmp.height,
1171 tmp.x,
1172 tmp.y,
1173 self->priv->viewer->drag_x - self->priv->viewer->image_area.x,
1174 self->priv->viewer->drag_y - self->priv->viewer->image_area.y);
1175 if (semiplane == 1)
1176 dy = IROUND (dx / self->priv->ratio);
1177 else
1178 dx = IROUND (dy * self->priv->ratio);
1179 }
1180 grow_upward (&self->priv->surface_area, &new_selection, dy, check);
1181 grow_leftward (&self->priv->surface_area, &new_selection, dx, check);
1182 break;
1183
1184 case C_TOP_RIGHT_AREA:
1185 if (self->priv->use_ratio) {
1186 tmp = self->priv->selection_area;
1187 semiplane = get_semiplane_no (tmp.x,
1188 tmp.y + tmp.height,
1189 tmp.x + tmp.width,
1190 tmp.y,
1191 self->priv->viewer->drag_x - self->priv->viewer->image_area.x,
1192 self->priv->viewer->drag_y - self->priv->viewer->image_area.y);
1193 if (semiplane == 1)
1194 dx = IROUND (-dy * self->priv->ratio);
1195 else
1196 dy = IROUND (-dx / self->priv->ratio);
1197 }
1198 grow_upward (&self->priv->surface_area, &new_selection, dy, check);
1199 grow_rightward (&self->priv->surface_area, &new_selection, dx, check);
1200 break;
1201
1202 case C_BOTTOM_LEFT_AREA:
1203 if (self->priv->use_ratio) {
1204 tmp = self->priv->selection_area;
1205 semiplane = get_semiplane_no (tmp.x + tmp.width,
1206 tmp.y,
1207 tmp.x,
1208 tmp.y + tmp.height,
1209 self->priv->viewer->drag_x - self->priv->viewer->image_area.x,
1210 self->priv->viewer->drag_y - self->priv->viewer->image_area.y);
1211 if (semiplane == 1)
1212 dx = IROUND (-dy * self->priv->ratio);
1213 else
1214 dy = IROUND (-dx / self->priv->ratio);
1215 }
1216 grow_downward (&self->priv->surface_area, &new_selection, dy, check);
1217 grow_leftward (&self->priv->surface_area, &new_selection, dx, check);
1218 break;
1219
1220 case C_BOTTOM_RIGHT_AREA:
1221 if (self->priv->use_ratio) {
1222 tmp = self->priv->selection_area;
1223 semiplane = get_semiplane_no (tmp.x,
1224 tmp.y,
1225 tmp.x + tmp.width,
1226 tmp.y + tmp.height,
1227 self->priv->viewer->drag_x - self->priv->viewer->image_area.x,
1228 self->priv->viewer->drag_y - self->priv->viewer->image_area.y);
1229
1230 if (semiplane == 1)
1231 dy = IROUND (dx / self->priv->ratio);
1232 else
1233 dx = IROUND (dy * self->priv->ratio);
1234 }
1235 grow_downward (&self->priv->surface_area, &new_selection, dy, check);
1236 grow_rightward (&self->priv->surface_area, &new_selection, dx, check);
1237 break;
1238
1239 default:
1240 break;
1241 }
1242
1243 /* if the user is changing selection size and the selection has a fixed
1244 * ratio and it goes out of the image bounds, resize the selection in
1245 * order to stay inside the image bounds. */
1246
1247 if ((area_type != C_SELECTION_AREA)
1248 && self->priv->use_ratio
1249 && ! rectangle_in_rectangle (new_selection, self->priv->surface_area))
1250 {
1251 switch (area_type) {
1252 case C_TOP_AREA:
1253 if (new_selection.y < self->priv->surface_area.y) {
1254 dy = new_selection.y + new_selection.height;
1255 dx = IROUND (dy * self->priv->ratio);
1256 new_selection.y = new_selection.y + new_selection.height - dy;
1257 new_selection.width = dx;
1258 new_selection.height = dy;
1259 }
1260 if (new_selection.x + new_selection.width > self->priv->surface_area.width) {
1261 dx = self->priv->surface_area.width - new_selection.x;
1262 dy = IROUND (dx / self->priv->ratio);
1263 new_selection.y = new_selection.y + new_selection.height - dy;
1264 new_selection.width = dx;
1265 new_selection.height = dy;
1266 }
1267 break;
1268
1269 case C_RIGHT_AREA:
1270 if (new_selection.x + new_selection.width > self->priv->surface_area.width) {
1271 dx = self->priv->surface_area.width - new_selection.x;
1272 dy = IROUND (dx / self->priv->ratio);
1273 new_selection.width = dx;
1274 new_selection.height = dy;
1275 }
1276 if (new_selection.y + new_selection.height > self->priv->surface_area.height) {
1277 dy = self->priv->surface_area.height - new_selection.y;
1278 dx = IROUND (dy * self->priv->ratio);
1279 new_selection.width = dx;
1280 new_selection.height = dy;
1281 }
1282 break;
1283
1284 case C_TOP_RIGHT_AREA:
1285 if (new_selection.x + new_selection.width > self->priv->surface_area.width) {
1286 dx = self->priv->surface_area.width - new_selection.x;
1287 dy = IROUND (dx / self->priv->ratio);
1288 new_selection.y = new_selection.y + new_selection.height - dy;
1289 new_selection.width = dx;
1290 new_selection.height = dy;
1291 }
1292 if (new_selection.y < self->priv->surface_area.y) {
1293 dy = new_selection.y + new_selection.height;
1294 dx = IROUND (dy * self->priv->ratio);
1295 new_selection.y = new_selection.y + new_selection.height - dy;
1296 new_selection.width = dx;
1297 new_selection.height = dy;
1298 }
1299 if (new_selection.x + new_selection.width > self->priv->surface_area.width) {
1300 dx = self->priv->surface_area.width - new_selection.x;
1301 dy = IROUND (dx / self->priv->ratio);
1302 new_selection.y = new_selection.y + new_selection.height - dy;
1303 new_selection.width = dx;
1304 new_selection.height = dy;
1305 }
1306 break;
1307
1308 case C_TOP_LEFT_AREA:
1309 if (new_selection.x < self->priv->surface_area.y) {
1310 dx = new_selection.x + new_selection.width;
1311 dy = IROUND (dx / self->priv->ratio);
1312 new_selection.x = new_selection.x + new_selection.width - dx;
1313 new_selection.y = new_selection.y + new_selection.height - dy;
1314 new_selection.width = dx;
1315 new_selection.height = dy;
1316 }
1317 if (new_selection.y < self->priv->surface_area.y) {
1318 dy = new_selection.y + new_selection.height;
1319 dx = IROUND (dy * self->priv->ratio);
1320 new_selection.x = new_selection.x + new_selection.width - dx;
1321 new_selection.y = new_selection.y + new_selection.height - dy;
1322 new_selection.width = dx;
1323 new_selection.height = dy;
1324 }
1325 if (new_selection.x < self->priv->surface_area.y) {
1326 dx = new_selection.x + new_selection.width;
1327 dy = IROUND (dx / self->priv->ratio);
1328 new_selection.x = new_selection.x + new_selection.width - dx;
1329 new_selection.y = new_selection.y + new_selection.height - dy;
1330 new_selection.width = dx;
1331 new_selection.height = dy;
1332 }
1333 break;
1334
1335 case C_BOTTOM_RIGHT_AREA:
1336 if (new_selection.x + new_selection.width > self->priv->surface_area.width) {
1337 dx = self->priv->surface_area.width - new_selection.x;
1338 dy = IROUND (dx / self->priv->ratio);
1339 new_selection.width = dx;
1340 new_selection.height = dy;
1341 }
1342 if (new_selection.y + new_selection.height > self->priv->surface_area.height) {
1343 dy = self->priv->surface_area.height - new_selection.y;
1344 dx = IROUND (dy * self->priv->ratio);
1345 new_selection.width = dx;
1346 new_selection.height = dy;
1347 }
1348 if (new_selection.x + new_selection.width > self->priv->surface_area.width) {
1349 dx = self->priv->surface_area.width - new_selection.x;
1350 dy = IROUND (dx / self->priv->ratio);
1351 new_selection.width = dx;
1352 new_selection.height = dy;
1353 }
1354 break;
1355
1356 case C_BOTTOM_AREA:
1357 if (new_selection.y + new_selection.height > self->priv->surface_area.height) {
1358 dy = self->priv->surface_area.height - new_selection.y;
1359 dx = IROUND (dy * self->priv->ratio);
1360 new_selection.width = dx;
1361 new_selection.height = dy;
1362 }
1363 if (new_selection.x + new_selection.width > self->priv->surface_area.width) {
1364 dx = self->priv->surface_area.width - new_selection.x;
1365 dy = IROUND (dx / self->priv->ratio);
1366 new_selection.width = dx;
1367 new_selection.height = dy;
1368 }
1369 break;
1370
1371 case C_LEFT_AREA:
1372 if (new_selection.x < self->priv->surface_area.y) {
1373 dx = new_selection.x + new_selection.width;
1374 dy = IROUND (dx / self->priv->ratio);
1375 new_selection.x = new_selection.x + new_selection.width - dx;
1376 new_selection.width = dx;
1377 new_selection.height = dy;
1378 }
1379 if (new_selection.y + new_selection.height > self->priv->surface_area.height) {
1380 dy = self->priv->surface_area.height - new_selection.y;
1381 dx = IROUND (dy * self->priv->ratio);
1382 new_selection.x = new_selection.x + new_selection.width - dx;
1383 new_selection.width = dx;
1384 new_selection.height = dy;
1385 }
1386 break;
1387
1388 case C_BOTTOM_LEFT_AREA:
1389 if (new_selection.x < self->priv->surface_area.y) {
1390 dx = new_selection.x + new_selection.width;
1391 dy = IROUND (dx / self->priv->ratio);
1392 new_selection.x = new_selection.x + new_selection.width - dx;
1393 new_selection.width = dx;
1394 new_selection.height = dy;
1395 }
1396 if (new_selection.y + new_selection.height > self->priv->surface_area.height) {
1397 dy = self->priv->surface_area.height - new_selection.y;
1398 dx = IROUND (dy * self->priv->ratio);
1399 new_selection.x = new_selection.x + new_selection.width - dx;
1400 new_selection.width = dx;
1401 new_selection.height = dy;
1402 }
1403 if (new_selection.x < self->priv->surface_area.y) {
1404 dx = new_selection.x + new_selection.width;
1405 dy = IROUND (dx / self->priv->ratio);
1406 new_selection.x = new_selection.x + new_selection.width - dx;
1407 new_selection.width = dx;
1408 new_selection.height = dy;
1409 }
1410 break;
1411
1412 default:
1413 break;
1414 }
1415 }
1416
1417 check_and_set_new_selection (self, new_selection);
1418 }
1419
1420
1421 static gboolean
autoscroll_cb(gpointer data)1422 autoscroll_cb (gpointer data)
1423 {
1424 GthImageSelector *self = GTH_IMAGE_SELECTOR (data);
1425 double value;
1426 double max_value;
1427
1428 /* drag x */
1429
1430 value = gtk_adjustment_get_value (self->priv->viewer->hadj) + self->priv->x_value_diff;
1431 max_value = gtk_adjustment_get_upper (self->priv->viewer->hadj) - gtk_adjustment_get_page_size (self->priv->viewer->hadj);
1432 if (value > max_value)
1433 value = max_value;
1434 gtk_adjustment_set_value (self->priv->viewer->hadj, value);
1435 self->priv->viewer->drag_x = self->priv->viewer->drag_x + self->priv->x_value_diff;
1436
1437 /* drag y */
1438
1439 value = gtk_adjustment_get_value (self->priv->viewer->vadj) + self->priv->y_value_diff;
1440 max_value = gtk_adjustment_get_upper (self->priv->viewer->vadj) - gtk_adjustment_get_page_size (self->priv->viewer->vadj);
1441 if (value > max_value)
1442 value = max_value;
1443 gtk_adjustment_set_value (self->priv->viewer->vadj, value);
1444 self->priv->viewer->drag_y = self->priv->viewer->drag_y + self->priv->y_value_diff;
1445
1446 update_mouse_selection (self);
1447 gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
1448
1449 return TRUE;
1450 }
1451
1452
1453 static gboolean
gth_image_selector_motion_notify(GthImageViewerTool * base,GdkEventMotion * event)1454 gth_image_selector_motion_notify (GthImageViewerTool *base,
1455 GdkEventMotion *event)
1456 {
1457 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
1458 GtkWidget *widget;
1459 int x, y;
1460 GtkAllocation allocation;
1461 int absolute_x, absolute_y;
1462
1463 widget = GTK_WIDGET (self->priv->viewer);
1464 x = event->x + self->priv->viewer->visible_area.x;
1465 y = event->y + self->priv->viewer->visible_area.y;
1466
1467 if (self->priv->type == GTH_SELECTOR_TYPE_POINT) {
1468 x = selector_to_real (self, x - self->priv->viewer->image_area.x);
1469 y = selector_to_real (self, y - self->priv->viewer->image_area.y);
1470 if (point_in_rectangle (x, y, self->priv->surface_area))
1471 g_signal_emit (G_OBJECT (self), signals[MOTION_NOTIFY], 0, x, y);
1472 return TRUE;
1473 }
1474
1475 /* type == GTH_SELECTOR_TYPE_REGION */
1476
1477 if (! self->priv->viewer->dragging
1478 && self->priv->viewer->pressed
1479 && ((abs (self->priv->viewer->drag_x - self->priv->viewer->drag_x_prev) > DRAG_THRESHOLD)
1480 || (abs (self->priv->viewer->drag_y - self->priv->viewer->drag_y_prev) > DRAG_THRESHOLD))
1481 && (self->priv->current_area != NULL))
1482 {
1483 GdkGrabStatus status;
1484
1485 status = gdk_seat_grab (gdk_device_get_seat (gdk_event_get_device ((GdkEvent *) event)),
1486 gtk_widget_get_window (widget),
1487 GDK_SEAT_CAPABILITY_ALL_POINTING,
1488 TRUE,
1489 self->priv->current_area->cursor,
1490 (GdkEvent *) event,
1491 NULL,
1492 NULL);
1493 if (status == GDK_GRAB_SUCCESS)
1494 self->priv->viewer->dragging = TRUE;
1495
1496 return FALSE;
1497 }
1498
1499 if (! self->priv->viewer->dragging) {
1500 update_cursor (self, x, y);
1501 return FALSE;
1502 }
1503
1504 /* dragging == TRUE */
1505
1506 update_mouse_selection (self);
1507
1508 /* If we are out of bounds, schedule a timeout that will do
1509 * the scrolling */
1510
1511 gtk_widget_get_allocation (widget, &allocation);
1512 absolute_x = event->x;
1513 absolute_y = event->y;
1514 if ((absolute_y < 0) || (absolute_y > allocation.height)
1515 || (absolute_x < 0) || (absolute_x > allocation.width))
1516 {
1517
1518 /* Make the steppings be relative to the mouse
1519 * distance from the canvas.
1520 * Also notice the timeout is small to give a smoother
1521 * movement.
1522 */
1523 if (absolute_x < 0)
1524 self->priv->x_value_diff = absolute_x;
1525 else if (absolute_x > allocation.width)
1526 self->priv->x_value_diff = absolute_x - allocation.width;
1527 else
1528 self->priv->x_value_diff = 0.0;
1529 self->priv->x_value_diff /= 2;
1530
1531 if (absolute_y < 0)
1532 self->priv->y_value_diff = absolute_y;
1533 else if (absolute_y > allocation.height)
1534 self->priv->y_value_diff = absolute_y -allocation.height;
1535 else
1536 self->priv->y_value_diff = 0.0;
1537 self->priv->y_value_diff /= 2;
1538
1539 if (self->priv->timer_id == 0)
1540 self->priv->timer_id = gdk_threads_add_timeout (SCROLL_TIMEOUT,
1541 autoscroll_cb,
1542 self);
1543 }
1544 else if (self->priv->timer_id != 0) {
1545 g_source_remove (self->priv->timer_id);
1546 self->priv->timer_id = 0;
1547 }
1548
1549 return FALSE;
1550 }
1551
1552
1553 static gboolean
selection_is_valid(GthImageSelector * self)1554 selection_is_valid (GthImageSelector *self)
1555 {
1556 cairo_region_t *surface_region;
1557 gboolean valid;
1558
1559 surface_region = cairo_region_create_rectangle (&self->priv->surface_area);
1560 valid = cairo_region_contains_rectangle (surface_region, &self->priv->selection_area) == CAIRO_REGION_OVERLAP_IN;
1561 cairo_region_destroy (surface_region);
1562
1563 return valid;
1564 }
1565
1566
1567 static void
update_selection(GthImageSelector * self)1568 update_selection (GthImageSelector *self)
1569 {
1570 cairo_rectangle_int_t selection;
1571
1572 gth_image_selector_get_selection (self, &selection);
1573 set_selection (self, selection, TRUE);
1574 }
1575
1576
1577 static void
gth_image_selector_image_changed(GthImageViewerTool * base)1578 gth_image_selector_image_changed (GthImageViewerTool *base)
1579 {
1580 GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
1581
1582 cairo_surface_destroy (self->priv->surface);
1583 self->priv->surface = gth_image_viewer_get_current_image (self->priv->viewer);
1584
1585 if (self->priv->surface == NULL) {
1586 self->priv->surface_area.width = 0;
1587 self->priv->surface_area.height = 0;
1588 return;
1589 }
1590
1591 self->priv->surface = cairo_surface_reference (self->priv->surface);
1592 if (! _cairo_image_surface_get_original_size (self->priv->surface,
1593 &self->priv->surface_area.width,
1594 &self->priv->surface_area.height))
1595 {
1596 self->priv->surface_area.width = cairo_image_surface_get_width (self->priv->surface);
1597 self->priv->surface_area.height = cairo_image_surface_get_height (self->priv->surface);
1598 }
1599
1600 if (! selection_is_valid (self))
1601 init_selection (self);
1602 else
1603 update_selection (self);
1604 }
1605
1606
1607 static void
gth_image_selector_zoom_changed(GthImageViewerTool * base)1608 gth_image_selector_zoom_changed (GthImageViewerTool *base)
1609 {
1610 update_selection (GTH_IMAGE_SELECTOR (base));
1611 }
1612
1613
1614 static void
gth_image_selector_init(GthImageSelector * self)1615 gth_image_selector_init (GthImageSelector *self)
1616 {
1617 self->priv = gth_image_selector_get_instance_private (self);
1618
1619 self->priv->type = GTH_SELECTOR_TYPE_REGION;
1620 self->priv->ratio = 1.0;
1621 self->priv->mask_visible = TRUE;
1622 self->priv->grid_type = GTH_GRID_NONE;
1623 self->priv->bind_dimensions = FALSE;
1624 self->priv->bind_factor = 1;
1625 }
1626
1627
1628 static void
gth_image_selector_finalize(GObject * object)1629 gth_image_selector_finalize (GObject *object)
1630 {
1631 GthImageSelector *self;
1632
1633 g_return_if_fail (object != NULL);
1634 g_return_if_fail (GTH_IS_IMAGE_SELECTOR (object));
1635
1636 self = (GthImageSelector *) object;
1637 cairo_surface_destroy (self->priv->surface);
1638
1639 /* Chain up */
1640 G_OBJECT_CLASS (gth_image_selector_parent_class)->finalize (object);
1641 }
1642
1643
1644 static void
gth_image_selector_class_init(GthImageSelectorClass * class)1645 gth_image_selector_class_init (GthImageSelectorClass *class)
1646 {
1647 GObjectClass *gobject_class;
1648
1649 gobject_class = (GObjectClass*) class;
1650 gobject_class->finalize = gth_image_selector_finalize;
1651
1652 signals[SELECTION_CHANGED] = g_signal_new ("selection_changed",
1653 G_TYPE_FROM_CLASS (class),
1654 G_SIGNAL_RUN_LAST,
1655 G_STRUCT_OFFSET (GthImageSelectorClass, selection_changed),
1656 NULL, NULL,
1657 g_cclosure_marshal_VOID__VOID,
1658 G_TYPE_NONE,
1659 0);
1660 signals[MOTION_NOTIFY] = g_signal_new ("motion_notify",
1661 G_TYPE_FROM_CLASS (class),
1662 G_SIGNAL_RUN_LAST,
1663 G_STRUCT_OFFSET (GthImageSelectorClass, motion_notify),
1664 NULL, NULL,
1665 gth_marshal_VOID__INT_INT,
1666 G_TYPE_NONE,
1667 2,
1668 G_TYPE_INT,
1669 G_TYPE_INT);
1670 signals[SELECTED] = g_signal_new ("selected",
1671 G_TYPE_FROM_CLASS (class),
1672 G_SIGNAL_RUN_LAST,
1673 G_STRUCT_OFFSET (GthImageSelectorClass, selected),
1674 NULL, NULL,
1675 gth_marshal_VOID__INT_INT,
1676 G_TYPE_NONE,
1677 2,
1678 G_TYPE_INT,
1679 G_TYPE_INT);
1680 signals[MASK_VISIBILITY_CHANGED] = g_signal_new ("mask_visibility_changed",
1681 G_TYPE_FROM_CLASS (class),
1682 G_SIGNAL_RUN_LAST,
1683 G_STRUCT_OFFSET (GthImageSelectorClass, mask_visibility_changed),
1684 NULL, NULL,
1685 g_cclosure_marshal_VOID__VOID,
1686 G_TYPE_NONE,
1687 0);
1688 signals[GRID_VISIBILITY_CHANGED] = g_signal_new ("grid_visibility_changed",
1689 G_TYPE_FROM_CLASS (class),
1690 G_SIGNAL_RUN_LAST,
1691 G_STRUCT_OFFSET (GthImageSelectorClass, grid_visibility_changed),
1692 NULL, NULL,
1693 g_cclosure_marshal_VOID__VOID,
1694 G_TYPE_NONE,
1695 0);
1696 }
1697
1698
1699 static void
gth_image_selector_gth_image_tool_interface_init(GthImageViewerToolInterface * iface)1700 gth_image_selector_gth_image_tool_interface_init (GthImageViewerToolInterface *iface)
1701 {
1702 iface->set_viewer = gth_image_selector_set_viewer;
1703 iface->unset_viewer = gth_image_selector_unset_viewer;
1704 iface->realize = gth_image_selector_realize;
1705 iface->unrealize = gth_image_selector_unrealize;
1706 iface->size_allocate = gth_image_selector_size_allocate;
1707 iface->map = gth_image_selector_map;
1708 iface->unmap = gth_image_selector_unmap;
1709 iface->draw = gth_image_selector_draw;
1710 iface->button_press = gth_image_selector_button_press;
1711 iface->button_release = gth_image_selector_button_release;
1712 iface->motion_notify = gth_image_selector_motion_notify;
1713 iface->image_changed = gth_image_selector_image_changed;
1714 iface->zoom_changed = gth_image_selector_zoom_changed;
1715 }
1716
1717
1718 GthImageViewerTool *
gth_image_selector_new(GthSelectorType type)1719 gth_image_selector_new (GthSelectorType type)
1720 {
1721 GthImageSelector *selector;
1722
1723 selector = g_object_new (GTH_TYPE_IMAGE_SELECTOR, NULL);
1724 selector->priv->type = type;
1725
1726 return GTH_IMAGE_VIEWER_TOOL (selector);
1727 }
1728
1729
1730 gboolean
gth_image_selector_set_selection_x(GthImageSelector * self,int x)1731 gth_image_selector_set_selection_x (GthImageSelector *self,
1732 int x)
1733 {
1734 cairo_rectangle_int_t new_selection;
1735
1736 new_selection = self->priv->selection;
1737 new_selection.x = x;
1738 return check_and_set_new_selection (self, new_selection);
1739 }
1740
1741
1742 gboolean
gth_image_selector_set_selection_y(GthImageSelector * self,int y)1743 gth_image_selector_set_selection_y (GthImageSelector *self,
1744 int y)
1745 {
1746 cairo_rectangle_int_t new_selection;
1747
1748 new_selection = self->priv->selection;
1749 new_selection.y = y;
1750 return check_and_set_new_selection (self, new_selection);
1751 }
1752
1753
1754 gboolean
gth_image_selector_set_selection_pos(GthImageSelector * self,int x,int y)1755 gth_image_selector_set_selection_pos (GthImageSelector *self,
1756 int x,
1757 int y)
1758 {
1759 cairo_rectangle_int_t new_selection;
1760
1761 new_selection = self->priv->selection;
1762 new_selection.x = x;
1763 new_selection.y = y;
1764 return check_and_set_new_selection (self, new_selection);
1765 }
1766
1767
1768 gboolean
gth_image_selector_set_selection_width(GthImageSelector * self,int width)1769 gth_image_selector_set_selection_width (GthImageSelector *self,
1770 int width)
1771 {
1772 cairo_rectangle_int_t new_selection;
1773
1774 new_selection = self->priv->selection;
1775 new_selection.width = width;
1776 if (self->priv->use_ratio)
1777 new_selection.height = IROUND (width / self->priv->ratio);
1778 return check_and_set_new_selection (self, new_selection);
1779 }
1780
1781
1782 gboolean
gth_image_selector_set_selection_height(GthImageSelector * self,int height)1783 gth_image_selector_set_selection_height (GthImageSelector *self,
1784 int height)
1785 {
1786 cairo_rectangle_int_t new_selection;
1787
1788 new_selection = self->priv->selection;
1789 new_selection.height = height;
1790 if (self->priv->use_ratio)
1791 new_selection.width = IROUND (height * self->priv->ratio);
1792 return check_and_set_new_selection (self, new_selection);
1793 }
1794
1795
1796 void
gth_image_selector_set_selection(GthImageSelector * self,cairo_rectangle_int_t selection)1797 gth_image_selector_set_selection (GthImageSelector *self,
1798 cairo_rectangle_int_t selection)
1799 {
1800 set_selection (self, selection, FALSE);
1801 }
1802
1803
1804 void
gth_image_selector_get_selection(GthImageSelector * self,cairo_rectangle_int_t * selection)1805 gth_image_selector_get_selection (GthImageSelector *self,
1806 cairo_rectangle_int_t *selection)
1807 {
1808 selection->x = MAX (self->priv->selection.x, 0);
1809 selection->y = MAX (self->priv->selection.y, 0);
1810 selection->width = MIN (self->priv->selection.width, self->priv->surface_area.width - self->priv->selection.x);
1811 selection->height = MIN (self->priv->selection.height, self->priv->surface_area.height - self->priv->selection.y);
1812 }
1813
1814
1815 void
gth_image_selector_set_ratio(GthImageSelector * self,gboolean use_ratio,double ratio,gboolean swap_x_and_y_to_start)1816 gth_image_selector_set_ratio (GthImageSelector *self,
1817 gboolean use_ratio,
1818 double ratio,
1819 gboolean swap_x_and_y_to_start)
1820 {
1821 int new_starting_width;
1822
1823 self->priv->use_ratio = use_ratio;
1824 self->priv->ratio = ratio;
1825
1826 if (self->priv->use_ratio) {
1827 /* When changing the cropping aspect ratio, it looks more natural
1828 to swap the height and width, rather than (for example) keeping
1829 the width constant and shrinking the height. */
1830 if (swap_x_and_y_to_start == TRUE)
1831 new_starting_width = self->priv->selection.height;
1832 else
1833 new_starting_width = self->priv->selection.width;
1834
1835 gth_image_selector_set_selection_width (self, new_starting_width);
1836 gth_image_selector_set_selection_height (self, self->priv->selection.height);
1837
1838 /* However, if swapping the height and width fails because it exceeds
1839 the image size, then revert to keeping the width constant and shrinking
1840 the height. That is guaranteed to fit inside the old selection. */
1841 if ( (swap_x_and_y_to_start == TRUE) &&
1842 (self->priv->selection.width != new_starting_width))
1843 {
1844 gth_image_selector_set_selection_width (self, self->priv->selection.width);
1845 gth_image_selector_set_selection_height (self, self->priv->selection.height);
1846 }
1847 }
1848 }
1849
1850
1851 double
gth_image_selector_get_ratio(GthImageSelector * self)1852 gth_image_selector_get_ratio (GthImageSelector *self)
1853 {
1854 return self->priv->ratio;
1855 }
1856
1857
1858 gboolean
gth_image_selector_get_use_ratio(GthImageSelector * self)1859 gth_image_selector_get_use_ratio (GthImageSelector *self)
1860 {
1861 return self->priv->use_ratio;
1862 }
1863
1864
1865 void
gth_image_selector_set_mask_visible(GthImageSelector * self,gboolean visible)1866 gth_image_selector_set_mask_visible (GthImageSelector *self,
1867 gboolean visible)
1868 {
1869 if (visible == self->priv->mask_visible)
1870 return;
1871
1872 self->priv->mask_visible = visible;
1873 if (self->priv->viewer != NULL)
1874 gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
1875 g_signal_emit (G_OBJECT (self),
1876 signals[MASK_VISIBILITY_CHANGED],
1877 0);
1878 }
1879
1880
1881 void
gth_image_selector_set_grid_type(GthImageSelector * self,GthGridType grid_type)1882 gth_image_selector_set_grid_type (GthImageSelector *self,
1883 GthGridType grid_type)
1884 {
1885 if (grid_type == self->priv->grid_type)
1886 return;
1887
1888 self->priv->grid_type = grid_type;
1889 if (self->priv->viewer != NULL)
1890 gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
1891 g_signal_emit (G_OBJECT (self),
1892 signals[GRID_VISIBILITY_CHANGED],
1893 0);
1894 }
1895
1896
1897 gboolean
gth_image_selector_get_mask_visible(GthImageSelector * self)1898 gth_image_selector_get_mask_visible (GthImageSelector *self)
1899 {
1900 return self->priv->mask_visible;
1901 }
1902
1903
1904 GthGridType
gth_image_selector_get_grid_type(GthImageSelector * self)1905 gth_image_selector_get_grid_type (GthImageSelector *self)
1906 {
1907 return self->priv->grid_type;
1908 }
1909
1910
1911 void
gth_image_selector_bind_dimensions(GthImageSelector * self,gboolean bind,int factor)1912 gth_image_selector_bind_dimensions (GthImageSelector *self,
1913 gboolean bind,
1914 int factor)
1915 {
1916 self->priv->bind_dimensions = bind;
1917 self->priv->bind_factor = factor;
1918 }
1919
1920
1921 void
gth_image_selector_center(GthImageSelector * self)1922 gth_image_selector_center (GthImageSelector *self)
1923 {
1924 cairo_rectangle_int_t new_selection;
1925
1926 new_selection = self->priv->selection;
1927 new_selection.x = (self->priv->surface_area.width - new_selection.width) / 2;
1928 new_selection.y = (self->priv->surface_area.height - new_selection.height) / 2;
1929 check_and_set_new_selection (self, new_selection);
1930 }
1931