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