1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4 
5 #include <stdlib.h>
6 #include <math.h>
7 #include <gdk-pixbuf/gdk-pixbuf.h>
8 #include <gdk/gdkkeysyms.h>
9 #ifdef HAVE_RSVG
10 #include <librsvg/rsvg.h>
11 #endif
12 
13 #include <glib/gi18n.h>
14 
15 #include "eog-config-keys.h"
16 #include "eog-enum-types.h"
17 #include "eog-scroll-view.h"
18 #include "eog-debug.h"
19 #include "zoom.h"
20 
21 /* Maximum zoom factor */
22 #define MAX_ZOOM_FACTOR EOG_SCROLL_VIEW_MAX_ZOOM_FACTOR
23 #define MIN_ZOOM_FACTOR EOG_SCROLL_VIEW_MIN_ZOOM_FACTOR
24 
25 /* Default increment for zooming.  The current zoom factor is multiplied or
26  * divided by this amount on every zooming step.  For consistency, you should
27  * use the same value elsewhere in the program.
28  */
29 #define IMAGE_VIEW_ZOOM_MULTIPLIER 1.05
30 
31 /* Maximum size of delayed repaint rectangles */
32 #define PAINT_RECT_WIDTH 128
33 #define PAINT_RECT_HEIGHT 128
34 
35 /* Scroll step increment */
36 #define SCROLL_STEP_SIZE 32
37 
38 #define CHECK_MEDIUM 8
39 #define CHECK_BLACK "#000000"
40 #define CHECK_DARK "#555555"
41 #define CHECK_GRAY "#808080"
42 #define CHECK_LIGHT "#cccccc"
43 #define CHECK_WHITE "#ffffff"
44 
45 /* Time used for the revealing animation of the overlaid buttons */
46 #define OVERLAY_REVEAL_ANIM_TIME (500U) /* ms */
47 
48 /* from cairo-image-surface.c */
49 #define MAX_IMAGE_SIZE 32767
50 
51 /* Signal IDs */
52 enum {
53 	SIGNAL_ZOOM_CHANGED,
54 	SIGNAL_ROTATION_CHANGED,
55 	SIGNAL_NEXT_IMAGE,
56 	SIGNAL_PREVIOUS_IMAGE,
57 	SIGNAL_LAST
58 };
59 static gint view_signals [SIGNAL_LAST];
60 
61 typedef enum {
62 	EOG_SCROLL_VIEW_CURSOR_NORMAL,
63 	EOG_SCROLL_VIEW_CURSOR_HIDDEN,
64 	EOG_SCROLL_VIEW_CURSOR_DRAG
65 } EogScrollViewCursor;
66 
67 typedef enum {
68 	EOG_ROTATION_0,
69 	EOG_ROTATION_90,
70 	EOG_ROTATION_180,
71 	EOG_ROTATION_270,
72 	N_EOG_ROTATIONS
73 } EogRotationState;
74 
75 typedef enum {
76 	EOG_PAN_ACTION_NONE,
77 	EOG_PAN_ACTION_NEXT,
78 	EOG_PAN_ACTION_PREV
79 } EogPanAction;
80 
81 /* Drag 'n Drop */
82 static GtkTargetEntry target_table[] = {
83         { "text/uri-list", 0, 0},
84 };
85 
86 enum {
87 	PROP_0,
88 	PROP_ANTIALIAS_IN,
89 	PROP_ANTIALIAS_OUT,
90 	PROP_BACKGROUND_COLOR,
91 	PROP_IMAGE,
92 	PROP_SCROLLWHEEL_ZOOM,
93 	PROP_TRANSP_COLOR,
94 	PROP_TRANSPARENCY_STYLE,
95 	PROP_USE_BG_COLOR,
96 	PROP_ZOOM_MODE,
97 	PROP_ZOOM_MULTIPLIER
98 };
99 
100 /* Private part of the EogScrollView structure */
101 struct _EogScrollViewPrivate {
102 	/* some widgets we rely on */
103 	GtkWidget *display;
104 	GtkAdjustment *hadj;
105 	GtkAdjustment *vadj;
106 	GtkWidget *hbar;
107 	GtkWidget *vbar;
108 	GtkWidget *menu;
109 
110 	/* actual image */
111 	EogImage *image;
112 	guint image_changed_id;
113 	guint frame_changed_id;
114 	GdkPixbuf *pixbuf;
115 	cairo_surface_t *surface;
116 
117 	/* zoom mode, either ZOOM_MODE_FIT or ZOOM_MODE_FREE */
118 	EogZoomMode zoom_mode;
119 
120 	/* whether to allow zoom > 1.0 on zoom fit */
121 	gboolean upscale;
122 
123 	/* the actual zoom factor */
124 	double zoom;
125 
126 	/* the minimum possible (reasonable) zoom factor */
127 	double min_zoom;
128 
129 	/* Current scrolling offsets */
130 	int xofs, yofs;
131 
132 	/* handler ID for paint idle callback */
133 	guint idle_id;
134 
135 	/* Interpolation type when zoomed in*/
136 	cairo_filter_t interp_type_in;
137 
138 	/* Interpolation type when zoomed out*/
139 	cairo_filter_t interp_type_out;
140 
141 	/* Scroll wheel zoom */
142 	gboolean scroll_wheel_zoom;
143 
144 	/* Scroll wheel zoom */
145 	gdouble zoom_multiplier;
146 
147 	/* dragging stuff */
148 	int drag_anchor_x, drag_anchor_y;
149 	int drag_ofs_x, drag_ofs_y;
150 	guint dragging : 1;
151 
152 	/* how to indicate transparency in images */
153 	EogTransparencyStyle transp_style;
154 	GdkRGBA transp_color;
155 
156 	/* the type of the cursor we are currently showing */
157 	EogScrollViewCursor cursor;
158 
159 	gboolean  use_bg_color;
160 	GdkRGBA *background_color;
161 	GdkRGBA *override_bg_color;
162 
163 	cairo_surface_t *background_surface;
164 
165 	GtkGesture *pan_gesture;
166 	GtkGesture *zoom_gesture;
167 	GtkGesture *rotate_gesture;
168 	gdouble initial_zoom;
169 	EogRotationState rotate_state;
170 	EogPanAction pan_action;
171 
172 	GtkWidget *overlay;
173 	GtkWidget *left_revealer;
174 	GtkWidget *right_revealer;
175 	GtkWidget *bottom_revealer;
176 	GSource   *overlay_timeout_source;
177 
178 	/* Two-pass filtering */
179 	GSource *hq_redraw_timeout_source;
180 	gboolean force_unfiltered;
181 };
182 
183 static void scroll_by (EogScrollView *view, int xofs, int yofs);
184 static void set_zoom_fit (EogScrollView *view);
185 /* static void request_paint_area (EogScrollView *view, GdkRectangle *area); */
186 static void set_minimum_zoom_factor (EogScrollView *view);
187 static void view_on_drag_begin_cb (GtkWidget *widget, GdkDragContext *context,
188                                    gpointer user_data);
189 static void view_on_drag_data_get_cb (GtkWidget *widget,
190                                       GdkDragContext*drag_context,
191                                       GtkSelectionData *data, guint info,
192                                       guint time, gpointer user_data);
193 static void _set_zoom_mode_internal (EogScrollView *view, EogZoomMode mode);
194 static gboolean eog_scroll_view_get_image_coords (EogScrollView *view, gint *x,
195                                                   gint *y, gint *width,
196                                                   gint *height);
197 static gboolean _eog_gdk_rgba_equal0 (const GdkRGBA *a, const GdkRGBA *b);
198 
199 
G_DEFINE_TYPE_WITH_PRIVATE(EogScrollView,eog_scroll_view,GTK_TYPE_GRID)200 G_DEFINE_TYPE_WITH_PRIVATE (EogScrollView, eog_scroll_view, GTK_TYPE_GRID)
201 
202 /*===================================
203     widget size changing handler &
204 	util functions
205   ---------------------------------*/
206 
207 static cairo_surface_t *
208 create_surface_from_pixbuf (EogScrollView *view, GdkPixbuf *pixbuf)
209 {
210 	cairo_surface_t *surface;
211 	gint w, h;
212 
213 	w = gdk_pixbuf_get_width (pixbuf);
214 	h = gdk_pixbuf_get_height (pixbuf);
215 
216 	if (w > MAX_IMAGE_SIZE || h > MAX_IMAGE_SIZE) {
217 		g_warning ("Image dimensions too large to process");
218 		w = 50;
219 		h = 50;
220 
221 		surface = gdk_window_create_similar_image_surface (
222 				gtk_widget_get_window (view->priv->display),
223 				CAIRO_FORMAT_ARGB32, w, h, 1.0);
224 	} else {
225 		surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 1.0,
226 				gtk_widget_get_window (view->priv->display));
227 	}
228 
229 	return surface;
230 }
231 
232 /* Disconnects from the EogImage and removes references to it */
233 static void
free_image_resources(EogScrollView * view)234 free_image_resources (EogScrollView *view)
235 {
236 	EogScrollViewPrivate *priv;
237 
238 	priv = view->priv;
239 
240 	if (priv->image_changed_id > 0) {
241 		g_signal_handler_disconnect (G_OBJECT (priv->image), priv->image_changed_id);
242 		priv->image_changed_id = 0;
243 	}
244 
245 	if (priv->frame_changed_id > 0) {
246 		g_signal_handler_disconnect (G_OBJECT (priv->image), priv->frame_changed_id);
247 		priv->frame_changed_id = 0;
248 	}
249 
250 	if (priv->image != NULL) {
251 		eog_image_data_unref (priv->image);
252 		priv->image = NULL;
253 	}
254 
255 	if (priv->pixbuf != NULL) {
256 		g_object_unref (priv->pixbuf);
257 		priv->pixbuf = NULL;
258 	}
259 
260 	if (priv->surface != NULL) {
261 		cairo_surface_destroy (priv->surface);
262 		priv->surface = NULL;
263 	}
264 }
265 
266 /* Computes the size in pixels of the scaled image */
267 static void
compute_scaled_size(EogScrollView * view,double zoom,int * width,int * height)268 compute_scaled_size (EogScrollView *view, double zoom, int *width, int *height)
269 {
270 	EogScrollViewPrivate *priv;
271 
272 	priv = view->priv;
273 
274 	if (priv->pixbuf) {
275 		*width = floor (gdk_pixbuf_get_width (priv->pixbuf) * zoom + 0.5);
276 		*height = floor (gdk_pixbuf_get_height (priv->pixbuf) * zoom + 0.5);
277 	} else
278 		*width = *height = 0;
279 }
280 
281 /* Computes the offsets for the new zoom value so that they keep the image
282  * centered on the view.
283  */
284 static void
compute_center_zoom_offsets(EogScrollView * view,double old_zoom,double new_zoom,int width,int height,double zoom_x_anchor,double zoom_y_anchor,int * xofs,int * yofs)285 compute_center_zoom_offsets (EogScrollView *view,
286                              double old_zoom, double new_zoom,
287                              int width, int height,
288                              double zoom_x_anchor, double zoom_y_anchor,
289                              int *xofs, int *yofs)
290 {
291 	EogScrollViewPrivate *priv;
292 	int old_scaled_width, old_scaled_height;
293 	int new_scaled_width, new_scaled_height;
294 	double view_cx, view_cy;
295 
296 	priv = view->priv;
297 
298 	compute_scaled_size (view, old_zoom,
299 	                     &old_scaled_width, &old_scaled_height);
300 
301 	if (old_scaled_width < width)
302 		view_cx = (zoom_x_anchor * old_scaled_width) / old_zoom;
303 	else
304 		view_cx = (priv->xofs + zoom_x_anchor * width) / old_zoom;
305 
306 	if (old_scaled_height < height)
307 		view_cy = (zoom_y_anchor * old_scaled_height) / old_zoom;
308 	else
309 		view_cy = (priv->yofs + zoom_y_anchor * height) / old_zoom;
310 
311 	compute_scaled_size (view, new_zoom,
312 	                     &new_scaled_width, &new_scaled_height);
313 
314 	if (new_scaled_width < width)
315 		*xofs = 0;
316 	else {
317 		*xofs = floor (view_cx * new_zoom - zoom_x_anchor * width + 0.5);
318 		if (*xofs < 0)
319 			*xofs = 0;
320 	}
321 
322 	if (new_scaled_height < height)
323 		*yofs = 0;
324 	else {
325 		*yofs = floor (view_cy * new_zoom - zoom_y_anchor * height + 0.5);
326 		if (*yofs < 0)
327 			*yofs = 0;
328 	}
329 }
330 
331 /* Sets the scrollbar values based on the current scrolling offset */
332 static void
update_scrollbar_values(EogScrollView * view)333 update_scrollbar_values (EogScrollView *view)
334 {
335 	EogScrollViewPrivate *priv;
336 	int scaled_width, scaled_height;
337 	gdouble page_size,page_increment,step_increment;
338 	gdouble lower, upper;
339 	GtkAllocation allocation;
340 
341 	priv = view->priv;
342 
343 	if (!gtk_widget_get_visible (GTK_WIDGET (priv->hbar))
344 	    && !gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
345 		return;
346 
347 	compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
348 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
349 
350 	if (gtk_widget_get_visible (GTK_WIDGET (priv->hbar))) {
351 		/* Set scroll increments */
352 		page_size = MIN (scaled_width, allocation.width);
353 		page_increment = allocation.width / 2;
354 		step_increment = SCROLL_STEP_SIZE;
355 
356 		/* Set scroll bounds and new offsets */
357 		lower = 0;
358 		upper = scaled_width;
359 		priv->xofs = CLAMP (priv->xofs, 0, upper - page_size);
360 
361 		g_signal_handlers_block_matched (
362 		        priv->hadj, G_SIGNAL_MATCH_DATA,
363 		        0, 0, NULL, NULL, view);
364 
365 		gtk_adjustment_configure (priv->hadj, priv->xofs, lower,
366 		                          upper, step_increment,
367 		                          page_increment, page_size);
368 
369 		g_signal_handlers_unblock_matched (
370 		        priv->hadj, G_SIGNAL_MATCH_DATA,
371 		        0, 0, NULL, NULL, view);
372 	}
373 
374 	if (gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) {
375 		page_size = MIN (scaled_height, allocation.height);
376 		page_increment = allocation.height / 2;
377 		step_increment = SCROLL_STEP_SIZE;
378 
379 		lower = 0;
380 		upper = scaled_height;
381 		priv->yofs = CLAMP (priv->yofs, 0, upper - page_size);
382 
383 		g_signal_handlers_block_matched (
384 		        priv->vadj, G_SIGNAL_MATCH_DATA,
385 		        0, 0, NULL, NULL, view);
386 
387 		gtk_adjustment_configure (priv->vadj, priv->yofs, lower,
388 		                          upper, step_increment,
389 		                          page_increment, page_size);
390 
391 		g_signal_handlers_unblock_matched (
392 		        priv->vadj, G_SIGNAL_MATCH_DATA,
393 		        0, 0, NULL, NULL, view);
394 	}
395 }
396 
397 static void
eog_scroll_view_set_cursor(EogScrollView * view,EogScrollViewCursor new_cursor)398 eog_scroll_view_set_cursor (EogScrollView *view, EogScrollViewCursor new_cursor)
399 {
400 	GdkCursor *cursor = NULL;
401 	GdkDisplay *display;
402 	GtkWidget *widget;
403 
404 	if (view->priv->cursor == new_cursor) {
405 		return;
406 	}
407 
408 	widget = gtk_widget_get_toplevel (GTK_WIDGET (view));
409 	display = gtk_widget_get_display (widget);
410 	view->priv->cursor = new_cursor;
411 
412 	switch (new_cursor) {
413 	        case EOG_SCROLL_VIEW_CURSOR_NORMAL:
414 		        gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
415 		        break;
416 	        case EOG_SCROLL_VIEW_CURSOR_HIDDEN:
417 		        cursor = gdk_cursor_new_for_display (display, GDK_BLANK_CURSOR);
418 		        break;
419 	        case EOG_SCROLL_VIEW_CURSOR_DRAG:
420 		        cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
421 		        break;
422 	}
423 
424 	if (cursor) {
425 		gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
426 		g_object_unref (cursor);
427 		gdk_display_flush(display);
428 	}
429 }
430 
431 /* Changes visibility of the scrollbars based on the zoom factor and the
432  * specified allocation, or the current allocation if NULL is specified.
433  */
434 static void
check_scrollbar_visibility(EogScrollView * view,GtkAllocation * alloc)435 check_scrollbar_visibility (EogScrollView *view, GtkAllocation *alloc)
436 {
437 	EogScrollViewPrivate *priv;
438 	int bar_height;
439 	int bar_width;
440 	int img_width;
441 	int img_height;
442 	GtkRequisition req;
443 	int width, height;
444 	gboolean hbar_visible, vbar_visible;
445 
446 	priv = view->priv;
447 
448 	if (alloc) {
449 		width = alloc->width;
450 		height = alloc->height;
451 	} else {
452 		GtkAllocation allocation;
453 
454 		gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
455 		width = allocation.width;
456 		height = allocation.height;
457 	}
458 
459 	compute_scaled_size (view, priv->zoom, &img_width, &img_height);
460 
461 	/* this should work fairly well in this special case for scrollbars */
462 	gtk_widget_get_preferred_size (priv->hbar, &req, NULL);
463 	bar_height = req.height;
464 	gtk_widget_get_preferred_size (priv->vbar, &req, NULL);
465 	bar_width = req.width;
466 
467 	eog_debug_message (DEBUG_WINDOW, "Widget Size allocate: %i, %i   Bar: %i, %i\n",
468 	                   width, height, bar_width, bar_height);
469 
470 	hbar_visible = vbar_visible = FALSE;
471 	if (priv->zoom_mode == EOG_ZOOM_MODE_SHRINK_TO_FIT)
472 		hbar_visible = vbar_visible = FALSE;
473 	else if (img_width <= width && img_height <= height)
474 		hbar_visible = vbar_visible = FALSE;
475 	else if (img_width > width && img_height > height)
476 		hbar_visible = vbar_visible = TRUE;
477 	else if (img_width > width) {
478 		hbar_visible = TRUE;
479 		if (img_height <= (height - bar_height))
480 			vbar_visible = FALSE;
481 		else
482 			vbar_visible = TRUE;
483 	}
484 	else if (img_height > height) {
485 		vbar_visible = TRUE;
486 		if (img_width <= (width - bar_width))
487 			hbar_visible = FALSE;
488 		else
489 			hbar_visible = TRUE;
490 	}
491 
492 	if (hbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->hbar)))
493 		g_object_set (G_OBJECT (priv->hbar), "visible", hbar_visible, NULL);
494 
495 	if (vbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
496 		g_object_set (G_OBJECT (priv->vbar), "visible", vbar_visible, NULL);
497 }
498 
499 #define DOUBLE_EQUAL_MAX_DIFF 1e-6
500 #define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF)
501 
502 /* Returns whether the image is zoomed in */
503 static gboolean
is_zoomed_in(EogScrollView * view)504 is_zoomed_in (EogScrollView *view)
505 {
506 	EogScrollViewPrivate *priv;
507 
508 	priv = view->priv;
509 	return priv->zoom - 1.0 > DOUBLE_EQUAL_MAX_DIFF;
510 }
511 
512 /* Returns whether the image is zoomed out */
513 static gboolean
is_zoomed_out(EogScrollView * view)514 is_zoomed_out (EogScrollView *view)
515 {
516 	EogScrollViewPrivate *priv;
517 
518 	priv = view->priv;
519 	return DOUBLE_EQUAL_MAX_DIFF + priv->zoom - 1.0 < 0.0;
520 }
521 
522 /* Returns wether the image is movable, that means if it is larger then
523  * the actual visible area.
524  */
525 
526 static gboolean
is_image_movable(EogScrollView * view)527 is_image_movable (EogScrollView *view)
528 {
529 	EogScrollViewPrivate *priv;
530 
531 	priv = view->priv;
532 
533 	return (gtk_widget_get_visible (priv->hbar) || gtk_widget_get_visible (priv->vbar));
534 }
535 
536 /*===================================
537 	  drawing core
538   ---------------------------------*/
539 
540 static void
get_transparency_params(EogScrollView * view,int * size,GdkRGBA * color1,GdkRGBA * color2)541 get_transparency_params (EogScrollView *view, int *size, GdkRGBA *color1, GdkRGBA *color2)
542 {
543 	EogScrollViewPrivate *priv;
544 
545 	priv = view->priv;
546 
547 	/* Compute transparency parameters */
548 	switch (priv->transp_style) {
549 	case EOG_TRANSP_BACKGROUND: {
550 		/* Simply return fully transparent color */
551 		color1->red = color1->green = color1->blue = color1->alpha = 0.0;
552 		color2->red = color2->green = color2->blue = color2->alpha = 0.0;
553 		break;
554 	}
555 
556 	case EOG_TRANSP_CHECKED:
557 		g_warn_if_fail (gdk_rgba_parse (color1, CHECK_GRAY));
558 		g_warn_if_fail (gdk_rgba_parse (color2, CHECK_LIGHT));
559 		break;
560 
561 	case EOG_TRANSP_COLOR:
562 		*color1 = *color2 = priv->transp_color;
563 		break;
564 
565 	default:
566 		g_assert_not_reached ();
567 	};
568 
569 	*size = CHECK_MEDIUM;
570 }
571 
572 
573 static cairo_surface_t *
create_background_surface(EogScrollView * view)574 create_background_surface (EogScrollView *view)
575 {
576 	int check_size;
577 	GdkRGBA check_1;
578 	GdkRGBA check_2;
579 	cairo_surface_t *surface;
580 
581 	get_transparency_params (view, &check_size, &check_1, &check_2);
582 	surface = gdk_window_create_similar_surface (gtk_widget_get_window (view->priv->display),
583 	                                             CAIRO_CONTENT_COLOR_ALPHA,
584 	                                             check_size * 2, check_size * 2);
585 	cairo_t* cr = cairo_create (surface);
586 
587 	/* Use source operator to make fully transparent work */
588 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
589 
590 	gdk_cairo_set_source_rgba(cr, &check_1);
591 	cairo_rectangle (cr, 0, 0, check_size, check_size);
592 	cairo_rectangle (cr, check_size, check_size, check_size, check_size);
593 	cairo_fill (cr);
594 
595 	gdk_cairo_set_source_rgba(cr, &check_2);
596 	cairo_rectangle (cr, 0, check_size, check_size, check_size);
597 	cairo_rectangle (cr, check_size, 0, check_size, check_size);
598 	cairo_fill (cr);
599 
600 	cairo_destroy (cr);
601 
602 	return surface;
603 }
604 
605 /* =======================================
606 
607     scrolling stuff
608 
609     --------------------------------------*/
610 
611 /* Scrolls the view to the specified offsets.  */
612 static void
scroll_to(EogScrollView * view,int x,int y,gboolean change_adjustments)613 scroll_to (EogScrollView *view, int x, int y, gboolean change_adjustments)
614 {
615 	EogScrollViewPrivate *priv;
616 	GtkAllocation allocation;
617 	int xofs, yofs;
618 	GdkWindow *window;
619 
620 	priv = view->priv;
621 
622 	/* Check bounds & Compute offsets */
623 	if (gtk_widget_get_visible (priv->hbar)) {
624 		x = CLAMP (x, 0, gtk_adjustment_get_upper (priv->hadj)
625 		                 - gtk_adjustment_get_page_size (priv->hadj));
626 		xofs = x - priv->xofs;
627 	} else
628 		xofs = 0;
629 
630 	if (gtk_widget_get_visible (priv->vbar)) {
631 		y = CLAMP (y, 0, gtk_adjustment_get_upper (priv->vadj)
632 		                 - gtk_adjustment_get_page_size (priv->vadj));
633 		yofs = y - priv->yofs;
634 	} else
635 		yofs = 0;
636 
637 	if (xofs == 0 && yofs == 0)
638 		return;
639 
640 	priv->xofs = x;
641 	priv->yofs = y;
642 
643 	if (!gtk_widget_is_drawable (priv->display))
644 		goto out;
645 
646 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
647 
648 	if (abs (xofs) >= allocation.width || abs (yofs) >= allocation.height) {
649 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
650 		goto out;
651 	}
652 
653 	window = gtk_widget_get_window (GTK_WIDGET (priv->display));
654 
655 	if (!gtk_gesture_is_recognized (priv->zoom_gesture)) {
656 		gdk_window_scroll (window, -xofs, -yofs);
657 	}
658 
659  out:
660 	if (!change_adjustments)
661 		return;
662 
663 	g_signal_handlers_block_matched (
664 	        priv->hadj, G_SIGNAL_MATCH_DATA,
665 	        0, 0, NULL, NULL, view);
666 	g_signal_handlers_block_matched (
667 	        priv->vadj, G_SIGNAL_MATCH_DATA,
668 	        0, 0, NULL, NULL, view);
669 
670 	gtk_adjustment_set_value (priv->hadj, x);
671 	gtk_adjustment_set_value (priv->vadj, y);
672 
673 	g_signal_handlers_unblock_matched (
674 	        priv->hadj, G_SIGNAL_MATCH_DATA,
675 	        0, 0, NULL, NULL, view);
676 	g_signal_handlers_unblock_matched (
677 	        priv->vadj, G_SIGNAL_MATCH_DATA,
678 	        0, 0, NULL, NULL, view);
679 }
680 
681 /* Scrolls the image view by the specified offsets.  Notifies the adjustments
682  * about their new values.
683  */
684 static void
scroll_by(EogScrollView * view,int xofs,int yofs)685 scroll_by (EogScrollView *view, int xofs, int yofs)
686 {
687 	EogScrollViewPrivate *priv;
688 
689 	priv = view->priv;
690 
691 	scroll_to (view, priv->xofs + xofs, priv->yofs + yofs, TRUE);
692 }
693 
694 
695 /* Callback used when an adjustment is changed */
696 static void
adjustment_changed_cb(GtkAdjustment * adj,gpointer data)697 adjustment_changed_cb (GtkAdjustment *adj, gpointer data)
698 {
699 	EogScrollView *view;
700 	EogScrollViewPrivate *priv;
701 
702 	view = EOG_SCROLL_VIEW (data);
703 	priv = view->priv;
704 
705 	scroll_to (view, gtk_adjustment_get_value (priv->hadj),
706 	           gtk_adjustment_get_value (priv->vadj), FALSE);
707 }
708 
709 
710 /* Drags the image to the specified position */
711 static void
drag_to(EogScrollView * view,int x,int y)712 drag_to (EogScrollView *view, int x, int y)
713 {
714 	EogScrollViewPrivate *priv;
715 	int dx, dy;
716 
717 	priv = view->priv;
718 
719 	dx = priv->drag_anchor_x - x;
720 	dy = priv->drag_anchor_y - y;
721 
722 	x = priv->drag_ofs_x + dx;
723 	y = priv->drag_ofs_y + dy;
724 
725 	scroll_to (view, x, y, TRUE);
726 }
727 
728 static void
set_minimum_zoom_factor(EogScrollView * view)729 set_minimum_zoom_factor (EogScrollView *view)
730 {
731 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
732 
733 	view->priv->min_zoom = MAX (1.0 / gdk_pixbuf_get_width (view->priv->pixbuf),
734 	                            MAX(1.0 / gdk_pixbuf_get_height (view->priv->pixbuf),
735 	                                MIN_ZOOM_FACTOR) );
736 	return;
737 }
738 
739 /**
740  * set_zoom:
741  * @view: A scroll view.
742  * @zoom: Zoom factor.
743  * @have_anchor: Whether the anchor point specified by (@anchorx, @anchory)
744  * should be used.
745  * @anchorx: Horizontal anchor point in pixels.
746  * @anchory: Vertical anchor point in pixels.
747  *
748  * Sets the zoom factor for an image view.  The anchor point can be used to
749  * specify the point that stays fixed when the image is zoomed.  If @have_anchor
750  * is %TRUE, then (@anchorx, @anchory) specify the point relative to the image
751  * view widget's allocation that will stay fixed when zooming.  If @have_anchor
752  * is %FALSE, then the center point of the image view will be used.
753  **/
754 static void
set_zoom(EogScrollView * view,double zoom,gboolean have_anchor,int anchorx,int anchory)755 set_zoom (EogScrollView *view, double zoom,
756           gboolean have_anchor, int anchorx, int anchory)
757 {
758 	EogScrollViewPrivate *priv;
759 	GtkAllocation allocation;
760 	int xofs, yofs;
761 	double x_rel, y_rel;
762 
763 	priv = view->priv;
764 
765 	if (priv->pixbuf == NULL)
766 		return;
767 
768 	if (zoom > MAX_ZOOM_FACTOR)
769 		zoom = MAX_ZOOM_FACTOR;
770 	else if (zoom < MIN_ZOOM_FACTOR)
771 		zoom = MIN_ZOOM_FACTOR;
772 
773 	if (DOUBLE_EQUAL (priv->zoom, zoom))
774 		return;
775 	if (DOUBLE_EQUAL (priv->zoom, priv->min_zoom) && zoom < priv->zoom)
776 		return;
777 
778 	eog_scroll_view_set_zoom_mode (view, EOG_ZOOM_MODE_FREE);
779 
780 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
781 
782 	/* compute new xofs/yofs values */
783 	if (have_anchor) {
784 		x_rel = (double) anchorx / allocation.width;
785 		y_rel = (double) anchory / allocation.height;
786 	} else {
787 		x_rel = 0.5;
788 		y_rel = 0.5;
789 	}
790 
791 	compute_center_zoom_offsets (view, priv->zoom, zoom,
792 	                             allocation.width, allocation.height,
793 	                             x_rel, y_rel,
794 	                             &xofs, &yofs);
795 
796 	/* set new values */
797 	priv->xofs = xofs; /* (img_width * x_rel * zoom) - anchorx; */
798 	priv->yofs = yofs; /* (img_height * y_rel * zoom) - anchory; */
799 
800 	if (priv->dragging) {
801 		priv->drag_anchor_x = anchorx;
802 		priv->drag_anchor_y = anchory;
803 		priv->drag_ofs_x = priv->xofs;
804 		priv->drag_ofs_y = priv->yofs;
805 	}
806 #if 0
807 	g_print ("xofs: %i  yofs: %i\n", priv->xofs, priv->yofs);
808 #endif
809 	if (zoom <= priv->min_zoom)
810 		priv->zoom = priv->min_zoom;
811 	else
812 		priv->zoom = zoom;
813 
814 	/* we make use of the new values here */
815 	check_scrollbar_visibility (view, NULL);
816 	update_scrollbar_values (view);
817 
818 	/* repaint the whole image */
819 	gtk_widget_queue_draw (GTK_WIDGET (priv->display));
820 
821 	g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom);
822 }
823 
824 /* Zooms the image to fit the available allocation */
825 static void
set_zoom_fit(EogScrollView * view)826 set_zoom_fit (EogScrollView *view)
827 {
828 	EogScrollViewPrivate *priv;
829 	GtkAllocation allocation;
830 	double new_zoom;
831 
832 	priv = view->priv;
833 
834 	priv->zoom_mode = EOG_ZOOM_MODE_SHRINK_TO_FIT;
835 
836 	if (!gtk_widget_get_mapped (GTK_WIDGET (view)))
837 		return;
838 
839 	if (priv->pixbuf == NULL)
840 		return;
841 
842 	gtk_widget_get_allocation (GTK_WIDGET(priv->display), &allocation);
843 
844 	new_zoom = zoom_fit_scale (allocation.width, allocation.height,
845 	                           gdk_pixbuf_get_width (priv->pixbuf),
846 	                           gdk_pixbuf_get_height (priv->pixbuf),
847 	                           priv->upscale);
848 
849 	if (new_zoom > MAX_ZOOM_FACTOR)
850 		new_zoom = MAX_ZOOM_FACTOR;
851 	else if (new_zoom < MIN_ZOOM_FACTOR)
852 		new_zoom = MIN_ZOOM_FACTOR;
853 
854 	priv->zoom = new_zoom;
855 	priv->xofs = 0;
856 	priv->yofs = 0;
857 
858 	g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom);
859 }
860 
861 /*===================================
862 
863    internal signal callbacks
864 
865   ---------------------------------*/
866 
867 /* Key press event handler for the image view */
868 static gboolean
display_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer data)869 display_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data)
870 {
871 	EogScrollView *view;
872 	EogScrollViewPrivate *priv;
873 	GtkAllocation allocation;
874 	gboolean do_zoom;
875 	double zoom;
876 	gboolean do_scroll;
877 	int xofs, yofs;
878 	GdkModifierType modifiers;
879 
880 	view = EOG_SCROLL_VIEW (data);
881 	priv = view->priv;
882 
883 	do_zoom = FALSE;
884 	do_scroll = FALSE;
885 	xofs = yofs = 0;
886 	zoom = 1.0;
887 
888 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
889 
890 	modifiers = gtk_accelerator_get_default_mod_mask ();
891 
892 	switch (event->keyval) {
893 	case GDK_KEY_Up:
894 		if ((event->state & modifiers) == GDK_MOD1_MASK) {
895 			do_scroll = TRUE;
896 			xofs = 0;
897 			yofs = -SCROLL_STEP_SIZE;
898 		}
899 		break;
900 
901 	case GDK_KEY_Page_Up:
902 		if ((event->state & GDK_MOD1_MASK) != 0) {
903 			do_scroll = TRUE;
904 			if (event->state & GDK_CONTROL_MASK) {
905 				xofs = -(allocation.width * 3) / 4;
906 				yofs = 0;
907 			} else {
908 				xofs = 0;
909 				yofs = -(allocation.height * 3) / 4;
910 			}
911 		}
912 		break;
913 
914 	case GDK_KEY_Down:
915 		if ((event->state & modifiers) == GDK_MOD1_MASK) {
916 			do_scroll = TRUE;
917 			xofs = 0;
918 			yofs = SCROLL_STEP_SIZE;
919 		}
920 		break;
921 
922 	case GDK_KEY_Page_Down:
923 		if ((event->state & GDK_MOD1_MASK) != 0) {
924 			do_scroll = TRUE;
925 			if (event->state & GDK_CONTROL_MASK) {
926 				xofs = (allocation.width * 3) / 4;
927 				yofs = 0;
928 			} else {
929 				xofs = 0;
930 				yofs = (allocation.height * 3) / 4;
931 			}
932 		}
933 		break;
934 
935 	case GDK_KEY_Left:
936 		if ((event->state & modifiers) == GDK_MOD1_MASK) {
937 			do_scroll = TRUE;
938 			xofs = -SCROLL_STEP_SIZE;
939 			yofs = 0;
940 		}
941 		break;
942 
943 	case GDK_KEY_Right:
944 		if ((event->state & modifiers) == GDK_MOD1_MASK) {
945 			do_scroll = TRUE;
946 			xofs = SCROLL_STEP_SIZE;
947 			yofs = 0;
948 		}
949 		break;
950 
951 	case GDK_KEY_plus:
952 	case GDK_KEY_equal:
953 	case GDK_KEY_KP_Add:
954 		if (!(event->state & modifiers)) {
955 			do_zoom = TRUE;
956 			zoom = priv->zoom * priv->zoom_multiplier;
957 		}
958 		break;
959 
960 	case GDK_KEY_minus:
961 	case GDK_KEY_KP_Subtract:
962 		if (!(event->state & modifiers)) {
963 			do_zoom = TRUE;
964 			zoom = priv->zoom / priv->zoom_multiplier;
965 		}
966 		break;
967 
968 	case GDK_KEY_1:
969 		if (!(event->state & modifiers)) {
970 			do_zoom = TRUE;
971 			zoom = 1.0;
972 		}
973 		break;
974 
975 	default:
976 		return FALSE;
977 	}
978 
979 	if (do_zoom) {
980 		GdkSeat *seat;
981 		GdkDevice *device;
982 		gint x, y;
983 
984 		seat = gdk_display_get_default_seat (gtk_widget_get_display (widget));
985 		device = gdk_seat_get_pointer (seat);
986 
987 		gdk_window_get_device_position (gtk_widget_get_window (widget), device,
988 		                                &x, &y, NULL);
989 		set_zoom (view, zoom, TRUE, x, y);
990 	}
991 
992 	if (do_scroll)
993 		scroll_by (view, xofs, yofs);
994 
995 	if(!do_scroll && !do_zoom)
996 		return FALSE;
997 
998 	return TRUE;
999 }
1000 
1001 
1002 /* Button press event handler for the image view */
1003 static gboolean
eog_scroll_view_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer data)1004 eog_scroll_view_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data)
1005 {
1006 	EogScrollView *view;
1007 	EogScrollViewPrivate *priv;
1008 
1009 	view = EOG_SCROLL_VIEW (data);
1010 	priv = view->priv;
1011 
1012 	if (!gtk_widget_has_focus (priv->display))
1013 		gtk_widget_grab_focus (GTK_WIDGET (priv->display));
1014 
1015 	if (priv->dragging)
1016 		return FALSE;
1017 
1018 	switch (event->button) {
1019 	        case 1:
1020 	        case 2:
1021 		        if (event->button == 1 && !priv->scroll_wheel_zoom &&
1022 			    !(event->state & GDK_CONTROL_MASK))
1023 				break;
1024 
1025 			if (is_image_movable (view)) {
1026 				eog_scroll_view_set_cursor (view, EOG_SCROLL_VIEW_CURSOR_DRAG);
1027 
1028 				priv->dragging = TRUE;
1029 				priv->drag_anchor_x = event->x;
1030 				priv->drag_anchor_y = event->y;
1031 
1032 				priv->drag_ofs_x = priv->xofs;
1033 				priv->drag_ofs_y = priv->yofs;
1034 
1035 				return TRUE;
1036 			}
1037 	        default:
1038 		        break;
1039 	}
1040 
1041 	return FALSE;
1042 }
1043 
1044 /* Button release event handler for the image view */
1045 static gboolean
eog_scroll_view_button_release_event(GtkWidget * widget,GdkEventButton * event,gpointer data)1046 eog_scroll_view_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer data)
1047 {
1048 	EogScrollView *view;
1049 	EogScrollViewPrivate *priv;
1050 
1051 	view = EOG_SCROLL_VIEW (data);
1052 	priv = view->priv;
1053 
1054 	if (!priv->dragging)
1055 		return FALSE;
1056 
1057 	switch (event->button) {
1058 	        case 1:
1059 	        case 2:
1060 		        drag_to (view, event->x, event->y);
1061 			priv->dragging = FALSE;
1062 
1063 			eog_scroll_view_set_cursor (view, EOG_SCROLL_VIEW_CURSOR_NORMAL);
1064 		        break;
1065 
1066 	        default:
1067 		        break;
1068 	}
1069 
1070 	return TRUE;
1071 }
1072 
1073 /* Scroll event handler for the image view.  We zoom with an event without
1074  * modifiers rather than scroll; we use the Shift modifier to scroll.
1075  * Rationale: images are not primarily vertical, and in EOG you scan scroll by
1076  * dragging the image with button 1 anyways.
1077  */
1078 static gboolean
eog_scroll_view_scroll_event(GtkWidget * widget,GdkEventScroll * event,gpointer data)1079 eog_scroll_view_scroll_event (GtkWidget *widget, GdkEventScroll *event, gpointer data)
1080 {
1081 	EogScrollView *view;
1082 	EogScrollViewPrivate *priv;
1083 	double zoom_factor;
1084 	double min_zoom_factor;
1085 	int xofs, yofs;
1086 
1087 	view = EOG_SCROLL_VIEW (data);
1088 	priv = view->priv;
1089 
1090 	/* Compute zoom factor and scrolling offsets; we'll only use either of them */
1091 	/* same as in gtkscrolledwindow.c */
1092 	xofs = gtk_adjustment_get_page_increment (priv->hadj) / 2;
1093 	yofs = gtk_adjustment_get_page_increment (priv->vadj) / 2;
1094 
1095 	/* Make sure the user visible zoom factor changes */
1096 	min_zoom_factor = (priv->zoom + 0.01L) / priv->zoom;
1097 
1098 	switch (event->direction) {
1099 	case GDK_SCROLL_UP:
1100 		zoom_factor = fmax(priv->zoom_multiplier, min_zoom_factor);
1101 
1102 		xofs = 0;
1103 		yofs = -yofs;
1104 		break;
1105 
1106 	case GDK_SCROLL_LEFT:
1107 		zoom_factor = 1.0 / fmax(priv->zoom_multiplier,
1108 					 min_zoom_factor);
1109 		xofs = -xofs;
1110 		yofs = 0;
1111 		break;
1112 
1113 	case GDK_SCROLL_DOWN:
1114 		zoom_factor = 1.0 / fmax(priv->zoom_multiplier,
1115 					 min_zoom_factor);
1116 		xofs = 0;
1117 		yofs = yofs;
1118 		break;
1119 
1120 	case GDK_SCROLL_RIGHT:
1121 		zoom_factor = fmax(priv->zoom_multiplier, min_zoom_factor);
1122 		xofs = xofs;
1123 		yofs = 0;
1124 		break;
1125 
1126 	default:
1127 		g_assert_not_reached ();
1128 		return FALSE;
1129 	}
1130 
1131 	if (priv->scroll_wheel_zoom) {
1132 		if (event->state & GDK_SHIFT_MASK)
1133 			scroll_by (view, yofs, xofs);
1134 		else if (event->state & GDK_CONTROL_MASK)
1135 			scroll_by (view, xofs, yofs);
1136 		else
1137 			set_zoom (view, priv->zoom * zoom_factor,
1138 			          TRUE, event->x, event->y);
1139 	} else {
1140 		if (event->state & GDK_SHIFT_MASK)
1141 			scroll_by (view, yofs, xofs);
1142 		else if (event->state & GDK_CONTROL_MASK)
1143 			set_zoom (view, priv->zoom * zoom_factor,
1144 			          TRUE, event->x, event->y);
1145 		else
1146 			scroll_by (view, xofs, yofs);
1147 	}
1148 
1149 	return TRUE;
1150 }
1151 
1152 /* Motion event handler for the image view */
1153 static gboolean
eog_scroll_view_motion_event(GtkWidget * widget,GdkEventMotion * event,gpointer data)1154 eog_scroll_view_motion_event (GtkWidget *widget, GdkEventMotion *event, gpointer data)
1155 {
1156 	EogScrollView *view;
1157 	EogScrollViewPrivate *priv;
1158 	gint x, y;
1159 	GdkModifierType mods;
1160 
1161 	view = EOG_SCROLL_VIEW (data);
1162 	priv = view->priv;
1163 
1164 	if (gtk_gesture_is_recognized (priv->zoom_gesture))
1165 		return TRUE;
1166 
1167 	if (!priv->dragging)
1168 		return FALSE;
1169 
1170 	if (event->is_hint)
1171 		gdk_window_get_device_position (gtk_widget_get_window (GTK_WIDGET (priv->display)), event->device, &x, &y, &mods);
1172 	else {
1173 		x = event->x;
1174 		y = event->y;
1175 	}
1176 
1177 	drag_to (view, x, y);
1178 	return TRUE;
1179 }
1180 
1181 static void
display_map_event(GtkWidget * widget,GdkEvent * event,gpointer data)1182 display_map_event (GtkWidget *widget, GdkEvent *event, gpointer data)
1183 {
1184 	EogScrollView *view;
1185 	EogScrollViewPrivate *priv;
1186 
1187 	view = EOG_SCROLL_VIEW (data);
1188 	priv = view->priv;
1189 
1190 	eog_debug (DEBUG_WINDOW);
1191 
1192 	set_zoom_fit (view);
1193 	check_scrollbar_visibility (view, NULL);
1194 	gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1195 }
1196 
1197 static void
eog_scroll_view_size_allocate(GtkWidget * widget,GtkAllocation * alloc)1198 eog_scroll_view_size_allocate (GtkWidget *widget, GtkAllocation *alloc)
1199 {
1200 	EogScrollView *view;
1201 
1202 	view = EOG_SCROLL_VIEW (widget);
1203 	check_scrollbar_visibility (view, alloc);
1204 
1205 	GTK_WIDGET_CLASS (eog_scroll_view_parent_class)->size_allocate (widget
1206 	                                                                ,alloc);
1207 }
1208 
1209 static void
display_size_change(GtkWidget * widget,GdkEventConfigure * event,gpointer data)1210 display_size_change (GtkWidget *widget, GdkEventConfigure *event, gpointer data)
1211 {
1212 	EogScrollView *view;
1213 	EogScrollViewPrivate *priv;
1214 
1215 	view = EOG_SCROLL_VIEW (data);
1216 	priv = view->priv;
1217 
1218 	if (priv->zoom_mode == EOG_ZOOM_MODE_SHRINK_TO_FIT) {
1219 		GtkAllocation alloc;
1220 
1221 		alloc.width = event->width;
1222 		alloc.height = event->height;
1223 
1224 		set_zoom_fit (view);
1225 		check_scrollbar_visibility (view, &alloc);
1226 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1227 	} else {
1228 		int scaled_width, scaled_height;
1229 		int x_offset = 0;
1230 		int y_offset = 0;
1231 
1232 		compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
1233 
1234 		if (priv->xofs + event->width > scaled_width)
1235 			x_offset = scaled_width - event->width - priv->xofs;
1236 
1237 		if (priv->yofs + event->height > scaled_height)
1238 			y_offset = scaled_height - event->height - priv->yofs;
1239 
1240 		scroll_by (view, x_offset, y_offset);
1241 	}
1242 
1243 	update_scrollbar_values (view);
1244 }
1245 
1246 
1247 static gboolean
eog_scroll_view_focus_in_event(GtkWidget * widget,GdkEventFocus * event,gpointer data)1248 eog_scroll_view_focus_in_event (GtkWidget     *widget,
1249                             GdkEventFocus *event,
1250                             gpointer data)
1251 {
1252 	g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_in_event");
1253 	return FALSE;
1254 }
1255 
1256 static gboolean
eog_scroll_view_focus_out_event(GtkWidget * widget,GdkEventFocus * event,gpointer data)1257 eog_scroll_view_focus_out_event (GtkWidget     *widget,
1258                              GdkEventFocus *event,
1259                              gpointer data)
1260 {
1261 	g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_out_event");
1262 	return FALSE;
1263 }
1264 
_hq_redraw_cb(gpointer user_data)1265 static gboolean _hq_redraw_cb (gpointer user_data)
1266 {
1267 	EogScrollViewPrivate *priv = EOG_SCROLL_VIEW (user_data)->priv;
1268 
1269 	priv->force_unfiltered = FALSE;
1270 	gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1271 
1272 	priv->hq_redraw_timeout_source = NULL;
1273 	return G_SOURCE_REMOVE;
1274 }
1275 
1276 static void
_clear_hq_redraw_timeout(EogScrollView * view)1277 _clear_hq_redraw_timeout (EogScrollView *view)
1278 {
1279 	EogScrollViewPrivate *priv = view->priv;
1280 
1281 	if (priv->hq_redraw_timeout_source != NULL) {
1282 		g_source_unref (priv->hq_redraw_timeout_source);
1283 		g_source_destroy (priv->hq_redraw_timeout_source);
1284 	}
1285 
1286 	priv->hq_redraw_timeout_source = NULL;
1287 }
1288 
1289 static void
_set_hq_redraw_timeout(EogScrollView * view)1290 _set_hq_redraw_timeout (EogScrollView *view)
1291 {
1292 	GSource *source;
1293 
1294 	_clear_hq_redraw_timeout (view);
1295 
1296 	source = g_timeout_source_new (200);
1297 	g_source_set_callback (source, &_hq_redraw_cb, view, NULL);
1298 
1299 	g_source_attach (source, NULL);
1300 
1301 	view->priv->hq_redraw_timeout_source = source;
1302 }
1303 
1304 static gboolean
display_draw(GtkWidget * widget,cairo_t * cr,gpointer data)1305 display_draw (GtkWidget *widget, cairo_t *cr, gpointer data)
1306 {
1307 	const GdkRGBA *background_color = NULL;
1308 	EogScrollView *view;
1309 	EogScrollViewPrivate *priv;
1310 	GtkAllocation allocation;
1311 	int scaled_width, scaled_height;
1312 	int xofs, yofs;
1313 
1314 	g_return_val_if_fail (GTK_IS_DRAWING_AREA (widget), FALSE);
1315 	g_return_val_if_fail (EOG_IS_SCROLL_VIEW (data), FALSE);
1316 
1317 	view = EOG_SCROLL_VIEW (data);
1318 
1319 	priv = view->priv;
1320 
1321 	if (priv->pixbuf == NULL)
1322 		return TRUE;
1323 
1324 	eog_scroll_view_get_image_coords (view, &xofs, &yofs,
1325 	                                  &scaled_width, &scaled_height);
1326 
1327 	eog_debug_message (DEBUG_WINDOW, "zoom %.2f, xofs: %i, yofs: %i scaled w: %i h: %i\n",
1328 	                   priv->zoom, xofs, yofs, scaled_width, scaled_height);
1329 
1330 	/* Paint the background */
1331 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
1332 	cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
1333 	if (priv->transp_style != EOG_TRANSP_BACKGROUND)
1334 		cairo_rectangle (cr, MAX (0, xofs), MAX (0, yofs),
1335 		                 scaled_width, scaled_height);
1336 	if (priv->override_bg_color != NULL)
1337 		background_color = priv->override_bg_color;
1338 	else if (priv->use_bg_color)
1339 		background_color = priv->background_color;
1340 	if (background_color != NULL)
1341 		cairo_set_source_rgba (cr,
1342 		                       background_color->red,
1343 		                       background_color->green,
1344 		                       background_color->blue,
1345 		                       background_color->alpha);
1346 	else {
1347 		GtkStyleContext *context;
1348 		GdkRGBA *pattern_rgba;
1349 		GtkStateFlags state;
1350 
1351 		context = gtk_widget_get_style_context (priv->display);
1352 		state = gtk_style_context_get_state (context);
1353 
1354 		gtk_style_context_get (context, state, GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &pattern_rgba, NULL);
1355 		gdk_cairo_set_source_rgba (cr, pattern_rgba);
1356 
1357 		gdk_rgba_free (pattern_rgba);
1358 	}
1359 	cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
1360 	cairo_fill (cr);
1361 
1362 	if (gdk_pixbuf_get_has_alpha (priv->pixbuf)) {
1363 		if (priv->background_surface == NULL) {
1364 			priv->background_surface = create_background_surface (view);
1365 		}
1366 		cairo_set_source_surface (cr, priv->background_surface, xofs, yofs);
1367 		cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
1368 		cairo_rectangle (cr, xofs, yofs, scaled_width, scaled_height);
1369 		cairo_fill (cr);
1370 	}
1371 
1372 	/* Make sure the image is only drawn as large as needed.
1373 	 * This is especially necessary for SVGs where there might
1374 	 * be more image data available outside the image boundaries.
1375 	 */
1376 	cairo_rectangle (cr, xofs, yofs, scaled_width, scaled_height);
1377 	cairo_clip (cr);
1378 
1379 #ifdef HAVE_RSVG
1380 	if (eog_image_is_svg (view->priv->image)) {
1381 		cairo_matrix_t matrix, translate, scale, original;
1382 		EogTransform *transform = eog_image_get_transform (priv->image);
1383 		cairo_matrix_init_identity (&matrix);
1384 		if (transform) {
1385 			cairo_matrix_t affine;
1386 			double image_offset_x = 0., image_offset_y = 0.;
1387 
1388 			eog_transform_get_affine (transform, &affine);
1389 			cairo_matrix_multiply (&matrix, &affine, &matrix);
1390 
1391 			switch (eog_transform_get_transform_type (transform)) {
1392 			case EOG_TRANSFORM_ROT_90:
1393 			case EOG_TRANSFORM_FLIP_HORIZONTAL:
1394 				image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf);
1395 				break;
1396 			case EOG_TRANSFORM_ROT_270:
1397 			case EOG_TRANSFORM_FLIP_VERTICAL:
1398 				image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf);
1399 				break;
1400 			case EOG_TRANSFORM_ROT_180:
1401 			case EOG_TRANSFORM_TRANSPOSE:
1402 			case EOG_TRANSFORM_TRANSVERSE:
1403 				image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf);
1404 				image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf);
1405 				break;
1406 			case EOG_TRANSFORM_NONE:
1407 			default:
1408 				break;
1409 			}
1410 			cairo_matrix_init_translate (&translate, image_offset_x, image_offset_y);
1411 			cairo_matrix_multiply (&matrix, &matrix, &translate);
1412 		}
1413 		cairo_matrix_init_scale (&scale, priv->zoom, priv->zoom);
1414 		cairo_matrix_multiply (&matrix, &matrix, &scale);
1415 		cairo_matrix_init_translate (&translate, xofs, yofs);
1416 		cairo_matrix_multiply (&matrix, &matrix, &translate);
1417 
1418 		cairo_get_matrix (cr, &original);
1419 		cairo_matrix_multiply (&matrix, &matrix, &original);
1420 		cairo_set_matrix (cr, &matrix);
1421 
1422 		rsvg_handle_render_cairo (eog_image_get_svg (priv->image), cr);
1423 
1424 	} else
1425 #endif /* HAVE_RSVG */
1426 	{
1427 		cairo_filter_t interp_type;
1428 
1429 		if(!DOUBLE_EQUAL(priv->zoom, 1.0) && priv->force_unfiltered)
1430 		{
1431 			interp_type = CAIRO_FILTER_NEAREST;
1432 			_set_hq_redraw_timeout(view);
1433 		}
1434 		else
1435 		{
1436 			if (is_zoomed_in (view))
1437 				interp_type = priv->interp_type_in;
1438 			else
1439 				interp_type = priv->interp_type_out;
1440 
1441 			_clear_hq_redraw_timeout (view);
1442 			priv->force_unfiltered = TRUE;
1443 		}
1444 		cairo_scale (cr, priv->zoom, priv->zoom);
1445 		cairo_set_source_surface (cr, priv->surface, xofs/priv->zoom, yofs/priv->zoom);
1446 		cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
1447 		if (is_zoomed_in (view) || is_zoomed_out (view))
1448 			cairo_pattern_set_filter (cairo_get_source (cr), interp_type);
1449 
1450 		cairo_paint (cr);
1451 	}
1452 
1453 	return TRUE;
1454 }
1455 
1456 static void
zoom_gesture_begin_cb(GtkGestureZoom * gesture,GdkEventSequence * sequence,EogScrollView * view)1457 zoom_gesture_begin_cb (GtkGestureZoom   *gesture,
1458                        GdkEventSequence *sequence,
1459                        EogScrollView    *view)
1460 {
1461 	gdouble center_x, center_y;
1462 	EogScrollViewPrivate *priv;
1463 
1464 	priv = view->priv;
1465 
1466 	/* Displace dragging point to gesture center */
1467 	gtk_gesture_get_bounding_box_center (GTK_GESTURE (gesture),
1468 	                                     &center_x, &center_y);
1469 	priv->drag_anchor_x = center_x;
1470 	priv->drag_anchor_y = center_y;
1471 	priv->drag_ofs_x = priv->xofs;
1472 	priv->drag_ofs_y = priv->yofs;
1473 	priv->dragging = TRUE;
1474 	priv->initial_zoom = priv->zoom;
1475 
1476 	gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
1477 }
1478 
1479 static void
zoom_gesture_update_cb(GtkGestureZoom * gesture,GdkEventSequence * sequence,EogScrollView * view)1480 zoom_gesture_update_cb (GtkGestureZoom   *gesture,
1481                         GdkEventSequence *sequence,
1482                         EogScrollView    *view)
1483 {
1484 	gdouble center_x, center_y, scale;
1485 	EogScrollViewPrivate *priv;
1486 
1487 	priv = view->priv;
1488 	scale = gtk_gesture_zoom_get_scale_delta (gesture);
1489 	gtk_gesture_get_bounding_box_center (GTK_GESTURE (gesture),
1490 	                                     &center_x, &center_y);
1491 
1492 	drag_to (view, center_x, center_y);
1493 	set_zoom (view, priv->initial_zoom * scale, TRUE,
1494 	          center_x, center_y);
1495 }
1496 
1497 static void
zoom_gesture_end_cb(GtkGestureZoom * gesture,GdkEventSequence * sequence,EogScrollView * view)1498 zoom_gesture_end_cb (GtkGestureZoom   *gesture,
1499                      GdkEventSequence *sequence,
1500                      EogScrollView    *view)
1501 {
1502 	EogScrollViewPrivate *priv;
1503 
1504 	priv = view->priv;
1505 	priv->dragging = FALSE;
1506 	eog_scroll_view_set_cursor (view, EOG_SCROLL_VIEW_CURSOR_NORMAL);
1507 }
1508 
1509 static void
rotate_gesture_begin_cb(GtkGesture * gesture,GdkEventSequence * sequence,EogScrollView * view)1510 rotate_gesture_begin_cb (GtkGesture       *gesture,
1511                          GdkEventSequence *sequence,
1512                          EogScrollView    *view)
1513 {
1514 	EogScrollViewPrivate *priv;
1515 
1516 	priv = view->priv;
1517 	priv->rotate_state = EOG_ROTATION_0;
1518 }
1519 
1520 static void
pan_gesture_pan_cb(GtkGesturePan * gesture,GtkPanDirection direction,gdouble offset,EogScrollView * view)1521 pan_gesture_pan_cb (GtkGesturePan   *gesture,
1522                     GtkPanDirection  direction,
1523                     gdouble          offset,
1524                     EogScrollView   *view)
1525 {
1526 	EogScrollViewPrivate *priv;
1527 	const gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (view)) == GTK_TEXT_DIR_RTL;
1528 
1529 	if (eog_scroll_view_scrollbars_visible (view)) {
1530 		gtk_gesture_set_state (GTK_GESTURE (gesture),
1531 		                       GTK_EVENT_SEQUENCE_DENIED);
1532 		return;
1533 	}
1534 
1535 #define PAN_ACTION_DISTANCE 200
1536 
1537 	priv = view->priv;
1538 	priv->pan_action = EOG_PAN_ACTION_NONE;
1539 	gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
1540 
1541 	if (offset > PAN_ACTION_DISTANCE) {
1542 		if (direction == GTK_PAN_DIRECTION_LEFT)
1543 			priv->pan_action = is_rtl ? EOG_PAN_ACTION_PREV
1544 			                          : EOG_PAN_ACTION_NEXT;
1545 		else
1546 			priv->pan_action = is_rtl ? EOG_PAN_ACTION_NEXT
1547 			                          : EOG_PAN_ACTION_PREV;
1548 
1549 	}
1550 #undef PAN_ACTION_DISTANCE
1551 }
1552 
1553 static void
pan_gesture_end_cb(GtkGesture * gesture,GdkEventSequence * sequence,EogScrollView * view)1554 pan_gesture_end_cb (GtkGesture       *gesture,
1555                     GdkEventSequence *sequence,
1556                     EogScrollView    *view)
1557 {
1558 	EogScrollViewPrivate *priv;
1559 
1560 	if (!gtk_gesture_handles_sequence (gesture, sequence))
1561 		return;
1562 
1563 	priv = view->priv;
1564 
1565 	if (priv->pan_action == EOG_PAN_ACTION_PREV)
1566 		g_signal_emit (view, view_signals [SIGNAL_PREVIOUS_IMAGE], 0);
1567 	else if (priv->pan_action == EOG_PAN_ACTION_NEXT)
1568 		g_signal_emit (view, view_signals [SIGNAL_NEXT_IMAGE], 0);
1569 
1570 	priv->pan_action = EOG_PAN_ACTION_NONE;
1571 }
1572 
1573 static gboolean
scroll_view_check_angle(gdouble angle,gdouble min,gdouble max,gdouble threshold)1574 scroll_view_check_angle (gdouble angle,
1575                          gdouble min,
1576                          gdouble max,
1577                          gdouble threshold)
1578 {
1579 	if (min < max) {
1580 		return (angle > min - threshold &&
1581 		        angle < max + threshold);
1582 	} else {
1583 		return (angle < max + threshold ||
1584 		        angle > min - threshold);
1585 	}
1586 }
1587 
1588 static EogRotationState
scroll_view_get_rotate_state(EogScrollView * view,gdouble delta)1589 scroll_view_get_rotate_state (EogScrollView *view,
1590                               gdouble        delta)
1591 {
1592 	EogScrollViewPrivate *priv;
1593 
1594 	priv = view->priv;
1595 
1596 #define THRESHOLD (G_PI / 16)
1597 	switch (priv->rotate_state) {
1598 	case EOG_ROTATION_0:
1599 		if (scroll_view_check_angle (delta, G_PI * 7 / 4,
1600 		                             G_PI / 4, THRESHOLD))
1601 			return priv->rotate_state;
1602 		break;
1603 	case EOG_ROTATION_90:
1604 		if (scroll_view_check_angle (delta, G_PI / 4,
1605 		                             G_PI * 3 / 4, THRESHOLD))
1606 			return priv->rotate_state;
1607 		break;
1608 	case EOG_ROTATION_180:
1609 		if (scroll_view_check_angle (delta, G_PI * 3 / 4,
1610 		                             G_PI * 5 / 4, THRESHOLD))
1611 			return priv->rotate_state;
1612 		break;
1613 	case EOG_ROTATION_270:
1614 		if (scroll_view_check_angle (delta, G_PI * 5 / 4,
1615 		                             G_PI * 7 / 4, THRESHOLD))
1616 			return priv->rotate_state;
1617 		break;
1618 	default:
1619 		g_assert_not_reached ();
1620 	}
1621 
1622 #undef THRESHOLD
1623 
1624 	if (scroll_view_check_angle (delta, G_PI / 4, G_PI * 3 / 4, 0))
1625 		return EOG_ROTATION_90;
1626 	else if (scroll_view_check_angle (delta, G_PI * 3 / 4, G_PI * 5 / 4, 0))
1627 		return EOG_ROTATION_180;
1628 	else if (scroll_view_check_angle (delta, G_PI * 5 / 4, G_PI * 7 / 4, 0))
1629 		return EOG_ROTATION_270;
1630 
1631 	return EOG_ROTATION_0;
1632 }
1633 
1634 static void
rotate_gesture_angle_changed_cb(GtkGestureRotate * rotate,gdouble angle,gdouble delta,EogScrollView * view)1635 rotate_gesture_angle_changed_cb (GtkGestureRotate *rotate,
1636                                  gdouble           angle,
1637                                  gdouble           delta,
1638                                  EogScrollView    *view)
1639 {
1640 	EogRotationState rotate_state;
1641 	EogScrollViewPrivate *priv;
1642 	gint angle_diffs [N_EOG_ROTATIONS][N_EOG_ROTATIONS] = {
1643 	        { 0,   90,  180, 270 },
1644 	        { 270, 0,   90,  180 },
1645 	        { 180, 270, 0,   90 },
1646 	        { 90,  180, 270, 0 }
1647 	};
1648 	gint rotate_angle;
1649 
1650 	priv = view->priv;
1651 	rotate_state = scroll_view_get_rotate_state (view, delta);
1652 
1653 	if (priv->rotate_state == rotate_state)
1654 		return;
1655 
1656 	rotate_angle = angle_diffs[priv->rotate_state][rotate_state];
1657 	g_signal_emit (view, view_signals [SIGNAL_ROTATION_CHANGED], 0, (gdouble) rotate_angle);
1658 	priv->rotate_state = rotate_state;
1659 }
1660 
1661 /*==================================
1662 
1663    image loading callbacks
1664 
1665    -----------------------------------*/
1666 
1667 /* Use when the pixbuf in the view is changed, to keep a
1668    reference to it and create its cairo surface. */
1669 static void
update_pixbuf(EogScrollView * view,GdkPixbuf * pixbuf)1670 update_pixbuf (EogScrollView *view, GdkPixbuf *pixbuf)
1671 {
1672 	EogScrollViewPrivate *priv;
1673 
1674 	priv = view->priv;
1675 
1676 	if (priv->pixbuf != NULL) {
1677 		g_object_unref (priv->pixbuf);
1678 		priv->pixbuf = NULL;
1679 	}
1680 
1681 	priv->pixbuf = pixbuf;
1682 
1683 	if (priv->surface) {
1684 		cairo_surface_destroy (priv->surface);
1685 	}
1686 	priv->surface = create_surface_from_pixbuf (view, priv->pixbuf);
1687 }
1688 
1689 static void
image_changed_cb(EogImage * img,gpointer data)1690 image_changed_cb (EogImage *img, gpointer data)
1691 {
1692 	update_pixbuf (EOG_SCROLL_VIEW (data), eog_image_get_pixbuf (img));
1693 
1694 	_set_zoom_mode_internal (EOG_SCROLL_VIEW (data),
1695 	                         EOG_ZOOM_MODE_SHRINK_TO_FIT);
1696 }
1697 
1698 /*===================================
1699 	 public API
1700   ---------------------------------*/
1701 
1702 void
eog_scroll_view_hide_cursor(EogScrollView * view)1703 eog_scroll_view_hide_cursor (EogScrollView *view)
1704 {
1705        eog_scroll_view_set_cursor (view, EOG_SCROLL_VIEW_CURSOR_HIDDEN);
1706 }
1707 
1708 void
eog_scroll_view_show_cursor(EogScrollView * view)1709 eog_scroll_view_show_cursor (EogScrollView *view)
1710 {
1711        eog_scroll_view_set_cursor (view, EOG_SCROLL_VIEW_CURSOR_NORMAL);
1712 }
1713 
1714 /* general properties */
1715 void
eog_scroll_view_set_zoom_upscale(EogScrollView * view,gboolean upscale)1716 eog_scroll_view_set_zoom_upscale (EogScrollView *view, gboolean upscale)
1717 {
1718 	EogScrollViewPrivate *priv;
1719 
1720 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1721 
1722 	priv = view->priv;
1723 
1724 	if (priv->upscale != upscale) {
1725 		priv->upscale = upscale;
1726 
1727 		if (priv->zoom_mode == EOG_ZOOM_MODE_SHRINK_TO_FIT) {
1728 			set_zoom_fit (view);
1729 			gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1730 		}
1731 	}
1732 }
1733 
1734 void
eog_scroll_view_set_antialiasing_in(EogScrollView * view,gboolean state)1735 eog_scroll_view_set_antialiasing_in (EogScrollView *view, gboolean state)
1736 {
1737 	EogScrollViewPrivate *priv;
1738 	cairo_filter_t new_interp_type;
1739 
1740 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1741 
1742 	priv = view->priv;
1743 
1744 	new_interp_type = state ? CAIRO_FILTER_GOOD : CAIRO_FILTER_NEAREST;
1745 
1746 	if (priv->interp_type_in != new_interp_type) {
1747 		priv->interp_type_in = new_interp_type;
1748 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1749 		g_object_notify (G_OBJECT (view), "antialiasing-in");
1750 	}
1751 }
1752 
1753 void
eog_scroll_view_set_antialiasing_out(EogScrollView * view,gboolean state)1754 eog_scroll_view_set_antialiasing_out (EogScrollView *view, gboolean state)
1755 {
1756 	EogScrollViewPrivate *priv;
1757 	cairo_filter_t new_interp_type;
1758 
1759 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1760 
1761 	priv = view->priv;
1762 
1763 	new_interp_type = state ? CAIRO_FILTER_GOOD : CAIRO_FILTER_NEAREST;
1764 
1765 	if (priv->interp_type_out != new_interp_type) {
1766 		priv->interp_type_out = new_interp_type;
1767 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1768 		g_object_notify (G_OBJECT (view), "antialiasing-out");
1769 	}
1770 }
1771 
1772 static void
_transp_background_changed(EogScrollView * view)1773 _transp_background_changed (EogScrollView *view)
1774 {
1775 	EogScrollViewPrivate *priv = view->priv;
1776 
1777 	if (priv->pixbuf != NULL && gdk_pixbuf_get_has_alpha (priv->pixbuf)) {
1778 		if (priv->background_surface) {
1779 			cairo_surface_destroy (priv->background_surface);
1780 			/* Will be recreated if needed during redraw */
1781 			priv->background_surface = NULL;
1782 		}
1783 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1784 	}
1785 
1786 }
1787 
1788 void
eog_scroll_view_set_transparency_color(EogScrollView * view,GdkRGBA * color)1789 eog_scroll_view_set_transparency_color (EogScrollView *view, GdkRGBA *color)
1790 {
1791 	EogScrollViewPrivate *priv;
1792 
1793 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1794 
1795 	priv = view->priv;
1796 
1797 	if (!_eog_gdk_rgba_equal0 (&priv->transp_color, color)) {
1798 		priv->transp_color = *color;
1799 		if (priv->transp_style == EOG_TRANSP_COLOR)
1800 			_transp_background_changed (view);
1801 
1802 		g_object_notify (G_OBJECT (view), "transparency-color");
1803 	}
1804 }
1805 
1806 void
eog_scroll_view_set_transparency(EogScrollView * view,EogTransparencyStyle style)1807 eog_scroll_view_set_transparency (EogScrollView        *view,
1808                                   EogTransparencyStyle  style)
1809 {
1810 	EogScrollViewPrivate *priv;
1811 
1812 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1813 
1814 	priv = view->priv;
1815 
1816 	if (priv->transp_style != style) {
1817 		priv->transp_style = style;
1818 		_transp_background_changed (view);
1819 		g_object_notify (G_OBJECT (view), "transparency-style");
1820 	}
1821 }
1822 
1823 /* zoom api */
1824 
1825 static double preferred_zoom_levels[] = {
1826         1.0 / 100, 1.0 / 50, 1.0 / 20,
1827         1.0 / 10.0, 1.0 / 5.0, 1.0 / 3.0, 1.0 / 2.0, 1.0 / 1.5,
1828         1.0, 1 / 0.75, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
1829         11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0
1830 };
1831 static const gint n_zoom_levels = (sizeof (preferred_zoom_levels) / sizeof (double));
1832 
1833 void
eog_scroll_view_zoom_in(EogScrollView * view,gboolean smooth)1834 eog_scroll_view_zoom_in (EogScrollView *view, gboolean smooth)
1835 {
1836 	EogScrollViewPrivate *priv;
1837 	double zoom;
1838 
1839 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1840 
1841 	priv = view->priv;
1842 
1843 	if (smooth) {
1844 		zoom = priv->zoom * priv->zoom_multiplier;
1845 	}
1846 	else {
1847 		int i;
1848 		int index = -1;
1849 
1850 		for (i = 0; i < n_zoom_levels; i++) {
1851 			if (preferred_zoom_levels [i] - priv->zoom
1852 			                > DOUBLE_EQUAL_MAX_DIFF) {
1853 				index = i;
1854 				break;
1855 			}
1856 		}
1857 
1858 		if (index == -1) {
1859 			zoom = priv->zoom;
1860 		}
1861 		else {
1862 			zoom = preferred_zoom_levels [i];
1863 		}
1864 	}
1865 	set_zoom (view, zoom, FALSE, 0, 0);
1866 
1867 }
1868 
1869 void
eog_scroll_view_zoom_out(EogScrollView * view,gboolean smooth)1870 eog_scroll_view_zoom_out (EogScrollView *view, gboolean smooth)
1871 {
1872 	EogScrollViewPrivate *priv;
1873 	double zoom;
1874 
1875 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1876 
1877 	priv = view->priv;
1878 
1879 	if (smooth) {
1880 		zoom = priv->zoom / priv->zoom_multiplier;
1881 	}
1882 	else {
1883 		int i;
1884 		int index = -1;
1885 
1886 		for (i = n_zoom_levels - 1; i >= 0; i--) {
1887 			if (priv->zoom - preferred_zoom_levels [i]
1888 			                > DOUBLE_EQUAL_MAX_DIFF) {
1889 				index = i;
1890 				break;
1891 			}
1892 		}
1893 		if (index == -1) {
1894 			zoom = priv->zoom;
1895 		}
1896 		else {
1897 			zoom = preferred_zoom_levels [i];
1898 		}
1899 	}
1900 	set_zoom (view, zoom, FALSE, 0, 0);
1901 }
1902 
1903 static void
eog_scroll_view_zoom_fit(EogScrollView * view)1904 eog_scroll_view_zoom_fit (EogScrollView *view)
1905 {
1906 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1907 
1908 	set_zoom_fit (view);
1909 	check_scrollbar_visibility (view, NULL);
1910 	gtk_widget_queue_draw (GTK_WIDGET (view->priv->display));
1911 }
1912 
1913 void
eog_scroll_view_set_zoom(EogScrollView * view,double zoom)1914 eog_scroll_view_set_zoom (EogScrollView *view, double zoom)
1915 {
1916 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1917 
1918 	set_zoom (view, zoom, FALSE, 0, 0);
1919 }
1920 
1921 double
eog_scroll_view_get_zoom(EogScrollView * view)1922 eog_scroll_view_get_zoom (EogScrollView *view)
1923 {
1924 	g_return_val_if_fail (EOG_IS_SCROLL_VIEW (view), 0.0);
1925 
1926 	return view->priv->zoom;
1927 }
1928 
1929 gboolean
eog_scroll_view_get_zoom_is_min(EogScrollView * view)1930 eog_scroll_view_get_zoom_is_min (EogScrollView *view)
1931 {
1932 	g_return_val_if_fail (EOG_IS_SCROLL_VIEW (view), FALSE);
1933 
1934 	set_minimum_zoom_factor (view);
1935 
1936 	return DOUBLE_EQUAL (view->priv->zoom, MIN_ZOOM_FACTOR) ||
1937 	       DOUBLE_EQUAL (view->priv->zoom, view->priv->min_zoom);
1938 }
1939 
1940 gboolean
eog_scroll_view_get_zoom_is_max(EogScrollView * view)1941 eog_scroll_view_get_zoom_is_max (EogScrollView *view)
1942 {
1943 	g_return_val_if_fail (EOG_IS_SCROLL_VIEW (view), FALSE);
1944 
1945 	return DOUBLE_EQUAL (view->priv->zoom, MAX_ZOOM_FACTOR);
1946 }
1947 
1948 static void
display_next_frame_cb(EogImage * image,gint delay,gpointer data)1949 display_next_frame_cb (EogImage *image, gint delay, gpointer data)
1950 {
1951 	EogScrollViewPrivate *priv;
1952 	EogScrollView *view;
1953 
1954 	if (!EOG_IS_SCROLL_VIEW (data))
1955 		return;
1956 
1957 	view = EOG_SCROLL_VIEW (data);
1958 	priv = view->priv;
1959 
1960 	update_pixbuf (view, eog_image_get_pixbuf (image));
1961 
1962 	gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1963 }
1964 
1965 void
eog_scroll_view_set_image(EogScrollView * view,EogImage * image)1966 eog_scroll_view_set_image (EogScrollView *view, EogImage *image)
1967 {
1968 	EogScrollViewPrivate *priv;
1969 
1970 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
1971 
1972 	priv = view->priv;
1973 
1974 	if (priv->image == image) {
1975 		return;
1976 	}
1977 
1978 	if (priv->image != NULL) {
1979 		free_image_resources (view);
1980 	}
1981 	g_assert (priv->image == NULL);
1982 	g_assert (priv->pixbuf == NULL);
1983 
1984 	/* priv->progressive_state = PROGRESSIVE_NONE; */
1985 	if (image != NULL) {
1986 		eog_image_data_ref (image);
1987 
1988 		if (priv->pixbuf == NULL) {
1989 			update_pixbuf (view, eog_image_get_pixbuf (image));
1990 			/* priv->progressive_state = PROGRESSIVE_NONE; */
1991 			_set_zoom_mode_internal (view,
1992 			                         EOG_ZOOM_MODE_SHRINK_TO_FIT);
1993 
1994 		}
1995 
1996 		priv->image_changed_id = g_signal_connect (image, "changed",
1997 		                                           (GCallback) image_changed_cb, view);
1998 		if (eog_image_is_animation (image) == TRUE ) {
1999 			eog_image_start_animation (image);
2000 			priv->frame_changed_id = g_signal_connect (image, "next-frame",
2001 			                                            (GCallback) display_next_frame_cb, view);
2002 		}
2003 	} else {
2004 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2005 	}
2006 
2007 	priv->image = image;
2008 
2009 	g_object_notify (G_OBJECT (view), "image");
2010 }
2011 
2012 /**
2013  * eog_scroll_view_get_image:
2014  * @view: An #EogScrollView.
2015  *
2016  * Gets the currently displayed #EogImage.
2017  *
2018  * Returns: (transfer full): An #EogImage.
2019  **/
2020 EogImage*
eog_scroll_view_get_image(EogScrollView * view)2021 eog_scroll_view_get_image (EogScrollView *view)
2022 {
2023 	EogImage *img;
2024 
2025 	g_return_val_if_fail (EOG_IS_SCROLL_VIEW (view), NULL);
2026 
2027 	img = view->priv->image;
2028 
2029 	if (img != NULL)
2030 		g_object_ref (img);
2031 
2032 	return img;
2033 }
2034 
2035 gboolean
eog_scroll_view_scrollbars_visible(EogScrollView * view)2036 eog_scroll_view_scrollbars_visible (EogScrollView *view)
2037 {
2038 	if (!gtk_widget_get_visible (GTK_WIDGET (view->priv->hbar)) &&
2039 	    !gtk_widget_get_visible (GTK_WIDGET (view->priv->vbar)))
2040 		return FALSE;
2041 
2042 	return TRUE;
2043 }
2044 
2045 /*===================================
2046     object creation/freeing
2047   ---------------------------------*/
2048 
2049 static gboolean
sv_string_to_rgba_mapping(GValue * value,GVariant * variant,gpointer user_data)2050 sv_string_to_rgba_mapping (GValue   *value,
2051                             GVariant *variant,
2052                             gpointer  user_data)
2053 {
2054 	GdkRGBA color;
2055 
2056 	g_return_val_if_fail (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING), FALSE);
2057 
2058 	if (gdk_rgba_parse (&color, g_variant_get_string (variant, NULL))) {
2059 		g_value_set_boxed (value, &color);
2060 		return TRUE;
2061 	}
2062 
2063 	return FALSE;
2064 }
2065 
2066 static GVariant*
sv_rgba_to_string_mapping(const GValue * value,const GVariantType * expected_type,gpointer user_data)2067 sv_rgba_to_string_mapping (const GValue       *value,
2068                             const GVariantType *expected_type,
2069                             gpointer            user_data)
2070 {
2071 	GVariant *variant = NULL;
2072 	GdkRGBA *color;
2073 	gchar *hex_val;
2074 
2075 	g_return_val_if_fail (G_VALUE_TYPE (value) == GDK_TYPE_RGBA, NULL);
2076 	g_return_val_if_fail (g_variant_type_equal (expected_type, G_VARIANT_TYPE_STRING), NULL);
2077 
2078 	color = g_value_get_boxed (value);
2079 	hex_val = gdk_rgba_to_string(color);
2080 	variant = g_variant_new_string (hex_val);
2081 	g_free (hex_val);
2082 
2083 	return variant;
2084 }
2085 
2086 static void
_clear_overlay_timeout(EogScrollView * view)2087 _clear_overlay_timeout (EogScrollView *view)
2088 {
2089 	EogScrollViewPrivate *priv = view->priv;
2090 
2091 	if (priv->overlay_timeout_source != NULL) {
2092 		g_source_unref (priv->overlay_timeout_source);
2093 		g_source_destroy (priv->overlay_timeout_source);
2094 	}
2095 
2096 	priv->overlay_timeout_source = NULL;
2097 }
2098 static gboolean
_overlay_timeout_cb(gpointer data)2099 _overlay_timeout_cb (gpointer data)
2100 {
2101 	EogScrollView *view = EOG_SCROLL_VIEW (data);
2102 	EogScrollViewPrivate *priv = view->priv;
2103 
2104 	gtk_revealer_set_reveal_child (GTK_REVEALER (priv->left_revealer), FALSE);
2105 	gtk_revealer_set_reveal_child (GTK_REVEALER (priv->right_revealer), FALSE);
2106 	gtk_revealer_set_reveal_child (GTK_REVEALER (priv->bottom_revealer), FALSE);
2107 
2108 	_clear_overlay_timeout (view);
2109 
2110 	return FALSE;
2111 }
2112 static void
_set_overlay_timeout(EogScrollView * view)2113 _set_overlay_timeout (EogScrollView *view)
2114 {
2115 	GSource *source;
2116 
2117 	_clear_overlay_timeout (view);
2118 
2119 	source = g_timeout_source_new (1000);
2120 	g_source_set_callback (source, _overlay_timeout_cb, view, NULL);
2121 
2122 	g_source_attach (source, NULL);
2123 
2124 	view->priv->overlay_timeout_source = source;
2125 }
2126 
2127 static gboolean
_enter_overlay_event_cb(GtkWidget * widget,GdkEvent * event,gpointer user_data)2128 _enter_overlay_event_cb (GtkWidget *widget,
2129                          GdkEvent *event,
2130                          gpointer user_data)
2131 {
2132 	EogScrollView *view = EOG_SCROLL_VIEW (user_data);
2133 
2134 	_clear_overlay_timeout (view);
2135 
2136 	return FALSE;
2137 }
2138 
2139 static gboolean
_motion_notify_cb(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)2140 _motion_notify_cb (GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
2141 {
2142 	EogScrollView *view = EOG_SCROLL_VIEW (user_data);
2143 	EogScrollViewPrivate *priv = view->priv;
2144 	gboolean reveal_child;
2145 
2146 	reveal_child = gtk_revealer_get_reveal_child (GTK_REVEALER (priv->left_revealer));
2147 
2148 	if (!reveal_child) {
2149 		gtk_revealer_set_reveal_child (GTK_REVEALER (priv->left_revealer), TRUE);
2150 		gtk_revealer_set_reveal_child (GTK_REVEALER (priv->right_revealer), TRUE);
2151 		gtk_revealer_set_reveal_child (GTK_REVEALER (priv->bottom_revealer), TRUE);
2152 	}
2153 
2154 	/* reset timeout */
2155 	_set_overlay_timeout(view);
2156 
2157 	return FALSE;
2158 }
2159 
2160 static void
eog_scroll_view_init(EogScrollView * view)2161 eog_scroll_view_init (EogScrollView *view)
2162 {
2163 	GSettings *settings;
2164 	EogScrollViewPrivate *priv;
2165 
2166 	priv = view->priv = eog_scroll_view_get_instance_private (view);
2167 	settings = g_settings_new (EOG_CONF_VIEW);
2168 
2169 	priv->zoom = 1.0;
2170 	priv->min_zoom = MIN_ZOOM_FACTOR;
2171 	priv->zoom_mode = EOG_ZOOM_MODE_SHRINK_TO_FIT;
2172 	priv->upscale = FALSE;
2173 	priv->interp_type_in = CAIRO_FILTER_GOOD;
2174 	priv->interp_type_out = CAIRO_FILTER_GOOD;
2175 	priv->scroll_wheel_zoom = FALSE;
2176 	priv->zoom_multiplier = IMAGE_VIEW_ZOOM_MULTIPLIER;
2177 	priv->image = NULL;
2178 	priv->pixbuf = NULL;
2179 	priv->surface = NULL;
2180 	/* priv->progressive_state = PROGRESSIVE_NONE; */
2181 	priv->transp_style = EOG_TRANSP_BACKGROUND;
2182 	g_warn_if_fail (gdk_rgba_parse(&priv->transp_color, CHECK_BLACK));
2183 	priv->cursor = EOG_SCROLL_VIEW_CURSOR_NORMAL;
2184 	priv->menu = NULL;
2185 	priv->override_bg_color = NULL;
2186 	priv->background_surface = NULL;
2187 
2188 	priv->hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100));
2189 	g_signal_connect (priv->hadj, "value_changed",
2190 	                  G_CALLBACK (adjustment_changed_cb),
2191 	                  view);
2192 
2193 	priv->hbar = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, priv->hadj);
2194 	priv->vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100));
2195 	g_signal_connect (priv->vadj, "value_changed",
2196 	                  G_CALLBACK (adjustment_changed_cb),
2197 	                  view);
2198 
2199 	priv->vbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, priv->vadj);
2200 
2201 	priv->overlay = gtk_overlay_new ();
2202 	gtk_grid_attach (GTK_GRID (view), priv->overlay, 0, 0, 1, 1);
2203 
2204 	priv->display = g_object_new (GTK_TYPE_DRAWING_AREA,
2205 	                              "can-focus", TRUE,
2206 	                              NULL);
2207 
2208 	gtk_widget_add_events (GTK_WIDGET (priv->display),
2209 	                       GDK_EXPOSURE_MASK
2210 	                       | GDK_TOUCHPAD_GESTURE_MASK
2211 	                       | GDK_BUTTON_PRESS_MASK
2212 	                       | GDK_BUTTON_RELEASE_MASK
2213 	                       | GDK_POINTER_MOTION_MASK
2214 	                       | GDK_POINTER_MOTION_HINT_MASK
2215 	                       | GDK_TOUCH_MASK
2216 	                       | GDK_SCROLL_MASK
2217 	                       | GDK_KEY_PRESS_MASK);
2218 	g_signal_connect (G_OBJECT (priv->display), "configure_event",
2219 	                  G_CALLBACK (display_size_change), view);
2220 	g_signal_connect (G_OBJECT (priv->display), "draw",
2221 	                  G_CALLBACK (display_draw), view);
2222 	g_signal_connect (G_OBJECT (priv->display), "map_event",
2223 	                  G_CALLBACK (display_map_event), view);
2224 	g_signal_connect (G_OBJECT (priv->display), "button_press_event",
2225 	                  G_CALLBACK (eog_scroll_view_button_press_event),
2226 	                  view);
2227 	g_signal_connect (G_OBJECT (priv->display), "motion_notify_event",
2228 	                  G_CALLBACK (eog_scroll_view_motion_event), view);
2229 	g_signal_connect (G_OBJECT (priv->display), "button_release_event",
2230 	                  G_CALLBACK (eog_scroll_view_button_release_event),
2231 	                  view);
2232 	g_signal_connect (G_OBJECT (priv->display), "scroll_event",
2233 	                  G_CALLBACK (eog_scroll_view_scroll_event), view);
2234 	g_signal_connect (G_OBJECT (priv->display), "focus_in_event",
2235 	                  G_CALLBACK (eog_scroll_view_focus_in_event), NULL);
2236 	g_signal_connect (G_OBJECT (priv->display), "focus_out_event",
2237 	                  G_CALLBACK (eog_scroll_view_focus_out_event), NULL);
2238 
2239 	g_signal_connect (G_OBJECT (view), "key_press_event",
2240 	                  G_CALLBACK (display_key_press_event), view);
2241 
2242 	gtk_drag_source_set (priv->display, GDK_BUTTON1_MASK,
2243 	                     target_table, G_N_ELEMENTS (target_table),
2244 	                     GDK_ACTION_COPY | GDK_ACTION_MOVE |
2245 	                     GDK_ACTION_LINK | GDK_ACTION_ASK);
2246 	g_signal_connect (G_OBJECT (priv->display), "drag-data-get",
2247 	                  G_CALLBACK (view_on_drag_data_get_cb), view);
2248 	g_signal_connect (G_OBJECT (priv->display), "drag-begin",
2249 	                  G_CALLBACK (view_on_drag_begin_cb), view);
2250 
2251 	gtk_container_add (GTK_CONTAINER (priv->overlay), priv->display);
2252 
2253 	gtk_widget_set_hexpand (priv->display, TRUE);
2254 	gtk_widget_set_vexpand (priv->display, TRUE);
2255 	gtk_grid_attach (GTK_GRID (view), priv->hbar,
2256 	                 0, 1, 1, 1);
2257 	gtk_widget_set_hexpand (priv->hbar, TRUE);
2258 	gtk_grid_attach (GTK_GRID (view), priv->vbar,
2259 	                 1, 0, 1, 1);
2260 	gtk_widget_set_vexpand (priv->vbar, TRUE);
2261 
2262 	g_settings_bind (settings, EOG_CONF_VIEW_USE_BG_COLOR, view,
2263 	                 "use-background-color", G_SETTINGS_BIND_DEFAULT);
2264 	g_settings_bind_with_mapping (settings, EOG_CONF_VIEW_BACKGROUND_COLOR,
2265 	                              view, "background-color",
2266 	                              G_SETTINGS_BIND_DEFAULT,
2267 	                              sv_string_to_rgba_mapping,
2268 	                              sv_rgba_to_string_mapping, NULL, NULL);
2269 	g_settings_bind_with_mapping (settings, EOG_CONF_VIEW_TRANS_COLOR,
2270 	                              view, "transparency-color",
2271 	                              G_SETTINGS_BIND_GET,
2272 	                              sv_string_to_rgba_mapping,
2273 	                              sv_rgba_to_string_mapping, NULL, NULL);
2274 	g_settings_bind (settings, EOG_CONF_VIEW_TRANSPARENCY, view,
2275 	                 "transparency-style", G_SETTINGS_BIND_GET);
2276 	g_settings_bind (settings, EOG_CONF_VIEW_EXTRAPOLATE, view,
2277 	                 "antialiasing-in", G_SETTINGS_BIND_GET);
2278 	g_settings_bind (settings, EOG_CONF_VIEW_INTERPOLATE, view,
2279 	                 "antialiasing-out", G_SETTINGS_BIND_GET);
2280 
2281 	g_object_unref (settings);
2282 
2283 	priv->zoom_gesture = gtk_gesture_zoom_new (GTK_WIDGET (view));
2284 	g_signal_connect (priv->zoom_gesture, "begin",
2285 	                  G_CALLBACK (zoom_gesture_begin_cb), view);
2286 	g_signal_connect (priv->zoom_gesture, "update",
2287 	                  G_CALLBACK (zoom_gesture_update_cb), view);
2288 	g_signal_connect (priv->zoom_gesture, "end",
2289 	                  G_CALLBACK (zoom_gesture_end_cb), view);
2290 	g_signal_connect (priv->zoom_gesture, "cancel",
2291 	                  G_CALLBACK (zoom_gesture_end_cb), view);
2292 	gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->zoom_gesture),
2293 	                                            GTK_PHASE_CAPTURE);
2294 
2295 	priv->rotate_gesture = gtk_gesture_rotate_new (GTK_WIDGET (view));
2296 	gtk_gesture_group (priv->rotate_gesture, priv->zoom_gesture);
2297 	g_signal_connect (priv->rotate_gesture, "angle-changed",
2298 	                  G_CALLBACK (rotate_gesture_angle_changed_cb), view);
2299 	g_signal_connect (priv->rotate_gesture, "begin",
2300 	                  G_CALLBACK (rotate_gesture_begin_cb), view);
2301 	gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->rotate_gesture),
2302 	                                            GTK_PHASE_CAPTURE);
2303 
2304 	priv->pan_gesture = gtk_gesture_pan_new (GTK_WIDGET (view),
2305 	                                         GTK_ORIENTATION_HORIZONTAL);
2306 	g_signal_connect (priv->pan_gesture, "pan",
2307 	                  G_CALLBACK (pan_gesture_pan_cb), view);
2308 	g_signal_connect (priv->pan_gesture, "end",
2309 	                  G_CALLBACK (pan_gesture_end_cb), view);
2310 	gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->pan_gesture),
2311 	                                   TRUE);
2312 	gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->pan_gesture),
2313 	                                            GTK_PHASE_CAPTURE);
2314 
2315 	/* left revealer */
2316 	priv->left_revealer = gtk_revealer_new ();
2317 	gtk_revealer_set_transition_type (GTK_REVEALER (priv->left_revealer),
2318 	                                  GTK_REVEALER_TRANSITION_TYPE_CROSSFADE);
2319 	gtk_revealer_set_transition_duration (GTK_REVEALER (priv->left_revealer),
2320 	                                      OVERLAY_REVEAL_ANIM_TIME);
2321 	gtk_widget_set_halign (priv->left_revealer, GTK_ALIGN_START);
2322 	gtk_widget_set_valign (priv->left_revealer, GTK_ALIGN_CENTER);
2323 	gtk_widget_set_margin_start(priv->left_revealer, 12);
2324 	gtk_widget_set_margin_end(priv->left_revealer, 12);
2325 	gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay),
2326 	                         priv->left_revealer);
2327 
2328 	/* right revealer */
2329 	priv->right_revealer = gtk_revealer_new ();
2330 	gtk_revealer_set_transition_type (GTK_REVEALER (priv->right_revealer),
2331 	                                  GTK_REVEALER_TRANSITION_TYPE_CROSSFADE);
2332 	gtk_revealer_set_transition_duration (GTK_REVEALER (priv->right_revealer),
2333 	                                      OVERLAY_REVEAL_ANIM_TIME);
2334 	gtk_widget_set_halign (priv->right_revealer, GTK_ALIGN_END);
2335 	gtk_widget_set_valign (priv->right_revealer, GTK_ALIGN_CENTER);
2336 	gtk_widget_set_margin_start (priv->right_revealer, 12);
2337 	gtk_widget_set_margin_end (priv->right_revealer, 12);
2338 	gtk_overlay_add_overlay(GTK_OVERLAY (priv->overlay),
2339 	                        priv->right_revealer);
2340 
2341 	/* bottom revealer */
2342 	priv->bottom_revealer = gtk_revealer_new ();
2343 	gtk_revealer_set_transition_type (GTK_REVEALER (priv->bottom_revealer),
2344 	                                  GTK_REVEALER_TRANSITION_TYPE_CROSSFADE);
2345 	gtk_revealer_set_transition_duration (GTK_REVEALER (priv->bottom_revealer),
2346 	                                      OVERLAY_REVEAL_ANIM_TIME);
2347 	gtk_widget_set_halign (priv->bottom_revealer, GTK_ALIGN_CENTER);
2348 	gtk_widget_set_valign (priv->bottom_revealer, GTK_ALIGN_END);
2349 	gtk_widget_set_margin_bottom (priv->bottom_revealer, 12);
2350 	gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay),
2351 	                         priv->bottom_revealer);
2352 
2353 	/* overlaid buttons */
2354 	GtkWidget *button = gtk_button_new_from_icon_name ("go-next-symbolic",
2355 	                                                   GTK_ICON_SIZE_BUTTON);
2356 
2357 	gtk_container_add(GTK_CONTAINER (priv->right_revealer), button);
2358 	gtk_actionable_set_action_name(GTK_ACTIONABLE (button), "win.go-next");
2359 	gtk_widget_set_tooltip_text (button,
2360 	                             _("Go to the next image of the gallery"));
2361 	gtk_style_context_add_class (gtk_widget_get_style_context (button),
2362 	                             GTK_STYLE_CLASS_OSD);
2363 
2364 
2365 	button = gtk_button_new_from_icon_name("go-previous-symbolic",
2366 	                                       GTK_ICON_SIZE_BUTTON);
2367 
2368 	gtk_container_add(GTK_CONTAINER (priv->left_revealer), button);
2369 	gtk_actionable_set_action_name (GTK_ACTIONABLE(button),
2370 	                                "win.go-previous");
2371 	gtk_widget_set_tooltip_text (button,
2372 	                             _("Go to the previous image of the gallery"));
2373 	gtk_style_context_add_class (gtk_widget_get_style_context (button),
2374 	                             GTK_STYLE_CLASS_OSD);
2375 
2376 
2377 	/* group rotate buttons into a box */
2378 	GtkWidget* bottomBox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2379 	gtk_style_context_add_class (gtk_widget_get_style_context (bottomBox),
2380 	                             GTK_STYLE_CLASS_LINKED);
2381 
2382 	button = gtk_button_new_from_icon_name ("object-rotate-left-symbolic",
2383 	                                        GTK_ICON_SIZE_BUTTON);
2384 	gtk_actionable_set_action_name (GTK_ACTIONABLE (button),
2385 	                                "win.rotate-270");
2386 	gtk_widget_set_tooltip_text (button,
2387 	                             _("Rotate the image 90 degrees to the left"));
2388 	gtk_style_context_add_class (gtk_widget_get_style_context (button),
2389 	                             GTK_STYLE_CLASS_OSD);
2390 
2391 	gtk_container_add (GTK_CONTAINER (bottomBox), button);
2392 
2393 	button = gtk_button_new_from_icon_name ("object-rotate-right-symbolic",
2394 	                                        GTK_ICON_SIZE_BUTTON);
2395 	gtk_actionable_set_action_name (GTK_ACTIONABLE (button),
2396 	                                "win.rotate-90");
2397 	gtk_widget_set_tooltip_text (button,
2398 	                             _("Rotate the image 90 degrees to the right"));
2399 	gtk_style_context_add_class (gtk_widget_get_style_context (button),
2400 	                             GTK_STYLE_CLASS_OSD);
2401 	gtk_container_add (GTK_CONTAINER (bottomBox), button);
2402 
2403 	gtk_container_add (GTK_CONTAINER (priv->bottom_revealer), bottomBox);
2404 
2405 	/* Display overlay buttons on mouse movement */
2406 	g_signal_connect (priv->display,
2407 	                  "motion-notify-event",
2408 	                  G_CALLBACK (_motion_notify_cb),
2409 	                  view);
2410 
2411 	/* Don't hide overlay buttons when above */
2412 	gtk_widget_add_events (GTK_WIDGET (priv->overlay),
2413 	                       GDK_ENTER_NOTIFY_MASK);
2414 	g_signal_connect (priv->overlay,
2415 	                  "enter-notify-event",
2416 	                  G_CALLBACK (_enter_overlay_event_cb),
2417 	                  view);
2418 }
2419 
2420 static void
eog_scroll_view_dispose(GObject * object)2421 eog_scroll_view_dispose (GObject *object)
2422 {
2423 	EogScrollView *view;
2424 	EogScrollViewPrivate *priv;
2425 
2426 	g_return_if_fail (EOG_IS_SCROLL_VIEW (object));
2427 
2428 	view = EOG_SCROLL_VIEW (object);
2429 	priv = view->priv;
2430 
2431 	_clear_overlay_timeout (view);
2432 	_clear_hq_redraw_timeout (view);
2433 
2434 	if (priv->idle_id != 0) {
2435 		g_source_remove (priv->idle_id);
2436 		priv->idle_id = 0;
2437 	}
2438 
2439 	if (priv->background_color != NULL) {
2440 		gdk_rgba_free (priv->background_color);
2441 		priv->background_color = NULL;
2442 	}
2443 
2444 	if (priv->override_bg_color != NULL) {
2445 		gdk_rgba_free (priv->override_bg_color);
2446 		priv->override_bg_color = NULL;
2447 	}
2448 
2449 	if (priv->background_surface != NULL) {
2450 		cairo_surface_destroy (priv->background_surface);
2451 		priv->background_surface = NULL;
2452 	}
2453 
2454 	free_image_resources (view);
2455 
2456 	if (priv->zoom_gesture) {
2457 		g_object_unref (priv->zoom_gesture);
2458 		priv->zoom_gesture = NULL;
2459 	}
2460 
2461 	if (priv->rotate_gesture) {
2462 		g_object_unref (priv->rotate_gesture);
2463 		priv->rotate_gesture = NULL;
2464 	}
2465 
2466 	if (priv->pan_gesture) {
2467 		g_object_unref (priv->pan_gesture);
2468 		priv->pan_gesture = NULL;
2469 	}
2470 
2471 	G_OBJECT_CLASS (eog_scroll_view_parent_class)->dispose (object);
2472 }
2473 
2474 static void
eog_scroll_view_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)2475 eog_scroll_view_get_property (GObject *object, guint property_id,
2476                               GValue *value, GParamSpec *pspec)
2477 {
2478 	EogScrollView *view;
2479 	EogScrollViewPrivate *priv;
2480 
2481 	g_return_if_fail (EOG_IS_SCROLL_VIEW (object));
2482 
2483 	view = EOG_SCROLL_VIEW (object);
2484 	priv = view->priv;
2485 
2486 	switch (property_id) {
2487 	case PROP_ANTIALIAS_IN:
2488 	{
2489 		gboolean filter = (priv->interp_type_in != CAIRO_FILTER_NEAREST);
2490 		g_value_set_boolean (value, filter);
2491 		break;
2492 	}
2493 	case PROP_ANTIALIAS_OUT:
2494 	{
2495 		gboolean filter = (priv->interp_type_out != CAIRO_FILTER_NEAREST);
2496 		g_value_set_boolean (value, filter);
2497 		break;
2498 	}
2499 	case PROP_USE_BG_COLOR:
2500 		g_value_set_boolean (value, priv->use_bg_color);
2501 		break;
2502 	case PROP_BACKGROUND_COLOR:
2503 		//FIXME: This doesn't really handle the NULL color.
2504 		g_value_set_boxed (value, priv->background_color);
2505 		break;
2506 	case PROP_SCROLLWHEEL_ZOOM:
2507 		g_value_set_boolean (value, priv->scroll_wheel_zoom);
2508 		break;
2509 	case PROP_TRANSPARENCY_STYLE:
2510 		g_value_set_enum (value, priv->transp_style);
2511 		break;
2512 	case PROP_ZOOM_MODE:
2513 		g_value_set_enum (value, priv->zoom_mode);
2514 		break;
2515 	case PROP_ZOOM_MULTIPLIER:
2516 		g_value_set_double (value, priv->zoom_multiplier);
2517 		break;
2518 	case PROP_IMAGE:
2519 		g_value_set_object (value, priv->image);
2520 		break;
2521 	default:
2522 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2523 	}
2524 }
2525 
2526 static void
eog_scroll_view_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)2527 eog_scroll_view_set_property (GObject *object, guint property_id,
2528                               const GValue *value, GParamSpec *pspec)
2529 {
2530 	EogScrollView *view;
2531 
2532 	g_return_if_fail (EOG_IS_SCROLL_VIEW (object));
2533 
2534 	view = EOG_SCROLL_VIEW (object);
2535 
2536 	switch (property_id) {
2537 	case PROP_ANTIALIAS_IN:
2538 		eog_scroll_view_set_antialiasing_in (view, g_value_get_boolean (value));
2539 		break;
2540 	case PROP_ANTIALIAS_OUT:
2541 		eog_scroll_view_set_antialiasing_out (view, g_value_get_boolean (value));
2542 		break;
2543 	case PROP_USE_BG_COLOR:
2544 		eog_scroll_view_set_use_bg_color (view, g_value_get_boolean (value));
2545 		break;
2546 	case PROP_BACKGROUND_COLOR:
2547 	{
2548 		const GdkRGBA *color = g_value_get_boxed (value);
2549 		eog_scroll_view_set_background_color (view, color);
2550 		break;
2551 	}
2552 	case PROP_SCROLLWHEEL_ZOOM:
2553 		eog_scroll_view_set_scroll_wheel_zoom (view, g_value_get_boolean (value));
2554 		break;
2555 	case PROP_TRANSP_COLOR:
2556 		eog_scroll_view_set_transparency_color (view, g_value_get_boxed (value));
2557 		break;
2558 	case PROP_TRANSPARENCY_STYLE:
2559 		eog_scroll_view_set_transparency (view, g_value_get_enum (value));
2560 		break;
2561 	case PROP_ZOOM_MODE:
2562 		eog_scroll_view_set_zoom_mode (view, g_value_get_enum (value));
2563 		break;
2564 	case PROP_ZOOM_MULTIPLIER:
2565 		eog_scroll_view_set_zoom_multiplier (view, g_value_get_double (value));
2566 		break;
2567 	case PROP_IMAGE:
2568 		eog_scroll_view_set_image (view, g_value_get_object (value));
2569 		break;
2570 	default:
2571 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2572 	}
2573 }
2574 
2575 
2576 static void
eog_scroll_view_class_init(EogScrollViewClass * klass)2577 eog_scroll_view_class_init (EogScrollViewClass *klass)
2578 {
2579 	GObjectClass *gobject_class;
2580 	GtkWidgetClass *widget_class;
2581 
2582 	gobject_class = (GObjectClass*) klass;
2583 	widget_class = (GtkWidgetClass*) klass;
2584 
2585 	gobject_class->dispose = eog_scroll_view_dispose;
2586 	gobject_class->set_property = eog_scroll_view_set_property;
2587 	gobject_class->get_property = eog_scroll_view_get_property;
2588 
2589 	/**
2590 	 * EogScrollView:antialiasing-in:
2591 	 *
2592 	 * If %TRUE the displayed image will be filtered in a second pass
2593 	 * while being zoomed in.
2594 	 */
2595 	g_object_class_install_property (
2596 	        gobject_class, PROP_ANTIALIAS_IN,
2597 	        g_param_spec_boolean ("antialiasing-in", NULL, NULL, TRUE,
2598 	                              G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2599 	/**
2600 	 * EogScrollView:antialiasing-out:
2601 	 *
2602 	 * If %TRUE the displayed image will be filtered in a second pass
2603 	 * while being zoomed out.
2604 	 */
2605 	g_object_class_install_property (
2606 	        gobject_class, PROP_ANTIALIAS_OUT,
2607 	        g_param_spec_boolean ("antialiasing-out", NULL, NULL, TRUE,
2608 	                              G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2609 
2610 	/**
2611 	 * EogScrollView:background-color:
2612 	 *
2613 	 * This is the default background color used for painting the background
2614 	 * of the image view. If set to %NULL the color is determined by the
2615 	 * active GTK theme.
2616 	 */
2617 	g_object_class_install_property (
2618 	        gobject_class, PROP_BACKGROUND_COLOR,
2619 	        g_param_spec_boxed ("background-color", NULL, NULL,
2620 	                            GDK_TYPE_RGBA,
2621 	                            G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2622 
2623 	g_object_class_install_property (
2624 	        gobject_class, PROP_USE_BG_COLOR,
2625 	        g_param_spec_boolean ("use-background-color", NULL, NULL, FALSE,
2626 	                              G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2627 
2628 	/**
2629 	 * EogScrollView:zoom-multiplier:
2630 	 *
2631 	 * The current zoom factor is multiplied with this value + 1.0 when
2632 	 * scrolling with the scrollwheel to determine the next zoom factor.
2633 	 */
2634 	g_object_class_install_property (
2635 	        gobject_class, PROP_ZOOM_MULTIPLIER,
2636 	        g_param_spec_double ("zoom-multiplier", NULL, NULL,
2637 	                             -G_MAXDOUBLE, G_MAXDOUBLE - 1.0, 0.05,
2638 	                             G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2639 
2640 	/**
2641 	 * EogScrollView:scrollwheel-zoom:
2642 	 *
2643 	 * If %TRUE the scrollwheel will zoom the view, otherwise it will be
2644 	 * used for scrolling a zoomed image.
2645 	 */
2646 	g_object_class_install_property (
2647 	        gobject_class, PROP_SCROLLWHEEL_ZOOM,
2648 	        g_param_spec_boolean ("scrollwheel-zoom", NULL, NULL, TRUE,
2649 	                              G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2650 
2651 	/**
2652 	 * EogScrollView:image:
2653 	 *
2654 	 * This is the currently display #EogImage.
2655 	 */
2656 	g_object_class_install_property (
2657 	        gobject_class, PROP_IMAGE,
2658 	        g_param_spec_object ("image", NULL, NULL, EOG_TYPE_IMAGE,
2659 	                             G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2660 
2661 	/**
2662 	 * EogScrollView:transparency-color:
2663 	 *
2664 	 * This is the color used to fill the transparent parts of an image
2665 	 * if #EogScrollView:transparency-style is set to %EOG_TRANSP_COLOR.
2666 	 */
2667 	g_object_class_install_property (
2668 	        gobject_class, PROP_TRANSP_COLOR,
2669 	        g_param_spec_boxed ("transparency-color", NULL, NULL,
2670 	                            GDK_TYPE_RGBA,
2671 	                            G_PARAM_WRITABLE | G_PARAM_STATIC_NAME));
2672 
2673 	/**
2674 	 * EogScrollView:transparency-style:
2675 	 *
2676 	 * Determines how to fill the shown image's transparent areas.
2677 	 */
2678 	g_object_class_install_property (
2679 	        gobject_class, PROP_TRANSPARENCY_STYLE,
2680 	        g_param_spec_enum ("transparency-style", NULL, NULL,
2681 	                           EOG_TYPE_TRANSPARENCY_STYLE,
2682 	                           EOG_TRANSP_CHECKED,
2683 	                           G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2684 
2685 	g_object_class_install_property (
2686 	        gobject_class, PROP_ZOOM_MODE,
2687 	        g_param_spec_enum ("zoom-mode", NULL, NULL,
2688 	                           EOG_TYPE_ZOOM_MODE,
2689 	                           EOG_ZOOM_MODE_SHRINK_TO_FIT,
2690 	                           G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
2691 
2692 	view_signals [SIGNAL_ZOOM_CHANGED] =
2693 	        g_signal_new ("zoom_changed",
2694 	                      EOG_TYPE_SCROLL_VIEW,
2695 	                      G_SIGNAL_RUN_LAST,
2696 	                      G_STRUCT_OFFSET (EogScrollViewClass, zoom_changed),
2697 	                      NULL, NULL,
2698 	                      g_cclosure_marshal_VOID__DOUBLE,
2699 	                      G_TYPE_NONE, 1,
2700 	                      G_TYPE_DOUBLE);
2701 	view_signals [SIGNAL_ROTATION_CHANGED] =
2702 	        g_signal_new ("rotation-changed",
2703 	                      EOG_TYPE_SCROLL_VIEW,
2704 	                      G_SIGNAL_RUN_LAST,
2705 	                      G_STRUCT_OFFSET (EogScrollViewClass, rotation_changed),
2706 	                      NULL, NULL,
2707 	                      g_cclosure_marshal_VOID__DOUBLE,
2708 	                      G_TYPE_NONE, 1,
2709 	                      G_TYPE_DOUBLE);
2710 
2711 	view_signals [SIGNAL_NEXT_IMAGE] =
2712 	        g_signal_new ("next-image",
2713 	                      EOG_TYPE_SCROLL_VIEW,
2714 	                      G_SIGNAL_RUN_LAST,
2715 	                      G_STRUCT_OFFSET (EogScrollViewClass, next_image),
2716 	                      NULL, NULL,
2717 	                      g_cclosure_marshal_VOID__VOID,
2718 	                      G_TYPE_NONE, 0);
2719 	view_signals [SIGNAL_PREVIOUS_IMAGE] =
2720 	        g_signal_new ("previous-image",
2721 	                      EOG_TYPE_SCROLL_VIEW,
2722 	                      G_SIGNAL_RUN_LAST,
2723 	                      G_STRUCT_OFFSET (EogScrollViewClass, previous_image),
2724 	                      NULL, NULL,
2725 	                      g_cclosure_marshal_VOID__VOID,
2726 	                      G_TYPE_NONE, 0);
2727 
2728 	widget_class->size_allocate = eog_scroll_view_size_allocate;
2729 }
2730 
2731 static void
view_on_drag_begin_cb(GtkWidget * widget,GdkDragContext * context,gpointer user_data)2732 view_on_drag_begin_cb (GtkWidget        *widget,
2733                        GdkDragContext   *context,
2734                        gpointer          user_data)
2735 {
2736 	EogScrollView *view;
2737 	EogImage *image;
2738 	GdkPixbuf *thumbnail;
2739 	gint width, height;
2740 
2741 	view = EOG_SCROLL_VIEW (user_data);
2742 	image = view->priv->image;
2743 
2744 	if (!image)
2745 		return;
2746 
2747 	thumbnail = eog_image_get_thumbnail (image);
2748 
2749 	if  (thumbnail) {
2750 		width = gdk_pixbuf_get_width (thumbnail);
2751 		height = gdk_pixbuf_get_height (thumbnail);
2752 		gtk_drag_set_icon_pixbuf (context, thumbnail, width/2, height/2);
2753 		g_object_unref (thumbnail);
2754 	}
2755 }
2756 
2757 static void
view_on_drag_data_get_cb(GtkWidget * widget,GdkDragContext * drag_context,GtkSelectionData * data,guint info,guint time,gpointer user_data)2758 view_on_drag_data_get_cb (GtkWidget        *widget,
2759                           GdkDragContext   *drag_context,
2760                           GtkSelectionData *data,
2761                           guint             info,
2762                           guint             time,
2763                           gpointer          user_data)
2764 {
2765 	EogScrollView *view;
2766 	EogImage *image;
2767 	gchar *uris[2];
2768 	GFile *file;
2769 
2770 	view = EOG_SCROLL_VIEW (user_data);
2771 
2772 	image = view->priv->image;
2773 
2774 	if (!image)
2775 		return;
2776 
2777 	file = eog_image_get_file (image);
2778 	uris[0] = g_file_get_uri (file);
2779 	uris[1] = NULL;
2780 
2781 	gtk_selection_data_set_uris (data, uris);
2782 
2783 	g_free (uris[0]);
2784 	g_object_unref (file);
2785 }
2786 
2787 GtkWidget*
eog_scroll_view_new(void)2788 eog_scroll_view_new (void)
2789 {
2790 	GtkWidget *widget;
2791 
2792 	widget = g_object_new (EOG_TYPE_SCROLL_VIEW,
2793 	                       "can-focus", TRUE,
2794 	                       "row-homogeneous", FALSE,
2795 	                       "column-homogeneous", FALSE,
2796 	                       NULL);
2797 
2798 	return widget;
2799 }
2800 
2801 static void
eog_scroll_view_popup_menu(EogScrollView * view,GdkEventButton * event)2802 eog_scroll_view_popup_menu (EogScrollView *view, GdkEventButton *event)
2803 {
2804 	gtk_menu_popup_at_pointer (GTK_MENU (view->priv->menu),
2805 	                           (const GdkEvent*) event);
2806 }
2807 
2808 static gboolean
view_on_button_press_event_cb(GtkWidget * view,GdkEventButton * event,gpointer user_data)2809 view_on_button_press_event_cb (GtkWidget *view, GdkEventButton *event,
2810                                gpointer user_data)
2811 {
2812 	/* Ignore double-clicks and triple-clicks */
2813 	if (gdk_event_triggers_context_menu ((const GdkEvent*) event)
2814 	    && event->type == GDK_BUTTON_PRESS)
2815 	{
2816 		eog_scroll_view_popup_menu (EOG_SCROLL_VIEW (view), event);
2817 
2818 		return TRUE;
2819 	}
2820 
2821 	return FALSE;
2822 }
2823 
2824 static gboolean
eog_scroll_view_popup_menu_handler(GtkWidget * widget,gpointer user_data)2825 eog_scroll_view_popup_menu_handler (GtkWidget *widget, gpointer user_data)
2826 {
2827 	eog_scroll_view_popup_menu (EOG_SCROLL_VIEW (widget), NULL);
2828 	return TRUE;
2829 }
2830 
2831 void
eog_scroll_view_set_popup(EogScrollView * view,GtkMenu * menu)2832 eog_scroll_view_set_popup (EogScrollView *view,
2833                            GtkMenu *menu)
2834 {
2835 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
2836 	g_return_if_fail (view->priv->menu == NULL);
2837 
2838 	view->priv->menu = g_object_ref (GTK_WIDGET (menu));
2839 
2840 	gtk_menu_attach_to_widget (GTK_MENU (view->priv->menu),
2841 	                           GTK_WIDGET (view),
2842 	                           NULL);
2843 
2844 	g_signal_connect (G_OBJECT (view), "button_press_event",
2845 	                  G_CALLBACK (view_on_button_press_event_cb), NULL);
2846 	g_signal_connect (G_OBJECT (view), "popup-menu",
2847 	                  G_CALLBACK (eog_scroll_view_popup_menu_handler), NULL);
2848 }
2849 
2850 static gboolean
_eog_gdk_rgba_equal0(const GdkRGBA * a,const GdkRGBA * b)2851 _eog_gdk_rgba_equal0 (const GdkRGBA *a, const GdkRGBA *b)
2852 {
2853 	if (a == NULL || b == NULL)
2854 		return (a == b);
2855 
2856 	return gdk_rgba_equal (a, b);
2857 }
2858 
2859 static gboolean
_eog_replace_gdk_rgba(GdkRGBA ** dest,const GdkRGBA * src)2860 _eog_replace_gdk_rgba (GdkRGBA **dest, const GdkRGBA *src)
2861 {
2862 	GdkRGBA *old = *dest;
2863 
2864 	if (_eog_gdk_rgba_equal0 (old, src))
2865 		return FALSE;
2866 
2867 	if (old != NULL)
2868 		gdk_rgba_free (old);
2869 
2870 	*dest = (src) ? gdk_rgba_copy (src) : NULL;
2871 
2872 	return TRUE;
2873 }
2874 
2875 static void
_eog_scroll_view_update_bg_color(EogScrollView * view)2876 _eog_scroll_view_update_bg_color (EogScrollView *view)
2877 {
2878 	EogScrollViewPrivate *priv = view->priv;
2879 
2880 	if (priv->transp_style == EOG_TRANSP_BACKGROUND
2881 	    && priv->background_surface != NULL) {
2882 		/* Delete the SVG background to have it recreated with
2883 		 * the correct color during the next SVG redraw */
2884 		cairo_surface_destroy (priv->background_surface);
2885 		priv->background_surface = NULL;
2886 	}
2887 
2888 	gtk_widget_queue_draw (priv->display);
2889 }
2890 
2891 void
eog_scroll_view_set_background_color(EogScrollView * view,const GdkRGBA * color)2892 eog_scroll_view_set_background_color (EogScrollView *view,
2893                                       const GdkRGBA *color)
2894 {
2895 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
2896 
2897 	if (_eog_replace_gdk_rgba (&view->priv->background_color, color))
2898 		_eog_scroll_view_update_bg_color (view);
2899 }
2900 
2901 void
eog_scroll_view_override_bg_color(EogScrollView * view,const GdkRGBA * color)2902 eog_scroll_view_override_bg_color (EogScrollView *view,
2903                                    const GdkRGBA *color)
2904 {
2905 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
2906 
2907 	if (_eog_replace_gdk_rgba (&view->priv->override_bg_color, color))
2908 		_eog_scroll_view_update_bg_color (view);
2909 }
2910 
2911 void
eog_scroll_view_set_use_bg_color(EogScrollView * view,gboolean use)2912 eog_scroll_view_set_use_bg_color (EogScrollView *view, gboolean use)
2913 {
2914 	EogScrollViewPrivate *priv;
2915 
2916 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
2917 
2918 	priv = view->priv;
2919 
2920 	if (use != priv->use_bg_color) {
2921 		priv->use_bg_color = use;
2922 
2923 		_eog_scroll_view_update_bg_color (view);
2924 
2925 		g_object_notify (G_OBJECT (view), "use-background-color");
2926 	}
2927 }
2928 
2929 void
eog_scroll_view_set_scroll_wheel_zoom(EogScrollView * view,gboolean scroll_wheel_zoom)2930 eog_scroll_view_set_scroll_wheel_zoom (EogScrollView *view,
2931                                        gboolean       scroll_wheel_zoom)
2932 {
2933 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
2934 
2935 	if (view->priv->scroll_wheel_zoom != scroll_wheel_zoom) {
2936 		view->priv->scroll_wheel_zoom = scroll_wheel_zoom;
2937 		g_object_notify (G_OBJECT (view), "scrollwheel-zoom");
2938 	}
2939 }
2940 
2941 void
eog_scroll_view_set_zoom_multiplier(EogScrollView * view,gdouble zoom_multiplier)2942 eog_scroll_view_set_zoom_multiplier (EogScrollView *view,
2943                                      gdouble        zoom_multiplier)
2944 {
2945 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
2946 
2947 	view->priv->zoom_multiplier = 1.0 + zoom_multiplier;
2948 
2949 	g_object_notify (G_OBJECT (view), "zoom-multiplier");
2950 }
2951 
2952 /* Helper to cause a redraw even if the zoom mode is unchanged */
2953 static void
_set_zoom_mode_internal(EogScrollView * view,EogZoomMode mode)2954 _set_zoom_mode_internal (EogScrollView *view, EogZoomMode mode)
2955 {
2956 	gboolean notify = (mode != view->priv->zoom_mode);
2957 
2958 
2959 	if (mode == EOG_ZOOM_MODE_SHRINK_TO_FIT)
2960 		eog_scroll_view_zoom_fit (view);
2961 	else
2962 		view->priv->zoom_mode = mode;
2963 
2964 	if (notify)
2965 		g_object_notify (G_OBJECT (view), "zoom-mode");
2966 }
2967 
2968 
2969 void
eog_scroll_view_set_zoom_mode(EogScrollView * view,EogZoomMode mode)2970 eog_scroll_view_set_zoom_mode (EogScrollView *view, EogZoomMode mode)
2971 {
2972 	g_return_if_fail (EOG_IS_SCROLL_VIEW (view));
2973 
2974 	if (view->priv->zoom_mode == mode)
2975 		return;
2976 
2977 	_set_zoom_mode_internal (view, mode);
2978 }
2979 
2980 EogZoomMode
eog_scroll_view_get_zoom_mode(EogScrollView * view)2981 eog_scroll_view_get_zoom_mode (EogScrollView *view)
2982 {
2983 	g_return_val_if_fail (EOG_IS_SCROLL_VIEW (view),
2984 	                      EOG_ZOOM_MODE_SHRINK_TO_FIT);
2985 
2986 	return view->priv->zoom_mode;
2987 }
2988 
2989 static gboolean
eog_scroll_view_get_image_coords(EogScrollView * view,gint * x,gint * y,gint * width,gint * height)2990 eog_scroll_view_get_image_coords (EogScrollView *view, gint *x, gint *y,
2991                                   gint *width, gint *height)
2992 {
2993 	EogScrollViewPrivate *priv = view->priv;
2994 	GtkAllocation allocation;
2995 	gint scaled_width, scaled_height, xofs, yofs;
2996 
2997 	compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
2998 
2999 	if (G_LIKELY (width))
3000 		*width = scaled_width;
3001 	if (G_LIKELY (height))
3002 		*height = scaled_height;
3003 
3004 	/* If only width and height are needed stop here. */
3005 	if (x == NULL && y == NULL)
3006 		return TRUE;
3007 
3008 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
3009 
3010 	/* Compute image offsets with respect to the window */
3011 
3012 	if (scaled_width <= allocation.width)
3013 		xofs = (allocation.width - scaled_width) / 2;
3014 	else
3015 		xofs = -priv->xofs;
3016 
3017 	if (scaled_height <= allocation.height)
3018 		yofs = (allocation.height - scaled_height) / 2;
3019 	else
3020 		yofs = -priv->yofs;
3021 
3022 	if (G_LIKELY (x))
3023 		*x = xofs;
3024 	if (G_LIKELY (y))
3025 		*y = yofs;
3026 
3027 	return TRUE;
3028 }
3029 
3030 /**
3031  * eog_scroll_view_event_is_over_image:
3032  * @view: An #EogScrollView that has an image loaded.
3033  * @ev: A #GdkEvent which must have window-relative coordinates.
3034  *
3035  * Tells if @ev's originates from inside the image area. @view must be
3036  * realized and have an image set for this to work.
3037  *
3038  * It only works with #GdkEvent<!-- -->s that supply coordinate data,
3039  * i.e. #GdkEventButton.
3040  *
3041  * Returns: %TRUE if @ev originates from over the image, %FALSE otherwise.
3042  */
3043 gboolean
eog_scroll_view_event_is_over_image(EogScrollView * view,const GdkEvent * ev)3044 eog_scroll_view_event_is_over_image (EogScrollView *view, const GdkEvent *ev)
3045 {
3046 	EogScrollViewPrivate *priv;
3047 	GdkWindow *window;
3048 	gdouble evx, evy;
3049 	gint x, y, width, height;
3050 
3051 	g_return_val_if_fail (EOG_IS_SCROLL_VIEW (view), FALSE);
3052 	g_return_val_if_fail (gtk_widget_get_realized(GTK_WIDGET(view)), FALSE);
3053 	g_return_val_if_fail (ev != NULL, FALSE);
3054 
3055 	priv = view->priv;
3056 	window = gtk_widget_get_window (GTK_WIDGET (priv->display));
3057 
3058 	if (G_UNLIKELY (priv->pixbuf == NULL
3059 	    || window != ((GdkEventAny*) ev)->window))
3060 		return FALSE;
3061 
3062 	if (G_UNLIKELY (!gdk_event_get_coords (ev, &evx, &evy)))
3063 		return FALSE;
3064 
3065 	if (!eog_scroll_view_get_image_coords (view, &x, &y, &width, &height))
3066 		return FALSE;
3067 
3068 	if (evx < x || evy < y || evx > (x + width) || evy > (y + height))
3069 		return FALSE;
3070 
3071 	return TRUE;
3072 }
3073