1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4 
5 #include <stdlib.h>
6 #include <fcntl.h>
7 #include <math.h>
8 #include <gdk-pixbuf/gdk-pixbuf.h>
9 #include <gdk/gdkkeysyms.h>
10 #ifdef HAVE_RSVG
11 #include <librsvg/rsvg.h>
12 #endif
13 
14 #include "xviewer-config-keys.h"
15 #include "xviewer-enum-types.h"
16 #include "xviewer-scroll-view.h"
17 #include "xviewer-debug.h"
18 
19 #if 0
20 #include "uta.h"
21 #endif
22 #include "zoom.h"
23 
24 /* Maximum size of delayed repaint rectangles */
25 #define PAINT_RECT_WIDTH 128
26 #define PAINT_RECT_HEIGHT 128
27 
28 /* Scroll step increment */
29 #define SCROLL_STEP_SIZE 32
30 
31 /* Maximum zoom factor */
32 #define MAX_ZOOM_FACTOR 20
33 #define MIN_ZOOM_FACTOR 0.02
34 
35 #define CHECK_MEDIUM 8
36 #define CHECK_BLACK "#000000"
37 #define CHECK_DARK "#555555"
38 #define CHECK_GRAY "#808080"
39 #define CHECK_LIGHT "#cccccc"
40 #define CHECK_WHITE "#ffffff"
41 
42 /* Default increment for zooming.  The current zoom factor is multiplied or
43  * divided by this amount on every zooming step.  For consistency, you should
44  * use the same value elsewhere in the program.
45  */
46 #define IMAGE_VIEW_ZOOM_MULTIPLIER 1.05
47 
48 /* from cairo-utils.h */
49 #define _CAIRO_MAX_IMAGE_SIZE 32767
50 
51 
52 #if 0
53 /* Progressive loading state */
54 typedef enum {
55 	PROGRESSIVE_NONE,	/* We are not loading an image or it is already loaded */
56 	PROGRESSIVE_LOADING,	/* An image is being loaded */
57 	PROGRESSIVE_POLISHING	/* We have finished loading an image but have not scaled it with interpolation */
58 } ProgressiveState;
59 #endif
60 
61 /* Signal IDs */
62 enum {
63 	SIGNAL_ZOOM_CHANGED,
64 	SIGNAL_ROTATION_CHANGED,
65 	SIGNAL_NEXT_IMAGE,
66 	SIGNAL_PREVIOUS_IMAGE,
67 	SIGNAL_LAST
68 };
69 static gint view_signals [SIGNAL_LAST];
70 
71 typedef enum {
72 	XVIEWER_SCROLL_VIEW_CURSOR_NORMAL,
73 	XVIEWER_SCROLL_VIEW_CURSOR_HIDDEN,
74 	XVIEWER_SCROLL_VIEW_CURSOR_DRAG
75 } XviewerScrollViewCursor;
76 
77 typedef enum {
78 	XVIEWER_ROTATION_0,
79 	XVIEWER_ROTATION_90,
80 	XVIEWER_ROTATION_180,
81 	XVIEWER_ROTATION_270,
82 	N_XVIEWER_ROTATIONS
83 } XviewerRotationState;
84 
85 typedef enum {
86 	XVIEWER_PAN_ACTION_NONE,
87 	XVIEWER_PAN_ACTION_NEXT,
88 	XVIEWER_PAN_ACTION_PREV
89 } XviewerPanAction;
90 
91 /* Drag 'n Drop */
92 static GtkTargetEntry target_table[] = {
93 	{ "text/uri-list", 0, 0},
94 };
95 
96 enum {
97 	PROP_0,
98 	PROP_ANTIALIAS_IN,
99 	PROP_ANTIALIAS_OUT,
100 	PROP_BACKGROUND_COLOR,
101 	PROP_IMAGE,
102 	PROP_SCROLLWHEEL_ZOOM,
103 	PROP_TRANSP_COLOR,
104 	PROP_TRANSPARENCY_STYLE,
105 	PROP_USE_BG_COLOR,
106 	PROP_ZOOM_MODE,
107 	PROP_ZOOM_MULTIPLIER
108 };
109 
110 /* Private part of the XviewerScrollView structure */
111 struct _XviewerScrollViewPrivate {
112 	/* some widgets we rely on */
113 	GtkWidget *display;
114 	GtkAdjustment *hadj;
115 	GtkAdjustment *vadj;
116 	GtkWidget *hbar;
117 	GtkWidget *vbar;
118 	GtkWidget *menu;
119 
120 	/* actual image */
121 	XviewerImage *image;
122 	guint image_changed_id;
123 	guint frame_changed_id;
124 	GdkPixbuf *pixbuf;
125 	cairo_surface_t *surface;
126 
127 	GSettings           *view_settings;
128 
129 	/* zoom mode, either ZOOM_MODE_FIT or ZOOM_MODE_FREE */
130 	XviewerZoomMode zoom_mode;
131 
132 	/* whether to allow zoom > 1.0 on zoom fit */
133 	gboolean upscale;
134 
135 	/* the actual zoom factor */
136 	double zoom;
137 
138 	/* the minimum possible (reasonable) zoom factor */
139 	double min_zoom;
140 
141 	/* Current scrolling offsets */
142 	int xofs, yofs;
143 
144 #if 0
145 	/* Microtile arrays for dirty region.  This represents the dirty region
146 	 * for interpolated drawing.
147 	 */
148 	XviewerUta *uta;
149 #endif
150 
151 	/* handler ID for paint idle callback */
152 	guint idle_id;
153 
154 	/* Interpolation type when zoomed in*/
155 	cairo_filter_t interp_type_in;
156 
157 	/* Interpolation type when zoomed out*/
158 	cairo_filter_t interp_type_out;
159 
160 	/* Scroll wheel zoom */
161 	gboolean scroll_wheel_zoom;
162 
163 	/* Scroll wheel zoom */
164 	gdouble zoom_multiplier;
165 
166 	/* dragging stuff */
167 	int drag_anchor_x, drag_anchor_y;
168 	int drag_ofs_x, drag_ofs_y;
169 	guint dragging : 1;
170 
171 #if 0
172 	/* status of progressive loading */
173 	ProgressiveState progressive_state;
174 #endif
175 
176 	/* how to indicate transparency in images */
177 	XviewerTransparencyStyle transp_style;
178 	GdkRGBA transp_color;
179 
180 	/* the type of the cursor we are currently showing */
181 	XviewerScrollViewCursor cursor;
182 
183 	gboolean  use_bg_color;
184 	GdkRGBA *background_color;
185 	GdkRGBA *override_bg_color;
186 
187 	cairo_surface_t *background_surface;
188 
189 #if GTK_CHECK_VERSION (3, 14, 0)
190 	GtkGesture *pan_gesture;
191 	GtkGesture *zoom_gesture;
192 	GtkGesture *rotate_gesture;
193 #endif
194 	gdouble initial_zoom;
195 	XviewerRotationState rotate_state;
196 	XviewerPanAction pan_action;
197 
198 	/* Two-pass filtering */
199 	GSource *hq_redraw_timeout_source;
200 	gboolean force_unfiltered;
201 };
202 
203 static void scroll_by (XviewerScrollView *view, int xofs, int yofs);
204 static void set_zoom_fit (XviewerScrollView *view);
205 /* static void request_paint_area (XviewerScrollView *view, GdkRectangle *area); */
206 static void set_minimum_zoom_factor (XviewerScrollView *view);
207 static void view_on_drag_begin_cb (GtkWidget *widget, GdkDragContext *context,
208 				   gpointer user_data);
209 static void view_on_drag_data_get_cb (GtkWidget *widget,
210 				      GdkDragContext*drag_context,
211 				      GtkSelectionData *data, guint info,
212 				      guint time, gpointer user_data);
213 static void _set_zoom_mode_internal (XviewerScrollView *view, XviewerZoomMode mode);
214 static gboolean xviewer_scroll_view_get_image_coords (XviewerScrollView *view, gint *x,
215                                                   gint *y, gint *width,
216                                                   gint *height);
217 static gboolean _xviewer_gdk_rgba_equal0 (const GdkRGBA *a, const GdkRGBA *b);
218 
219 
G_DEFINE_TYPE_WITH_PRIVATE(XviewerScrollView,xviewer_scroll_view,GTK_TYPE_GRID)220 G_DEFINE_TYPE_WITH_PRIVATE (XviewerScrollView, xviewer_scroll_view, GTK_TYPE_GRID)
221 
222 
223 /*===================================
224     widget size changing handler &
225         util functions
226   ---------------------------------*/
227 
228 static cairo_surface_t *
229 create_surface_from_pixbuf (XviewerScrollView *view, GdkPixbuf *pixbuf)
230 {
231 	cairo_surface_t *surface;
232 	cairo_t *cr;
233 	gint w, h;
234 	gboolean size_invalid = FALSE;
235 
236 	w = gdk_pixbuf_get_width (pixbuf);
237 	h = gdk_pixbuf_get_height (pixbuf);
238 
239     if (w > _CAIRO_MAX_IMAGE_SIZE || h > _CAIRO_MAX_IMAGE_SIZE) {
240         g_warning ("Image dimensions too large to process");
241         w = 50;
242         h = 50;
243         size_invalid = TRUE;
244     }
245 
246     surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
247 
248 	if (size_invalid) {
249 		return surface;
250 	}
251 
252 	cr = cairo_create (surface);
253 	gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
254 	cairo_paint (cr);
255 	cairo_destroy (cr);
256 
257 	return surface;
258 }
259 
260 /* Disconnects from the XviewerImage and removes references to it */
261 static void
free_image_resources(XviewerScrollView * view)262 free_image_resources (XviewerScrollView *view)
263 {
264 	XviewerScrollViewPrivate *priv;
265 
266 	priv = view->priv;
267 
268 	if (priv->image_changed_id > 0) {
269 		g_signal_handler_disconnect (G_OBJECT (priv->image), priv->image_changed_id);
270 		priv->image_changed_id = 0;
271 	}
272 
273 	if (priv->frame_changed_id > 0) {
274 		g_signal_handler_disconnect (G_OBJECT (priv->image), priv->frame_changed_id);
275 		priv->frame_changed_id = 0;
276 	}
277 
278 	if (priv->image != NULL) {
279 		xviewer_image_data_unref (priv->image);
280 		priv->image = NULL;
281 	}
282 
283 	if (priv->pixbuf != NULL) {
284 		g_object_unref (priv->pixbuf);
285 		priv->pixbuf = NULL;
286 	}
287 
288 	if (priv->surface != NULL) {
289 		cairo_surface_destroy (priv->surface);
290 		priv->surface = NULL;
291 	}
292 }
293 
294 /* Computes the size in pixels of the scaled image */
295 static void
compute_scaled_size(XviewerScrollView * view,double zoom,int * width,int * height)296 compute_scaled_size (XviewerScrollView *view, double zoom, int *width, int *height)
297 {
298 	XviewerScrollViewPrivate *priv;
299 
300 	priv = view->priv;
301 
302 	if (priv->pixbuf) {
303 		*width = floor (gdk_pixbuf_get_width (priv->pixbuf) * zoom + 0.5);
304 		*height = floor (gdk_pixbuf_get_height (priv->pixbuf) * zoom + 0.5);
305 	} else
306 		*width = *height = 0;
307 }
308 
309 /* Computes the offsets for the new zoom value so that they keep the image
310  * centered on the view.
311  */
312 static void
compute_center_zoom_offsets(XviewerScrollView * view,double old_zoom,double new_zoom,int width,int height,double zoom_x_anchor,double zoom_y_anchor,int * xofs,int * yofs)313 compute_center_zoom_offsets (XviewerScrollView *view,
314 			     double old_zoom, double new_zoom,
315 			     int width, int height,
316 			     double zoom_x_anchor, double zoom_y_anchor,
317 			     int *xofs, int *yofs)
318 {
319 	XviewerScrollViewPrivate *priv;
320 	int old_scaled_width, old_scaled_height;
321 	int new_scaled_width, new_scaled_height;
322 	double view_cx, view_cy;
323 	GtkRequisition req;
324 
325 	priv = view->priv;
326 
327 	compute_scaled_size (view, old_zoom,
328 			     &old_scaled_width, &old_scaled_height);
329 
330 	if (old_scaled_width < width)
331 		view_cx = (zoom_x_anchor * old_scaled_width) / old_zoom;
332 	else
333 		view_cx = (priv->xofs + zoom_x_anchor * width) / old_zoom;
334 
335 	if (old_scaled_height < height)
336 		view_cy = (zoom_y_anchor * old_scaled_height) / old_zoom;
337 	else
338 		view_cy = (priv->yofs + zoom_y_anchor * height) / old_zoom;
339 
340 	compute_scaled_size (view, new_zoom,
341 			     &new_scaled_width, &new_scaled_height);
342 
343 	if (new_scaled_width < width)
344 		*xofs = 0;
345 	else {
346 		if (old_scaled_width < width)
347 			*xofs = floor (view_cx * new_zoom - zoom_x_anchor * old_scaled_width - ((width - old_scaled_width) / 2) + 0.5);
348 		else
349 			*xofs = floor (view_cx * new_zoom - zoom_x_anchor * width + 0.5);
350 		if (*xofs < 0)
351 			*xofs = 0;
352 	}
353 
354 	if (new_scaled_height < height)
355 		*yofs = 0;
356 	else {
357 		if (old_scaled_height < height)
358 			*yofs = floor (view_cy * new_zoom - zoom_y_anchor * old_scaled_height - ((height - old_scaled_height) / 2) + 0.5);
359 		else
360 			*yofs = floor (view_cy * new_zoom - zoom_y_anchor * height + 0.5);
361 		if (*yofs < 0)
362 			*yofs = 0;
363 	}
364 }
365 
366 /* Sets the scrollbar values based on the current scrolling offset */
367 static void
update_scrollbar_values(XviewerScrollView * view)368 update_scrollbar_values (XviewerScrollView *view)
369 {
370 	XviewerScrollViewPrivate *priv;
371 	int scaled_width, scaled_height;
372 	gdouble page_size,page_increment,step_increment;
373 	gdouble lower, upper;
374 	GtkAllocation allocation;
375 
376 	priv = view->priv;
377 
378 	if (!gtk_widget_get_visible (GTK_WIDGET (priv->hbar))
379 	    && !gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
380 		return;
381 
382 	compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
383 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
384 
385 	if (gtk_widget_get_visible (GTK_WIDGET (priv->hbar))) {
386 		/* Set scroll increments */
387 		page_size = MIN (scaled_width, allocation.width);
388 		page_increment = allocation.width / 2;
389 		step_increment = SCROLL_STEP_SIZE;
390 
391 		/* Set scroll bounds and new offsets */
392 		lower = 0;
393 		upper = scaled_width;
394 		priv->xofs = CLAMP (priv->xofs, 0, upper - page_size);
395 
396 		g_signal_handlers_block_matched (
397 			priv->hadj, G_SIGNAL_MATCH_DATA,
398 			0, 0, NULL, NULL, view);
399 
400 		gtk_adjustment_configure (priv->hadj, priv->xofs, lower,
401 					  upper, step_increment,
402 					  page_increment, page_size);
403 
404 		g_signal_handlers_unblock_matched (
405 			priv->hadj, G_SIGNAL_MATCH_DATA,
406 			0, 0, NULL, NULL, view);
407 	}
408 
409 	if (gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) {
410 		page_size = MIN (scaled_height, allocation.height);
411 		page_increment = allocation.height / 2;
412 		step_increment = SCROLL_STEP_SIZE;
413 
414 		lower = 0;
415 		upper = scaled_height;
416 		priv->yofs = CLAMP (priv->yofs, 0, upper - page_size);
417 
418 		g_signal_handlers_block_matched (
419 			priv->vadj, G_SIGNAL_MATCH_DATA,
420 			0, 0, NULL, NULL, view);
421 
422 		gtk_adjustment_configure (priv->vadj, priv->yofs, lower,
423 					  upper, step_increment,
424 					  page_increment, page_size);
425 
426 		g_signal_handlers_unblock_matched (
427 			priv->vadj, G_SIGNAL_MATCH_DATA,
428 			0, 0, NULL, NULL, view);
429 	}
430 }
431 
432 static void
xviewer_scroll_view_set_cursor(XviewerScrollView * view,XviewerScrollViewCursor new_cursor)433 xviewer_scroll_view_set_cursor (XviewerScrollView *view, XviewerScrollViewCursor new_cursor)
434 {
435 	GdkCursor *cursor = NULL;
436 	GdkDisplay *display;
437 	GtkWidget *widget;
438 
439 	if (view->priv->cursor == new_cursor) {
440 		return;
441 	}
442 
443 	widget = gtk_widget_get_toplevel (GTK_WIDGET (view));
444 	display = gtk_widget_get_display (widget);
445 	view->priv->cursor = new_cursor;
446 
447 	switch (new_cursor) {
448 		case XVIEWER_SCROLL_VIEW_CURSOR_NORMAL:
449 			gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
450 			break;
451                 case XVIEWER_SCROLL_VIEW_CURSOR_HIDDEN:
452                         cursor = gdk_cursor_new (GDK_BLANK_CURSOR);
453                         break;
454 		case XVIEWER_SCROLL_VIEW_CURSOR_DRAG:
455 			cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
456 			break;
457 	}
458 
459 	if (cursor) {
460 		gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
461 		g_object_unref (cursor);
462 		gdk_flush();
463 	}
464 }
465 
466 /* Changes visibility of the scrollbars based on the zoom factor and the
467  * specified allocation, or the current allocation if NULL is specified.
468  */
469 static void
check_scrollbar_visibility(XviewerScrollView * view,GtkAllocation * alloc)470 check_scrollbar_visibility (XviewerScrollView *view, GtkAllocation *alloc)
471 {
472 	XviewerScrollViewPrivate *priv;
473 	int bar_height;
474 	int bar_width;
475 	int img_width;
476 	int img_height;
477 	GtkRequisition req;
478 	int width, height;
479 	gboolean hbar_visible, vbar_visible;
480 
481 	priv = view->priv;
482 
483 	if (alloc) {
484 		width = alloc->width;
485 		height = alloc->height;
486 	} else {
487 		GtkAllocation allocation;
488 
489 		gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
490 		width = allocation.width;
491 		height = allocation.height;
492 	}
493 
494 	compute_scaled_size (view, priv->zoom, &img_width, &img_height);
495 
496 	/* this should work fairly well in this special case for scrollbars */
497 	gtk_widget_get_preferred_size (priv->hbar, &req, NULL);
498 	bar_height = req.height;
499 	gtk_widget_get_preferred_size (priv->vbar, &req, NULL);
500 	bar_width = req.width;
501 
502 	xviewer_debug_message (DEBUG_WINDOW, "Widget Size allocate: %i, %i   Bar: %i, %i\n",
503 			   width, height, bar_width, bar_height);
504 
505 	hbar_visible = vbar_visible = FALSE;
506 	if (priv->zoom_mode == XVIEWER_ZOOM_MODE_SHRINK_TO_FIT)
507 		hbar_visible = vbar_visible = FALSE;
508 	else if (img_width <= width && img_height <= height)
509 		hbar_visible = vbar_visible = FALSE;
510 	else if (img_width > width && img_height > height)
511 		hbar_visible = vbar_visible = TRUE;
512 	else if (img_width > width) {
513 		hbar_visible = TRUE;
514 		if (img_height <= (height - bar_height))
515 			vbar_visible = FALSE;
516 		else
517 			vbar_visible = TRUE;
518 	}
519         else if (img_height > height) {
520 		vbar_visible = TRUE;
521 		if (img_width <= (width - bar_width))
522 			hbar_visible = FALSE;
523 		else
524 			hbar_visible = TRUE;
525 	}
526 
527 	if (hbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->hbar)))
528 		g_object_set (G_OBJECT (priv->hbar), "visible", hbar_visible, NULL);
529 
530 	if (vbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
531 		g_object_set (G_OBJECT (priv->vbar), "visible", vbar_visible, NULL);
532 }
533 
534 #define DOUBLE_EQUAL_MAX_DIFF 1e-6
535 #define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF)
536 
537 #if 0
538 /* Returns whether the zoom factor is 1.0 */
539 static gboolean
540 is_unity_zoom (XviewerScrollView *view)
541 {
542 	XviewerScrollViewPrivate *priv;
543 
544 	priv = view->priv;
545 	return DOUBLE_EQUAL (priv->zoom, 1.0);
546 }
547 #endif
548 
549 /* Returns whether the image is zoomed in */
550 static gboolean
is_zoomed_in(XviewerScrollView * view)551 is_zoomed_in (XviewerScrollView *view)
552 {
553 	XviewerScrollViewPrivate *priv;
554 
555 	priv = view->priv;
556 	return priv->zoom - 1.0 > DOUBLE_EQUAL_MAX_DIFF;
557 }
558 
559 /* Returns whether the image is zoomed out */
560 static gboolean
is_zoomed_out(XviewerScrollView * view)561 is_zoomed_out (XviewerScrollView *view)
562 {
563 	XviewerScrollViewPrivate *priv;
564 
565 	priv = view->priv;
566 	return DOUBLE_EQUAL_MAX_DIFF + priv->zoom - 1.0 < 0.0;
567 }
568 
569 /* Returns wether the image is movable, that means if it is larger then
570  * the actual visible area.
571  */
572 static gboolean
is_image_movable(XviewerScrollView * view)573 is_image_movable (XviewerScrollView *view)
574 {
575 	XviewerScrollViewPrivate *priv;
576 
577 	priv = view->priv;
578 
579 	return (gtk_widget_get_visible (priv->hbar) || gtk_widget_get_visible (priv->vbar));
580 }
581 
582 
583 /* Computes the image offsets with respect to the window */
584 /*
585 static void
586 get_image_offsets (XviewerScrollView *view, int *xofs, int *yofs)
587 {
588 	XviewerScrollViewPrivate *priv;
589 	int scaled_width, scaled_height;
590 	int width, height;
591 
592 	priv = view->priv;
593 
594 	compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
595 
596 	width = GTK_WIDGET (priv->display)->allocation.width;
597 	height = GTK_WIDGET (priv->display)->allocation.height;
598 
599 	// Compute image offsets with respect to the window
600 	if (scaled_width <= width)
601 		*xofs = (width - scaled_width) / 2;
602 	else
603 		*xofs = -priv->xofs;
604 
605 	if (scaled_height <= height)
606 		*yofs = (height - scaled_height) / 2;
607 	else
608 		*yofs = -priv->yofs;
609 }
610 */
611 
612 /*===================================
613           drawing core
614   ---------------------------------*/
615 
616 
617 #if 0
618 /* Pulls a rectangle from the specified microtile array.  The rectangle is the
619  * first one that would be glommed together by art_rect_list_from_uta(), and its
620  * size is bounded by max_width and max_height.  The rectangle is also removed
621  * from the microtile array.
622  */
623 static void
624 pull_rectangle (XviewerUta *uta, XviewerIRect *rect, int max_width, int max_height)
625 {
626 	uta_find_first_glom_rect (uta, rect, max_width, max_height);
627 	uta_remove_rect (uta, rect->x0, rect->y0, rect->x1, rect->y1);
628 }
629 
630 /* Paints a rectangle with the background color if the specified rectangle
631  * intersects the dirty rectangle.
632  */
633 static void
634 paint_background (XviewerScrollView *view, XviewerIRect *r, XviewerIRect *rect)
635 {
636 	XviewerScrollViewPrivate *priv;
637 	XviewerIRect d;
638 
639 	priv = view->priv;
640 
641 	xviewer_irect_intersect (&d, r, rect);
642 	if (!xviewer_irect_empty (&d)) {
643 		gdk_window_clear_area (gtk_widget_get_window (priv->display),
644 				       d.x0, d.y0,
645 				       d.x1 - d.x0, d.y1 - d.y0);
646 	}
647 }
648 #endif
649 
650 static void
get_transparency_params(XviewerScrollView * view,int * size,GdkRGBA * color1,GdkRGBA * color2)651 get_transparency_params (XviewerScrollView *view, int *size, GdkRGBA *color1, GdkRGBA *color2)
652 {
653 	XviewerScrollViewPrivate *priv;
654 
655 	priv = view->priv;
656 
657 	/* Compute transparency parameters */
658 	switch (priv->transp_style) {
659 	case XVIEWER_TRANSP_BACKGROUND: {
660 		/* Simply return fully transparent color */
661 		color1->red = color1->green = color1->blue = color1->alpha = 0.0;
662 		color2->red = color2->green = color2->blue = color2->alpha = 0.0;
663 		break;
664 	}
665 
666 	case XVIEWER_TRANSP_CHECKED:
667 		g_warn_if_fail (gdk_rgba_parse (color1, CHECK_GRAY));
668 		g_warn_if_fail (gdk_rgba_parse (color2, CHECK_LIGHT));
669 		break;
670 
671 	case XVIEWER_TRANSP_COLOR:
672 		*color1 = *color2 = priv->transp_color;
673 		break;
674 
675 	default:
676 		g_assert_not_reached ();
677 	};
678 
679 	*size = CHECK_MEDIUM;
680 }
681 
682 
683 static cairo_surface_t *
create_background_surface(XviewerScrollView * view)684 create_background_surface (XviewerScrollView *view)
685 {
686 	int check_size;
687 	GdkRGBA check_1;
688 	GdkRGBA check_2;
689 	cairo_surface_t *surface;
690 
691 	get_transparency_params (view, &check_size, &check_1, &check_2);
692 	surface = gdk_window_create_similar_surface (gtk_widget_get_window (view->priv->display),
693 						     CAIRO_CONTENT_COLOR_ALPHA,
694 						     check_size * 2, check_size * 2);
695 	cairo_t* cr = cairo_create (surface);
696 
697 	/* Use source operator to make fully transparent work */
698 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
699 
700 	gdk_cairo_set_source_rgba(cr, &check_1);
701 	cairo_rectangle (cr, 0, 0, check_size, check_size);
702 	cairo_rectangle (cr, check_size, check_size, check_size, check_size);
703 	cairo_fill (cr);
704 
705 	gdk_cairo_set_source_rgba(cr, &check_2);
706 	cairo_rectangle (cr, 0, check_size, check_size, check_size);
707 	cairo_rectangle (cr, check_size, 0, check_size, check_size);
708 	cairo_fill (cr);
709 
710 	cairo_destroy (cr);
711 
712 	return surface;
713 }
714 
715 #if 0
716 #ifdef HAVE_RSVG
717 static cairo_surface_t *
718 create_background_surface (XviewerScrollView *view)
719 {
720 	int check_size;
721 	guint32 check_1 = 0;
722 	guint32 check_2 = 0;
723 	cairo_surface_t *surface;
724 	cairo_t *check_cr;
725 
726 	get_transparency_params (view, &check_size, &check_1, &check_2);
727 
728 	surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, check_size * 2, check_size * 2);
729 	check_cr = cairo_create (surface);
730 	cairo_set_source_rgba (check_cr,
731 			       ((check_1 & 0xff0000) >> 16) / 255.,
732 			       ((check_1 & 0x00ff00) >> 8)  / 255.,
733 			        (check_1 & 0x0000ff)        / 255.,
734 				1.);
735 	cairo_rectangle (check_cr, 0., 0., check_size, check_size);
736 	cairo_fill (check_cr);
737 	cairo_translate (check_cr, check_size, check_size);
738 	cairo_rectangle (check_cr, 0., 0., check_size, check_size);
739 	cairo_fill (check_cr);
740 
741 	cairo_set_source_rgba (check_cr,
742 			       ((check_2 & 0xff0000) >> 16) / 255.,
743 			       ((check_2 & 0x00ff00) >> 8)  / 255.,
744 			        (check_2 & 0x0000ff)        / 255.,
745 				1.);
746 	cairo_translate (check_cr, -check_size, 0);
747 	cairo_rectangle (check_cr, 0., 0., check_size, check_size);
748 	cairo_fill (check_cr);
749 	cairo_translate (check_cr, check_size, -check_size);
750 	cairo_rectangle (check_cr, 0., 0., check_size, check_size);
751 	cairo_fill (check_cr);
752 	cairo_destroy (check_cr);
753 
754 	return surface;
755 }
756 
757 static void
758 draw_svg_background (XviewerScrollView *view, cairo_t *cr, XviewerIRect *render_rect, XviewerIRect *image_rect)
759 {
760 	XviewerScrollViewPrivate *priv;
761 
762 	priv = view->priv;
763 
764 	if (priv->background_surface == NULL)
765 		priv->background_surface = create_background_surface (view);
766 
767 	cairo_set_source_surface (cr, priv->background_surface,
768 				  - (render_rect->x0 - image_rect->x0) % (CHECK_MEDIUM * 2),
769 				  - (render_rect->y0 - image_rect->y0) % (CHECK_MEDIUM * 2));
770 	cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
771 	cairo_rectangle (cr,
772 			 0,
773 			 0,
774 			 render_rect->x1 - render_rect->x0,
775 			 render_rect->y1 - render_rect->y0);
776 	cairo_fill (cr);
777 }
778 
779 static cairo_surface_t *
780 draw_svg_on_image_surface (XviewerScrollView *view, XviewerIRect *render_rect, XviewerIRect *image_rect)
781 {
782 	XviewerScrollViewPrivate *priv;
783 	cairo_t *cr;
784 	cairo_surface_t *surface;
785 	cairo_matrix_t matrix, translate, scale;
786 	XviewerTransform *transform;
787 
788 	priv = view->priv;
789 
790 	surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
791 					      render_rect->x1 - render_rect->x0,
792 					      render_rect->y1 - render_rect->y0);
793 	cr = cairo_create (surface);
794 
795 	cairo_save (cr);
796 	draw_svg_background (view, cr, render_rect, image_rect);
797 	cairo_restore (cr);
798 
799 	cairo_matrix_init_identity (&matrix);
800 	transform = xviewer_image_get_transform (priv->image);
801 	if (transform) {
802 		cairo_matrix_t affine;
803 		double image_offset_x = 0., image_offset_y = 0.;
804 
805 		xviewer_transform_get_affine (transform, &affine);
806 		cairo_matrix_multiply (&matrix, &affine, &matrix);
807 
808 		switch (xviewer_transform_get_transform_type (transform)) {
809 		case XVIEWER_TRANSFORM_ROT_90:
810 		case XVIEWER_TRANSFORM_FLIP_HORIZONTAL:
811 			image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf);
812 			break;
813 		case XVIEWER_TRANSFORM_ROT_270:
814 		case XVIEWER_TRANSFORM_FLIP_VERTICAL:
815 			image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf);
816 			break;
817 		case XVIEWER_TRANSFORM_ROT_180:
818 		case XVIEWER_TRANSFORM_TRANSPOSE:
819 		case XVIEWER_TRANSFORM_TRANSVERSE:
820 			image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf);
821 			image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf);
822 			break;
823 		case XVIEWER_TRANSFORM_NONE:
824 		default:
825 			break;
826 		}
827 
828 		cairo_matrix_init_translate (&translate, image_offset_x, image_offset_y);
829 		cairo_matrix_multiply (&matrix, &matrix, &translate);
830 	}
831 
832 	cairo_matrix_init_scale (&scale, priv->zoom, priv->zoom);
833 	cairo_matrix_multiply (&matrix, &matrix, &scale);
834 	cairo_matrix_init_translate (&translate, image_rect->x0, image_rect->y0);
835 	cairo_matrix_multiply (&matrix, &matrix, &translate);
836 	cairo_matrix_init_translate (&translate, -render_rect->x0, -render_rect->y0);
837 	cairo_matrix_multiply (&matrix, &matrix, &translate);
838 
839 	cairo_set_matrix (cr, &matrix);
840 
841 	rsvg_handle_render_cairo (xviewer_image_get_svg (priv->image), cr);
842 	cairo_destroy (cr);
843 
844 	return surface;
845 }
846 
847 static void
848 draw_svg (XviewerScrollView *view, XviewerIRect *render_rect, XviewerIRect *image_rect)
849 {
850 	XviewerScrollViewPrivate *priv;
851 	cairo_t *cr;
852 	cairo_surface_t *surface;
853 	GdkWindow *window;
854 
855 	priv = view->priv;
856 
857 	window = gtk_widget_get_window (GTK_WIDGET (priv->display));
858 	surface = draw_svg_on_image_surface (view, render_rect, image_rect);
859 
860 	cr = gdk_cairo_create (window);
861 	cairo_set_source_surface (cr, surface, render_rect->x0, render_rect->y0);
862 	cairo_paint (cr);
863 	cairo_destroy (cr);
864 }
865 #endif
866 
867 /* Paints a rectangle of the dirty region */
868 static void
869 paint_rectangle (XviewerScrollView *view, XviewerIRect *rect, cairo_filter_t interp_type)
870 {
871 	XviewerScrollViewPrivate *priv;
872 	GdkPixbuf *tmp;
873 	char *str;
874 	GtkAllocation allocation;
875 	int scaled_width, scaled_height;
876 	int xofs, yofs;
877 	XviewerIRect r, d;
878 	int check_size;
879 	guint32 check_1 = 0;
880 	guint32 check_2 = 0;
881 
882 	priv = view->priv;
883 
884 	if (!gtk_widget_is_drawable (priv->display))
885 		return;
886 
887 	compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
888 
889 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
890 
891 	if (scaled_width < 1 || scaled_height < 1)
892 	{
893 		r.x0 = 0;
894 		r.y0 = 0;
895 		r.x1 = allocation.width;
896 		r.y1 = allocation.height;
897 		paint_background (view, &r, rect);
898 		return;
899 	}
900 
901 	/* Compute image offsets with respect to the window */
902 
903 	if (scaled_width <= allocation.width)
904 		xofs = (allocation.width - scaled_width) / 2;
905 	else
906 		xofs = -priv->xofs;
907 
908 	if (scaled_height <= allocation.height)
909 		yofs = (allocation.height - scaled_height) / 2;
910 	else
911 		yofs = -priv->yofs;
912 
913 	xviewer_debug_message (DEBUG_WINDOW, "zoom %.2f, xofs: %i, yofs: %i scaled w: %i h: %i\n",
914 			   priv->zoom, xofs, yofs, scaled_width, scaled_height);
915 
916 	/* Draw background if necessary, in four steps */
917 
918 	/* Top */
919 	if (yofs > 0) {
920 		r.x0 = 0;
921 		r.y0 = 0;
922 		r.x1 = allocation.width;
923 		r.y1 = yofs;
924 		paint_background (view, &r, rect);
925 	}
926 
927 	/* Left */
928 	if (xofs > 0) {
929 		r.x0 = 0;
930 		r.y0 = yofs;
931 		r.x1 = xofs;
932 		r.y1 = yofs + scaled_height;
933 		paint_background (view, &r, rect);
934 	}
935 
936 	/* Right */
937 	if (xofs >= 0) {
938 		r.x0 = xofs + scaled_width;
939 		r.y0 = yofs;
940 		r.x1 = allocation.width;
941 		r.y1 = yofs + scaled_height;
942 		if (r.x0 < r.x1)
943 			paint_background (view, &r, rect);
944 	}
945 
946 	/* Bottom */
947 	if (yofs >= 0) {
948 		r.x0 = 0;
949 		r.y0 = yofs + scaled_height;
950 		r.x1 = allocation.width;
951 		r.y1 = allocation.height;
952 		if (r.y0 < r.y1)
953 			paint_background (view, &r, rect);
954 	}
955 
956 
957 	/* Draw the scaled image
958 	 *
959 	 * FIXME: this is not using the color correction tables!
960 	 */
961 
962 	if (!priv->pixbuf)
963 		return;
964 
965 	r.x0 = xofs;
966 	r.y0 = yofs;
967 	r.x1 = xofs + scaled_width;
968 	r.y1 = yofs + scaled_height;
969 
970 	xviewer_irect_intersect (&d, &r, rect);
971 	if (xviewer_irect_empty (&d))
972 		return;
973 
974 	switch (interp_type) {
975 	case CAIRO_FILTER_NEAREST:
976 		str = "NEAREST";
977 		break;
978 	default:
979 		str = "ALIASED";
980 	}
981 
982 	xviewer_debug_message (DEBUG_WINDOW, "%s: x0: %i,\t y0: %i,\t x1: %i,\t y1: %i\n",
983 			   str, d.x0, d.y0, d.x1, d.y1);
984 
985 #ifdef HAVE_RSVG
986 	if (xviewer_image_is_svg (view->priv->image) && interp_type != CAIRO_FILTER_NEAREST) {
987 		draw_svg (view, &d, &r);
988 		return;
989 	}
990 #endif
991 	/* Short-circuit the fast case to avoid a memcpy() */
992 
993 	if (is_unity_zoom (view)
994 	    && gdk_pixbuf_get_colorspace (priv->pixbuf) == GDK_COLORSPACE_RGB
995 	    && !gdk_pixbuf_get_has_alpha (priv->pixbuf)
996 	    && gdk_pixbuf_get_bits_per_sample (priv->pixbuf) == 8) {
997 		guchar *pixels;
998 		int rowstride;
999 
1000 		rowstride = gdk_pixbuf_get_rowstride (priv->pixbuf);
1001 
1002 		pixels = (gdk_pixbuf_get_pixels (priv->pixbuf)
1003 			  + (d.y0 - yofs) * rowstride
1004 			  + 3 * (d.x0 - xofs));
1005 
1006 		gdk_draw_rgb_image_dithalign (gtk_widget_get_window (GTK_WIDGET (priv->display)),
1007 					      gtk_widget_get_style (GTK_WIDGET (priv->display))->black_gc,
1008 					      d.x0, d.y0,
1009 					      d.x1 - d.x0, d.y1 - d.y0,
1010 					      GDK_RGB_DITHER_MAX,
1011 					      pixels,
1012 					      rowstride,
1013 					      d.x0 - xofs, d.y0 - yofs);
1014 		return;
1015 	}
1016 
1017 	/* For all other cases, create a temporary pixbuf */
1018 
1019 	tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, d.x1 - d.x0, d.y1 - d.y0);
1020 
1021 	if (!tmp) {
1022 		g_message ("paint_rectangle(): Could not allocate temporary pixbuf of "
1023 			   "size (%d, %d); skipping", d.x1 - d.x0, d.y1 - d.y0);
1024 		return;
1025 	}
1026 
1027 	/* Compute transparency parameters */
1028 	get_transparency_params (view, &check_size, &check_1, &check_2);
1029 
1030 	/* Draw! */
1031 	gdk_pixbuf_composite_color (priv->pixbuf,
1032 				    tmp,
1033 				    0, 0,
1034 				    d.x1 - d.x0, d.y1 - d.y0,
1035 				    -(d.x0 - xofs), -(d.y0 - yofs),
1036 				    priv->zoom, priv->zoom,
1037 				    is_unity_zoom (view) ? CAIRO_FILTER_NEAREST : interp_type,
1038 				    255,
1039 				    d.x0 - xofs, d.y0 - yofs,
1040 				    check_size,
1041 				    check_1, check_2);
1042 
1043 	gdk_draw_rgb_image_dithalign (gtk_widget_get_window (priv->display),
1044 				      gtk_widget_get_style (priv->display)->black_gc,
1045 				      d.x0, d.y0,
1046 				      d.x1 - d.x0, d.y1 - d.y0,
1047 				      GDK_RGB_DITHER_MAX,
1048 				      gdk_pixbuf_get_pixels (tmp),
1049 				      gdk_pixbuf_get_rowstride (tmp),
1050 				      d.x0 - xofs, d.y0 - yofs);
1051 
1052 	g_object_unref (tmp);
1053 }
1054 
1055 
1056 /* Idle handler for the drawing process.  We pull a rectangle from the dirty
1057  * region microtile array, paint it, and leave the rest to the next idle
1058  * iteration.
1059  */
1060 static gboolean
1061 paint_iteration_idle (gpointer data)
1062 {
1063 	XviewerScrollView *view;
1064 	XviewerScrollViewPrivate *priv;
1065 	XviewerIRect rect;
1066 
1067 	view = XVIEWER_SCROLL_VIEW (data);
1068 	priv = view->priv;
1069 
1070 	g_assert (priv->uta != NULL);
1071 
1072 	pull_rectangle (priv->uta, &rect, PAINT_RECT_WIDTH, PAINT_RECT_HEIGHT);
1073 
1074 	if (xviewer_irect_empty (&rect)) {
1075 		xviewer_uta_free (priv->uta);
1076 		priv->uta = NULL;
1077 	} else {
1078 		if (is_zoomed_in (view))
1079 			paint_rectangle (view, &rect, priv->interp_type_in);
1080 		else if (is_zoomed_out (view))
1081 			paint_rectangle (view, &rect, priv->interp_type_out);
1082 		else
1083 			paint_rectangle (view, &rect, CAIRO_FILTER_NEAREST);
1084 	}
1085 
1086 	if (!priv->uta) {
1087 		priv->idle_id = 0;
1088 		return FALSE;
1089 	}
1090 
1091 	return TRUE;
1092 }
1093 
1094 /* Paints the requested area in non-interpolated mode.  Then, if we are
1095  * configured to use interpolation, we queue an idle handler to redraw the area
1096  * with interpolation.  The area is in window coordinates.
1097  */
1098 static void
1099 request_paint_area (XviewerScrollView *view, GdkRectangle *area)
1100 {
1101 	XviewerScrollViewPrivate *priv;
1102 	XviewerIRect r;
1103 	GtkAllocation allocation;
1104 
1105 	priv = view->priv;
1106 
1107 	xviewer_debug_message (DEBUG_WINDOW, "x: %i, y: %i, width: %i, height: %i\n",
1108 			   area->x, area->y, area->width, area->height);
1109 
1110 	if (!gtk_widget_is_drawable (priv->display))
1111 		return;
1112 
1113 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
1114 	r.x0 = MAX (0, area->x);
1115 	r.y0 = MAX (0, area->y);
1116 	r.x1 = MIN (allocation.width, area->x + area->width);
1117 	r.y1 = MIN (allocation.height, area->y + area->height);
1118 
1119 	xviewer_debug_message (DEBUG_WINDOW, "r: %i, %i, %i, %i\n", r.x0, r.y0, r.x1, r.y1);
1120 
1121 	if (r.x0 >= r.x1 || r.y0 >= r.y1)
1122 		return;
1123 
1124 	/* Do nearest neighbor, 1:1 zoom or active progressive loading synchronously for speed.  */
1125 	if ((is_zoomed_in (view) && priv->interp_type_in == CAIRO_FILTER_NEAREST) ||
1126 	    (is_zoomed_out (view) && priv->interp_type_out == CAIRO_FILTER_NEAREST) ||
1127 	    is_unity_zoom (view) ||
1128 	    priv->progressive_state == PROGRESSIVE_LOADING) {
1129 		paint_rectangle (view, &r, CAIRO_FILTER_NEAREST);
1130 		return;
1131 	}
1132 
1133 	if (priv->progressive_state == PROGRESSIVE_POLISHING)
1134 		/* We have already a complete image with nearest neighbor mode.
1135 		 * It's sufficient to add only a antitaliased idle update
1136 		 */
1137 		priv->progressive_state = PROGRESSIVE_NONE;
1138 	else if (!priv->image || !xviewer_image_is_animation (priv->image))
1139 		/* do nearest neigbor before anti aliased version,
1140 		   except for animations to avoid a "blinking" effect. */
1141 		paint_rectangle (view, &r, CAIRO_FILTER_NEAREST);
1142 
1143 	/* All other interpolation types are delayed.  */
1144 	if (priv->uta)
1145 		g_assert (priv->idle_id != 0);
1146 	else {
1147 		g_assert (priv->idle_id == 0);
1148 		priv->idle_id = g_idle_add (paint_iteration_idle, view);
1149 	}
1150 
1151 	priv->uta = uta_add_rect (priv->uta, r.x0, r.y0, r.x1, r.y1);
1152 }
1153 #endif
1154 
1155 
1156 /* =======================================
1157 
1158     scrolling stuff
1159 
1160     --------------------------------------*/
1161 
1162 
1163 /* Scrolls the view to the specified offsets.  */
1164 static void
scroll_to(XviewerScrollView * view,int x,int y,gboolean change_adjustments)1165 scroll_to (XviewerScrollView *view, int x, int y, gboolean change_adjustments)
1166 {
1167 	XviewerScrollViewPrivate *priv;
1168 	GtkAllocation allocation;
1169 	int xofs, yofs;
1170 	GdkWindow *window;
1171 #if 0
1172 	int src_x, src_y;
1173 	int dest_x, dest_y;
1174 	int twidth, theight;
1175 #endif
1176 
1177 	priv = view->priv;
1178 
1179 	/* Check bounds & Compute offsets */
1180 	if (gtk_widget_get_visible (priv->hbar)) {
1181 		x = CLAMP (x, 0, gtk_adjustment_get_upper (priv->hadj)
1182 				 - gtk_adjustment_get_page_size (priv->hadj));
1183 		xofs = x - priv->xofs;
1184 	} else
1185 		xofs = 0;
1186 
1187 	if (gtk_widget_get_visible (priv->vbar)) {
1188 		y = CLAMP (y, 0, gtk_adjustment_get_upper (priv->vadj)
1189 				 - gtk_adjustment_get_page_size (priv->vadj));
1190 		yofs = y - priv->yofs;
1191 	} else
1192 		yofs = 0;
1193 
1194 	if (xofs == 0 && yofs == 0)
1195 		return;
1196 
1197 	priv->xofs = x;
1198 	priv->yofs = y;
1199 
1200 	if (!gtk_widget_is_drawable (priv->display))
1201 		goto out;
1202 
1203 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
1204 
1205 	if (abs (xofs) >= allocation.width || abs (yofs) >= allocation.height) {
1206 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1207 		goto out;
1208 	}
1209 
1210 	window = gtk_widget_get_window (GTK_WIDGET (priv->display));
1211 
1212 	/* Ensure that the uta has the full size */
1213 #if 0
1214 	twidth = (allocation.width + XVIEWER_UTILE_SIZE - 1) >> XVIEWER_UTILE_SHIFT;
1215 	theight = (allocation.height + XVIEWER_UTILE_SIZE - 1) >> XVIEWER_UTILE_SHIFT;
1216 
1217 #if 0
1218 	if (priv->uta)
1219 		g_assert (priv->idle_id != 0);
1220 	else
1221 		priv->idle_id = g_idle_add (paint_iteration_idle, view);
1222 #endif
1223 
1224 	priv->uta = uta_ensure_size (priv->uta, 0, 0, twidth, theight);
1225 
1226 	/* Copy the uta area.  Our synchronous handling of expose events, below,
1227 	 * will queue the new scrolled-in areas.
1228 	 */
1229 	src_x = xofs < 0 ? 0 : xofs;
1230 	src_y = yofs < 0 ? 0 : yofs;
1231 	dest_x = xofs < 0 ? -xofs : 0;
1232 	dest_y = yofs < 0 ? -yofs : 0;
1233 
1234 	uta_copy_area (priv->uta,
1235 		       src_x, src_y,
1236 		       dest_x, dest_y,
1237 		       allocation.width - abs (xofs),
1238 		       allocation.height - abs (yofs));
1239 #endif
1240 	/* Scroll the window area and process exposure synchronously. */
1241 
1242 #if GTK_CHECK_VERSION (3, 14, 0)
1243 	if (!gtk_gesture_is_recognized (priv->zoom_gesture)) {
1244 		gdk_window_scroll (window, -xofs, -yofs);
1245 		gdk_window_process_updates (window, TRUE);
1246 	}
1247 #else
1248 	gdk_window_scroll (window, -xofs, -yofs);
1249 	gdk_window_process_updates (window, TRUE);
1250 #endif
1251 
1252  out:
1253 	if (!change_adjustments)
1254 		return;
1255 
1256 	g_signal_handlers_block_matched (
1257 		priv->hadj, G_SIGNAL_MATCH_DATA,
1258 		0, 0, NULL, NULL, view);
1259 	g_signal_handlers_block_matched (
1260 		priv->vadj, G_SIGNAL_MATCH_DATA,
1261 		0, 0, NULL, NULL, view);
1262 
1263 	gtk_adjustment_set_value (priv->hadj, x);
1264 	gtk_adjustment_set_value (priv->vadj, y);
1265 
1266 	g_signal_handlers_unblock_matched (
1267 		priv->hadj, G_SIGNAL_MATCH_DATA,
1268 		0, 0, NULL, NULL, view);
1269 	g_signal_handlers_unblock_matched (
1270 		priv->vadj, G_SIGNAL_MATCH_DATA,
1271 		0, 0, NULL, NULL, view);
1272 }
1273 
1274 /* Scrolls the image view by the specified offsets.  Notifies the adjustments
1275  * about their new values.
1276  */
1277 static void
scroll_by(XviewerScrollView * view,int xofs,int yofs)1278 scroll_by (XviewerScrollView *view, int xofs, int yofs)
1279 {
1280 	XviewerScrollViewPrivate *priv;
1281 
1282 	priv = view->priv;
1283 
1284 	scroll_to (view, priv->xofs + xofs, priv->yofs + yofs, TRUE);
1285 }
1286 
_hq_redraw_cb(gpointer user_data)1287 static gboolean _hq_redraw_cb (gpointer user_data)
1288 {
1289 	XviewerScrollViewPrivate *priv = XVIEWER_SCROLL_VIEW (user_data)->priv;
1290 
1291 	priv->force_unfiltered = FALSE;
1292 	gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1293 
1294 	priv->hq_redraw_timeout_source = NULL;
1295 	return G_SOURCE_REMOVE;
1296 }
1297 
1298 static void
_clear_hq_redraw_timeout(XviewerScrollView * view)1299 _clear_hq_redraw_timeout (XviewerScrollView *view)
1300 {
1301 	XviewerScrollViewPrivate *priv = view->priv;
1302 
1303 	if (priv->hq_redraw_timeout_source != NULL) {
1304 		g_source_unref (priv->hq_redraw_timeout_source);
1305 		g_source_destroy (priv->hq_redraw_timeout_source);
1306 	}
1307 
1308 	priv->hq_redraw_timeout_source = NULL;
1309 }
1310 
1311 static void
_set_hq_redraw_timeout(XviewerScrollView * view)1312 _set_hq_redraw_timeout (XviewerScrollView *view)
1313 {
1314 	GSource *source;
1315 
1316 	_clear_hq_redraw_timeout (view);
1317 
1318 	source = g_timeout_source_new (200);
1319 	g_source_set_callback (source, &_hq_redraw_cb, view, NULL);
1320 
1321 	g_source_attach (source, NULL);
1322 
1323 	view->priv->hq_redraw_timeout_source = source;
1324 }
1325 
1326 /* Callback used when an adjustment is changed */
1327 static void
adjustment_changed_cb(GtkAdjustment * adj,gpointer data)1328 adjustment_changed_cb (GtkAdjustment *adj, gpointer data)
1329 {
1330 	XviewerScrollView *view;
1331 	XviewerScrollViewPrivate *priv;
1332 
1333 	view = XVIEWER_SCROLL_VIEW (data);
1334 	priv = view->priv;
1335 
1336 	scroll_to (view, gtk_adjustment_get_value (priv->hadj),
1337 		   gtk_adjustment_get_value (priv->vadj), FALSE);
1338 }
1339 
1340 
1341 /* Drags the image to the specified position */
1342 static void
drag_to(XviewerScrollView * view,int x,int y)1343 drag_to (XviewerScrollView *view, int x, int y)
1344 {
1345 	XviewerScrollViewPrivate *priv;
1346 	int dx, dy;
1347 
1348 	priv = view->priv;
1349 
1350 	dx = priv->drag_anchor_x - x;
1351 	dy = priv->drag_anchor_y - y;
1352 
1353 	x = priv->drag_ofs_x + dx;
1354 	y = priv->drag_ofs_y + dy;
1355 
1356 	scroll_to (view, x, y, TRUE);
1357 }
1358 
1359 static void
set_minimum_zoom_factor(XviewerScrollView * view)1360 set_minimum_zoom_factor (XviewerScrollView *view)
1361 {
1362 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
1363 
1364 	view->priv->min_zoom = MAX (1.0 / gdk_pixbuf_get_width (view->priv->pixbuf),
1365 				    MAX(1.0 / gdk_pixbuf_get_height (view->priv->pixbuf),
1366 					MIN_ZOOM_FACTOR) );
1367 	return;
1368 }
1369 
1370 /**
1371  * set_zoom:
1372  * @view: A scroll view.
1373  * @zoom: Zoom factor.
1374  * @have_anchor: Whether the anchor point specified by (@anchorx, @anchory)
1375  * should be used.
1376  * @anchorx: Horizontal anchor point in pixels.
1377  * @anchory: Vertical anchor point in pixels.
1378  *
1379  * Sets the zoom factor for an image view.  The anchor point can be used to
1380  * specify the point that stays fixed when the image is zoomed.  If @have_anchor
1381  * is %TRUE, then (@anchorx, @anchory) specify the point relative to the image
1382  * view widget's allocation that will stay fixed when zooming.  If @have_anchor
1383  * is %FALSE, then the center point of the image view will be used.
1384  * If @force_display is %TRUE then the function will not return if @zoom == priv->zoom
1385  * (this is necessary for different scroll settings of the fit to width and fit to
1386  * height to be used consecutively)
1387  **/
1388 static void
set_zoom(XviewerScrollView * view,double zoom,gboolean have_anchor,int anchorx,int anchory,gboolean force_display)1389 set_zoom (XviewerScrollView *view, double zoom,
1390 	  gboolean have_anchor, int anchorx, int anchory,
1391       gboolean force_display)
1392 {
1393 	XviewerScrollViewPrivate *priv;
1394 	GtkAllocation allocation;
1395 	int xofs, yofs;
1396 	double x_rel, y_rel;
1397 	int zoomed_img_height;
1398 	int zoomed_img_width;
1399 
1400 	priv = view->priv;
1401 
1402 	if (priv->pixbuf == NULL)
1403 		return;
1404 
1405 	if (zoom > MAX_ZOOM_FACTOR)
1406 		zoom = MAX_ZOOM_FACTOR;
1407 	else if (zoom < MIN_ZOOM_FACTOR)
1408 		zoom = MIN_ZOOM_FACTOR;
1409 
1410 	if ((DOUBLE_EQUAL (priv->zoom, zoom)) && (!force_display))
1411 		return;
1412 	if (DOUBLE_EQUAL (priv->zoom, priv->min_zoom) && zoom < priv->zoom)
1413 		return;
1414 
1415 	xviewer_scroll_view_set_zoom_mode (view, XVIEWER_ZOOM_MODE_FREE);
1416 
1417 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
1418 
1419 	/* compute new xofs/yofs values */
1420 	if (have_anchor) {
1421 		int image_to_window_edge;
1422 
1423 		compute_scaled_size (view, priv->zoom, &zoomed_img_width, &zoomed_img_height);
1424 
1425 		if (zoomed_img_height >= allocation.height)
1426 			y_rel = (double) anchory / allocation.height;
1427 		else
1428 		{
1429 			image_to_window_edge = (allocation.height - zoomed_img_height) / 2;
1430 			if (anchory < image_to_window_edge)
1431 				y_rel = 0.0;
1432 			else
1433 			{
1434 				y_rel = (double)(anchory - image_to_window_edge)/zoomed_img_height;
1435 				if (y_rel > 1.0)
1436 					y_rel = 1.0;
1437 				if (y_rel < 0.0)
1438 					y_rel = 0.0;
1439 			}
1440 		}
1441 
1442 		if (zoomed_img_width >= allocation.width)
1443 			x_rel = (double) anchorx / allocation.width;
1444 		else
1445 		{
1446 			image_to_window_edge = (allocation.width - zoomed_img_width) / 2;
1447 			if (anchorx < image_to_window_edge)
1448 				x_rel = 0.0;
1449 			else
1450 			{
1451 				x_rel = (double)(anchorx - image_to_window_edge)/zoomed_img_width;
1452 				if (x_rel > 1.0)
1453 					x_rel = 1.0;
1454 				if (x_rel < 0.0)
1455 					x_rel = 0.0;
1456 			}
1457 		}
1458 	} else {
1459 		x_rel = 0.5;
1460 		y_rel = 0.5;
1461 	}
1462 
1463 	compute_center_zoom_offsets (view, priv->zoom, zoom,
1464 				     allocation.width, allocation.height,
1465 				     x_rel, y_rel,
1466 				     &xofs, &yofs);
1467 
1468 	/* set new values */
1469 	priv->xofs = xofs; /* (img_width * x_rel * zoom) - anchorx; */
1470 	priv->yofs = yofs; /* (img_height * y_rel * zoom) - anchory; */
1471 
1472 	if (priv->dragging) {
1473 		priv->drag_anchor_x = anchorx;
1474 		priv->drag_anchor_y = anchory;
1475 		priv->drag_ofs_x = priv->xofs;
1476 		priv->drag_ofs_y = priv->yofs;
1477 	}
1478 #if 0
1479 	g_print ("xofs: %i  yofs: %i\n", priv->xofs, priv->yofs);
1480 #endif
1481 	if (zoom <= priv->min_zoom)
1482 		priv->zoom = priv->min_zoom;
1483 	else
1484 		priv->zoom = zoom;
1485 
1486 	/* we make use of the new values here */
1487 	check_scrollbar_visibility (view, NULL);
1488 	update_scrollbar_values (view);
1489 
1490 	/* repaint the whole image */
1491 	gtk_widget_queue_draw (GTK_WIDGET (priv->display));
1492 	g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom);
1493 }
1494 
1495 /* Zooms the image to fit the available allocation */
1496 static void
set_zoom_fit(XviewerScrollView * view)1497 set_zoom_fit (XviewerScrollView *view)
1498 {
1499 	XviewerScrollViewPrivate *priv;
1500 	GtkAllocation allocation;
1501 	double new_zoom;
1502 
1503 	priv = view->priv;
1504 
1505 	priv->zoom_mode = XVIEWER_ZOOM_MODE_SHRINK_TO_FIT;
1506 
1507 	if (!gtk_widget_get_mapped (GTK_WIDGET (view)))
1508 		return;
1509 
1510 	if (priv->pixbuf == NULL)
1511 		return;
1512 
1513 	gtk_widget_get_allocation (GTK_WIDGET(priv->display), &allocation);
1514 
1515 	new_zoom = zoom_fit_scale (allocation.width, allocation.height,
1516 				   gdk_pixbuf_get_width (priv->pixbuf),
1517 				   gdk_pixbuf_get_height (priv->pixbuf),
1518 				   priv->upscale);
1519 
1520 	if (new_zoom > MAX_ZOOM_FACTOR)
1521 		new_zoom = MAX_ZOOM_FACTOR;
1522 	else if (new_zoom < MIN_ZOOM_FACTOR)
1523 		new_zoom = MIN_ZOOM_FACTOR;
1524 
1525 	priv->zoom = new_zoom;
1526 	priv->xofs = 0;
1527 	priv->yofs = 0;
1528 
1529 	g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom);
1530 }
1531 
1532 /*===================================
1533 
1534    internal signal callbacks
1535 
1536   ---------------------------------*/
1537 
1538 /* Key press event handler for the image view */
1539 static gboolean
display_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer data)1540 display_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data)
1541 {
1542 	XviewerScrollView *view;
1543 	XviewerScrollViewPrivate *priv;
1544 	GtkAllocation allocation;
1545 	gboolean do_zoom;
1546 	double zoom;
1547 	gboolean do_scroll;
1548 	int xofs, yofs;
1549 	GdkModifierType modifiers;
1550 	int zoomed_img_height;
1551 	int zoomed_img_width;
1552 	GtkRequisition req;
1553 	view = XVIEWER_SCROLL_VIEW (data);
1554 	priv = view->priv;
1555 
1556 	do_zoom = FALSE;
1557 	do_scroll = FALSE;
1558 	xofs = yofs = 0;
1559 	zoom = 1.0;
1560 
1561 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
1562 
1563 	modifiers = gtk_accelerator_get_default_mod_mask ();
1564 
1565 	switch (event->keyval) {
1566 	case GDK_KEY_Up:
1567 		if ((event->state & modifiers) == GDK_MOD1_MASK) {
1568 			do_scroll = TRUE;
1569 			xofs = 0;
1570 			yofs = -SCROLL_STEP_SIZE;
1571 		}
1572 		break;
1573 
1574 	case GDK_KEY_Page_Up:
1575 		if ((event->state & GDK_MOD1_MASK) != 0) {
1576 			do_scroll = TRUE;
1577 			if (event->state & GDK_CONTROL_MASK) {
1578 				xofs = -(allocation.width * 3) / 4;
1579 				yofs = 0;
1580 			} else {
1581 				xofs = 0;
1582 				yofs = -(allocation.height * 3) / 4;
1583 			}
1584 		}
1585 		break;
1586 
1587 	case GDK_KEY_Down:
1588 		if ((event->state & modifiers) == GDK_MOD1_MASK) {
1589 			do_scroll = TRUE;
1590 			xofs = 0;
1591 			yofs = SCROLL_STEP_SIZE;
1592 		}
1593 		break;
1594 
1595 	case GDK_KEY_Page_Down:
1596 		if ((event->state & GDK_MOD1_MASK) != 0) {
1597 			do_scroll = TRUE;
1598 			if (event->state & GDK_CONTROL_MASK) {
1599 				xofs = (allocation.width * 3) / 4;
1600 				yofs = 0;
1601 			} else {
1602 				xofs = 0;
1603 				yofs = (allocation.height * 3) / 4;
1604 			}
1605 		}
1606 		break;
1607 
1608 	case GDK_KEY_Left:
1609 		if ((event->state & modifiers) == GDK_MOD1_MASK) {
1610 			do_scroll = TRUE;
1611 			xofs = -SCROLL_STEP_SIZE;
1612 			yofs = 0;
1613 		}
1614 		break;
1615 
1616 	case GDK_KEY_Right:
1617 		if ((event->state & modifiers) == GDK_MOD1_MASK) {
1618 			do_scroll = TRUE;
1619 			xofs = SCROLL_STEP_SIZE;
1620 			yofs = 0;
1621 		}
1622 		break;
1623 
1624 	case GDK_KEY_plus:
1625 	case GDK_KEY_equal:
1626 	case GDK_KEY_KP_Add:
1627 		if (!(event->state & modifiers)) {
1628 			do_zoom = TRUE;
1629 			zoom = priv->zoom * priv->zoom_multiplier;
1630 		}
1631 		break;
1632 
1633 	case GDK_KEY_minus:
1634 	case GDK_KEY_KP_Subtract:
1635 		if (!(event->state & modifiers)) {
1636 			do_zoom = TRUE;
1637 			zoom = priv->zoom / priv->zoom_multiplier;
1638 		}
1639 		break;
1640 
1641                             /* zoom to fit image height */
1642     case GDK_KEY_J:         /* keycode needed so that the menu and the toolbar icon can call this code */
1643     case GDK_KEY_H:
1644     case GDK_KEY_h:
1645         if ((event->state & (modifiers ^ (GDK_SHIFT_MASK | GDK_MOD1_MASK))) == 0)
1646         {
1647             if (gtk_widget_get_visible (GTK_WIDGET (priv->hbar)))
1648             {
1649                 gtk_widget_get_preferred_size (priv->hbar, &req, NULL);
1650                 allocation.height += req.height;
1651             }
1652 
1653             if (gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
1654             {
1655                 gtk_widget_get_preferred_size (priv->vbar, &req, NULL);
1656                 allocation.width += req.width;
1657             }
1658 
1659                                             /* calculate the zoom factor for fitting the height assuming
1660                                                that the horizontal scrollbar is not required */
1661             zoom = (double)allocation.height / (double)gdk_pixbuf_get_height (priv->pixbuf);
1662 
1663             if ((zoom * gdk_pixbuf_get_width (priv->pixbuf)) > allocation.width)
1664             {                       /* need to display the horizontal scrollbar so reduce height
1665                                        available to show height without vertical scrollbar */
1666                 if (!gtk_widget_get_visible (GTK_WIDGET (priv->hbar)))
1667                     g_object_set (G_OBJECT (priv->hbar), "visible", TRUE, NULL);    /* show the horizontalal scrollbar so
1668                                                                                        that its height can be determined */
1669 
1670                 gtk_widget_get_preferred_size (priv->hbar, &req, NULL);
1671 
1672                 zoom = (double)(allocation.height - req.height) / (double)gdk_pixbuf_get_height (priv->pixbuf);
1673 
1674                                             /* avoid having blank bars on all 4 sides as would be the case if,
1675                                                with the reduced zoom, the horizontal scrollbar is no longer needed.
1676                                                Get rid of the side bars and reduce the width of the top and bottom bars */
1677                 if ((zoom * gdk_pixbuf_get_width (priv->pixbuf)) < allocation.width)
1678                     zoom = (double)allocation.width / (double)gdk_pixbuf_get_width (priv->pixbuf);
1679             }
1680 
1681             if (zoom > MAX_ZOOM_FACTOR)
1682                 zoom = MAX_ZOOM_FACTOR;
1683             else if (zoom < MIN_ZOOM_FACTOR)
1684                 zoom = MIN_ZOOM_FACTOR;
1685 
1686             do_zoom = TRUE;
1687         }
1688         break;
1689 
1690                             /* zoom to fit image width */
1691     case GDK_KEY_K:         /* keycode needed so that the menu and the toolbar icon can call this code */
1692     case GDK_KEY_W:
1693     case GDK_KEY_w:
1694         if ((event->state & (modifiers ^ (GDK_SHIFT_MASK | GDK_MOD1_MASK))) == 0)
1695         {
1696             if (gtk_widget_get_visible (GTK_WIDGET (priv->hbar)))
1697             {
1698                 gtk_widget_get_preferred_size (priv->hbar, &req, NULL);
1699                 allocation.height += req.height;
1700             }
1701 
1702             if (gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
1703             {
1704                 gtk_widget_get_preferred_size (priv->vbar, &req, NULL);
1705                 allocation.width += req.width;
1706             }
1707 
1708                                             /* calculate the zoom factor for fitting the width assuming
1709                                                that the vertical scrollbar is not required */
1710             zoom = (double)allocation.width / (double)gdk_pixbuf_get_width (priv->pixbuf);
1711 
1712             if ((zoom * gdk_pixbuf_get_height (priv->pixbuf)) > allocation.height)
1713             {                       /* need to display the vertical scrollbar so reduce width
1714                                        available to show width without horizontal scrollbar */
1715                 if (!gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
1716                     g_object_set (G_OBJECT (priv->vbar), "visible", TRUE, NULL);    /* show the vertical scrollbar so
1717                                                                                        that its width can be determined */
1718 
1719                 gtk_widget_get_preferred_size (priv->vbar, &req, NULL);
1720 
1721                 zoom = (double)(allocation.width - req.width) / (double)gdk_pixbuf_get_width (priv->pixbuf);
1722 
1723                                             /* avoid having blank bars on all 4 sides as would be the case if,
1724                                                with the reduced zoom, the vertical scrollbar is no longer needed.
1725                                                Get rid of the top and bottom bars and reduce the width of the side bars */
1726                 if ((zoom * gdk_pixbuf_get_height (priv->pixbuf)) < allocation.height)
1727                     zoom = (double)allocation.height / (double)gdk_pixbuf_get_height (priv->pixbuf);
1728             }
1729 
1730             if (zoom > MAX_ZOOM_FACTOR)
1731                 zoom = MAX_ZOOM_FACTOR;
1732             else if (zoom < MIN_ZOOM_FACTOR)
1733                 zoom = MIN_ZOOM_FACTOR;
1734 
1735             do_zoom = TRUE;
1736         }
1737         break;
1738 
1739 	case GDK_KEY_1:
1740         if (!(event->state & modifiers)) {
1741 
1742             double zoom_for_fit;
1743 
1744             compute_scaled_size (view, priv->zoom, &zoomed_img_width, &zoomed_img_height);
1745 
1746             if (gtk_widget_get_visible (GTK_WIDGET (priv->hbar))) {
1747                 gtk_widget_get_preferred_size (priv->hbar, &req, NULL);
1748                 allocation.height += req.height;
1749             }
1750 
1751             if (gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) {
1752                 gtk_widget_get_preferred_size (priv->vbar, &req, NULL);
1753                 allocation.width += req.width;
1754             }
1755 
1756             zoom_for_fit = zoom_fit_scale (allocation.width, allocation.height,
1757                     gdk_pixbuf_get_width (priv->pixbuf),
1758                     gdk_pixbuf_get_height (priv->pixbuf),
1759                     priv->upscale);
1760 
1761             if (zoom_for_fit > MAX_ZOOM_FACTOR)
1762                 zoom_for_fit = MAX_ZOOM_FACTOR;
1763             else if (zoom_for_fit < MIN_ZOOM_FACTOR)
1764                 zoom_for_fit = MIN_ZOOM_FACTOR;
1765 
1766             if ((gdk_pixbuf_get_width (priv->pixbuf) <= allocation.width)
1767                 && (gdk_pixbuf_get_height (priv->pixbuf) <= allocation.height))
1768             {
1769                 zoom = 1.0;                         /* the 1:1 image fits in the window */
1770                 priv->xofs = 0;                     /* as zoomed to fit the offsets must be 0 */
1771                 priv->yofs = 0;
1772             }
1773             else
1774             {
1775                 if (DOUBLE_EQUAL(priv->zoom, 1.0))
1776                 {
1777                     zoom = zoom_for_fit;
1778                     priv->xofs = 0;             /* as zoomed to fit the offsets must be 0 */
1779                     priv->yofs = 0;
1780                 }
1781                 else
1782                     zoom = 1.0;
1783             }
1784 
1785                             /* the following two statements are necessary otherwise if the 1:1 image
1786                                is dragged the alignment is thrown out */
1787             if (DOUBLE_EQUAL(priv->zoom,zoom_for_fit))
1788             {
1789                 priv->xofs = 0;
1790                 priv->yofs = 0;
1791             }
1792 
1793             do_zoom = TRUE;
1794 		}
1795 		break;
1796 
1797 	default:
1798 		return FALSE;
1799 	}
1800 
1801 	if (do_zoom) {
1802 		GdkDeviceManager *device_manager;
1803 		GdkDevice *device;
1804 		gint x, y;
1805 
1806 		device_manager = gdk_display_get_device_manager (gtk_widget_get_display(widget));
1807 		device = gdk_device_manager_get_client_pointer (device_manager);
1808 
1809 		gdk_window_get_device_position (gtk_widget_get_window (widget), device,
1810 					&x, &y, NULL);
1811 
1812 		set_zoom (view, zoom, TRUE, x, y, TRUE);    /* need to force a redisplay of the image after
1813                                                        the call of set_zoom_fit() above otherwise
1814                                                         pressing Shift W (or H) after W (or H) has no
1815                                                         effect whereas the image should scroll
1816                                                         (assuming that the height (or width respectively)
1817                                                         is larger than will fit in the window) */
1818 	}
1819 
1820 	if (do_scroll)
1821 		scroll_by (view, xofs, yofs);
1822 
1823 	if(!do_scroll && !do_zoom)
1824 		return FALSE;
1825 
1826 	return TRUE;
1827 }
1828 
1829 
1830 /* Button press event handler for the image view */
1831 static gboolean
xviewer_scroll_view_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer data)1832 xviewer_scroll_view_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data)
1833 {
1834 	XviewerScrollView *view;
1835 	XviewerScrollViewPrivate *priv;
1836 
1837 	view = XVIEWER_SCROLL_VIEW (data);
1838 	priv = view->priv;
1839 
1840 	if (!gtk_widget_has_focus (priv->display))
1841 		gtk_widget_grab_focus (GTK_WIDGET (priv->display));
1842 
1843 	if (priv->dragging)
1844 		return FALSE;
1845 
1846 	switch (event->button) {
1847 		case 1:
1848 		case 2:
1849                         if (event->button == 1 && !priv->scroll_wheel_zoom &&
1850 			    !(event->state & GDK_CONTROL_MASK))
1851 				break;
1852 
1853 			if (is_image_movable (view)) {
1854 				xviewer_scroll_view_set_cursor (view, XVIEWER_SCROLL_VIEW_CURSOR_DRAG);
1855 
1856 				priv->dragging = TRUE;
1857 				priv->drag_anchor_x = event->x;
1858 				priv->drag_anchor_y = event->y;
1859 
1860 				priv->drag_ofs_x = priv->xofs;
1861 				priv->drag_ofs_y = priv->yofs;
1862 
1863 				return TRUE;
1864 			}
1865 		default:
1866 			break;
1867 	}
1868 
1869 	return FALSE;
1870 }
1871 
1872 static void
xviewer_scroll_view_style_set(GtkWidget * widget,GtkStyle * old_style)1873 xviewer_scroll_view_style_set (GtkWidget *widget, GtkStyle *old_style)
1874 {
1875 	GtkStyle *style;
1876 	XviewerScrollViewPrivate *priv;
1877 
1878 	style = gtk_widget_get_style (widget);
1879 	priv = XVIEWER_SCROLL_VIEW (widget)->priv;
1880 
1881 	gtk_widget_set_style (priv->display, style);
1882 }
1883 
1884 
1885 /* Button release event handler for the image view */
1886 static gboolean
xviewer_scroll_view_button_release_event(GtkWidget * widget,GdkEventButton * event,gpointer data)1887 xviewer_scroll_view_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer data)
1888 {
1889 	XviewerScrollView *view;
1890 	XviewerScrollViewPrivate *priv;
1891 
1892 	view = XVIEWER_SCROLL_VIEW (data);
1893 	priv = view->priv;
1894 
1895 	if (!priv->dragging)
1896 		return FALSE;
1897 
1898 	switch (event->button) {
1899 		case 1:
1900 		case 2:
1901 			drag_to (view, event->x, event->y);
1902 			priv->dragging = FALSE;
1903 
1904 			xviewer_scroll_view_set_cursor (view, XVIEWER_SCROLL_VIEW_CURSOR_NORMAL);
1905 			break;
1906 
1907 		default:
1908 			break;
1909 	}
1910 
1911 	return TRUE;
1912 }
1913 
1914 /* Scroll event handler for the image view */
1915 static gboolean
xviewer_scroll_view_scroll_event(GtkWidget * widget,GdkEventScroll * event,gpointer data)1916 xviewer_scroll_view_scroll_event (GtkWidget *widget, GdkEventScroll *event, gpointer data)
1917 {
1918 	XviewerScrollView *view;
1919 	XviewerScrollViewPrivate *priv;
1920 	double zoom_factor;
1921 	int xofs, yofs;
1922     int button_combination;         /* 0 = scroll, 1 = scroll + shift, 2 = scroll + ctrl, 3 = scroll + shift + ctrl
1923                                         4..7 as 0..3 but for tilt wheel */
1924     int action;                     /* 0 = zoom, 1 = vertical pan, 2 = horizontal pan, 3 = next/prev image */
1925     static guint32 mouse_wheel_time = 0;     /* used to debounce the mouse wheel (scroll and tilt)
1926                                                          when used for next/previous image or rotate image */
1927 
1928 
1929 
1930 	view = XVIEWER_SCROLL_VIEW (data);
1931 	priv = view->priv;
1932 
1933 	priv->view_settings = g_settings_new (XVIEWER_CONF_VIEW);
1934 
1935 	/* Compute zoom factor and scrolling offsets; we'll only use either of them */
1936 	/* same as in gtkscrolledwindow.c */
1937 	xofs = gtk_adjustment_get_page_increment (priv->hadj) / 2;
1938 	yofs = gtk_adjustment_get_page_increment (priv->vadj) / 2;
1939 
1940 	switch (event->direction) {
1941 	    case GDK_SCROLL_UP:
1942             button_combination = 0;     /* scroll wheel */
1943 		    break;
1944 
1945 	    case GDK_SCROLL_LEFT:
1946             button_combination = 4;     /* tilt wheel */
1947 		    break;
1948 
1949 	    case GDK_SCROLL_DOWN:
1950             button_combination = 0;     /* scroll wheel */
1951 		    break;
1952 
1953 	    case GDK_SCROLL_RIGHT:
1954             button_combination = 4;     /* tilt wheel */
1955 		    break;
1956 
1957 	    default:
1958 		    g_assert_not_reached ();
1959 		    return FALSE;
1960 	}
1961 
1962     if (event->state & GDK_SHIFT_MASK)
1963         button_combination++;
1964 
1965     if (event->state & GDK_CONTROL_MASK)
1966         button_combination += 2;
1967 
1968     switch (button_combination)
1969     {
1970         case 0:
1971             action = g_settings_get_int(priv->view_settings,
1972 				     XVIEWER_CONF_VIEW_SCROLL_ACTION);
1973             break;
1974         case 1:
1975             action = g_settings_get_int(priv->view_settings,
1976 				     XVIEWER_CONF_VIEW_SCROLL_SHIFT_ACTION);
1977             break;
1978         case 2:
1979             action = g_settings_get_int(priv->view_settings,
1980 				     XVIEWER_CONF_VIEW_SCROLL_CTRL_ACTION);
1981             break;
1982         case 3:
1983             action = g_settings_get_int(priv->view_settings,
1984 				     XVIEWER_CONF_VIEW_SCROLL_SHIFT_CTRL_ACTION);
1985             break;
1986         case 4:
1987             action = g_settings_get_int(priv->view_settings,
1988 				     XVIEWER_CONF_VIEW_TILT_ACTION);
1989             break;
1990         case 5:
1991             action = g_settings_get_int(priv->view_settings,
1992 				     XVIEWER_CONF_VIEW_TILT_SHIFT_ACTION);
1993             break;
1994         case 6:
1995             action = g_settings_get_int(priv->view_settings,
1996 				     XVIEWER_CONF_VIEW_TILT_CTRL_ACTION);
1997             break;
1998         case 7:
1999             action = g_settings_get_int(priv->view_settings,
2000 				     XVIEWER_CONF_VIEW_TILT_SHIFT_CTRL_ACTION);
2001             break;
2002     }
2003 
2004     switch (action)
2005     {
2006         case 0:                             /* zoom */
2007             if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_RIGHT))
2008                 zoom_factor = priv->zoom_multiplier;
2009             else
2010                 zoom_factor = 1.0 / priv->zoom_multiplier;
2011             set_zoom (view, priv->zoom * zoom_factor, TRUE, event->x, event->y, FALSE);
2012             break;
2013 
2014         case 1:                             /* vertical pan */
2015             xofs = 0;
2016             if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_RIGHT))
2017                 yofs = -yofs;
2018             else
2019                 yofs = yofs;
2020 			scroll_by (view, xofs, yofs);
2021             break;
2022 
2023         case 2:                             /* horizontal pan */
2024             yofs = 0;
2025             if ((event->direction == GDK_SCROLL_DOWN) || (event->direction == GDK_SCROLL_RIGHT))
2026                 xofs = xofs;
2027             else
2028                 xofs = -xofs;
2029 			scroll_by (view, xofs, yofs);
2030             break;
2031 
2032         case 3:                             /* move to next/prev image */
2033         {
2034             GdkEventButton button_event;
2035 
2036             button_event.type = GDK_BUTTON_PRESS;
2037             button_event.window = gtk_widget_get_window(widget);
2038             button_event.send_event = TRUE;
2039             button_event.time = g_get_monotonic_time() / 1000;
2040             button_event.x = 0.0;         /* coordinate parameters are irrelevant for this button press */
2041             button_event.y = 0.0;
2042             button_event.axes = NULL;
2043             button_event.state = 0;
2044             if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_LEFT))
2045                 button_event.button = 8;
2046             else
2047                 button_event.button = 9;
2048 
2049             button_event.device = event->device;
2050             button_event.x_root = 0.0;
2051             button_event.y_root = 0.0;
2052 
2053 
2054             if (button_event.time - mouse_wheel_time > 400) /* 400 msec debounce of mouse wheel */
2055             {
2056                 gtk_main_do_event((GdkEvent *)&button_event);
2057 
2058                 mouse_wheel_time = button_event.time;
2059             }
2060 
2061             break;
2062         }
2063 
2064         case 4:                             /* Rotate image 90 CW or CCW */
2065         {
2066             GdkKeymapKey* keys;
2067             gint n_keys;
2068             guint keyval;
2069             guint state;
2070             GdkEventKey key_event;
2071 
2072             keyval = GDK_KEY_R;
2073 
2074             if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_LEFT))
2075                 state = GDK_CONTROL_MASK + GDK_SHIFT_MASK;
2076             else
2077                 state = GDK_CONTROL_MASK;
2078 
2079             gdk_keymap_get_entries_for_keyval(gdk_keymap_get_for_display (gtk_widget_get_display(widget)),
2080                                           keyval,
2081                                           &keys,
2082                                           &n_keys);
2083 
2084 
2085 
2086             key_event.type = GDK_KEY_PRESS;
2087             key_event.window = gtk_widget_get_window(widget);
2088             key_event.send_event = TRUE;
2089             key_event.time = g_get_monotonic_time() / 1000;
2090             key_event.state = state;
2091             key_event.keyval = keyval;
2092             key_event.length = 0;
2093             key_event.string = NULL;
2094             key_event.hardware_keycode = keys[0].keycode;
2095             key_event.group = keys[0].group;
2096             key_event.is_modifier = FALSE;
2097 
2098             if (key_event.time - mouse_wheel_time > 400) /* 400 msec debounce of mouse wheel */
2099             {
2100                                 /* When generating a mouse button event the event structure contains the device
2101                                    ID for the mouse (see case 3 above) and no Gdk-Warning is generated. The Key
2102                                    event structure has no device ID member and Gdk reports a warning that:
2103 
2104                                   "Event with type 8 not holding a GdkDevice. It is most likely synthesized
2105                                    outside Gdk/GTK+"
2106 
2107                                    The following code therefore temporarily suppresses stderr to avoid showing
2108                                    this warning when (given the Gdk implementation) it is expected - and untidy! */
2109                 int old_stderr, new_stderr;
2110 
2111                 fflush(stderr);
2112                 old_stderr = dup(2);
2113                 new_stderr = open("/dev/null", O_WRONLY);
2114                 dup2(new_stderr, 2);
2115                 close(new_stderr);
2116 
2117                 gtk_main_do_event((GdkEvent *)&key_event);
2118 
2119                 fflush(stderr);             /* restore normal stderr output */
2120                 dup2(old_stderr, 2);
2121                 close(old_stderr);
2122 
2123                 mouse_wheel_time = key_event.time;
2124             }
2125             break;
2126         }
2127 
2128 
2129         /* case 5 = no action */
2130     }
2131 
2132 	return TRUE;
2133 }
2134 
2135 /* Motion event handler for the image view */
2136 static gboolean
xviewer_scroll_view_motion_event(GtkWidget * widget,GdkEventMotion * event,gpointer data)2137 xviewer_scroll_view_motion_event (GtkWidget *widget, GdkEventMotion *event, gpointer data)
2138 {
2139 	XviewerScrollView *view;
2140 	XviewerScrollViewPrivate *priv;
2141 	gint x, y;
2142 	GdkModifierType mods;
2143 
2144 	view = XVIEWER_SCROLL_VIEW (data);
2145 	priv = view->priv;
2146 
2147 #if GTK_CHECK_VERSION (3, 14, 0)
2148 	if (gtk_gesture_is_recognized (priv->zoom_gesture))
2149 		return TRUE;
2150 #endif
2151 
2152 	if (!priv->dragging)
2153 		return FALSE;
2154 
2155 	if (event->is_hint)
2156 		gdk_window_get_device_position (gtk_widget_get_window (GTK_WIDGET (priv->display)), event->device, &x, &y, &mods);
2157 	else {
2158 		x = event->x;
2159 		y = event->y;
2160 	}
2161 
2162 	drag_to (view, x, y);
2163 	return TRUE;
2164 }
2165 
2166 static void
display_map_event(GtkWidget * widget,GdkEvent * event,gpointer data)2167 display_map_event (GtkWidget *widget, GdkEvent *event, gpointer data)
2168 {
2169 	XviewerScrollView *view;
2170 	XviewerScrollViewPrivate *priv;
2171 
2172 	view = XVIEWER_SCROLL_VIEW (data);
2173 	priv = view->priv;
2174 
2175 	xviewer_debug (DEBUG_WINDOW);
2176 
2177 	set_zoom_fit (view);
2178 	check_scrollbar_visibility (view, NULL);
2179 	gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2180 }
2181 
2182 static void
xviewer_scroll_view_size_allocate(GtkWidget * widget,GtkAllocation * alloc)2183 xviewer_scroll_view_size_allocate (GtkWidget *widget, GtkAllocation *alloc)
2184 {
2185 	XviewerScrollView *view;
2186 
2187 	view = XVIEWER_SCROLL_VIEW (widget);
2188 	check_scrollbar_visibility (view, alloc);
2189 
2190 	GTK_WIDGET_CLASS (xviewer_scroll_view_parent_class)->size_allocate (widget
2191 									,alloc);
2192 }
2193 
2194 static void
display_size_change(GtkWidget * widget,GdkEventConfigure * event,gpointer data)2195 display_size_change (GtkWidget *widget, GdkEventConfigure *event, gpointer data)
2196 {
2197 	XviewerScrollView *view;
2198 	XviewerScrollViewPrivate *priv;
2199 
2200 	view = XVIEWER_SCROLL_VIEW (data);
2201 	priv = view->priv;
2202 
2203 	if (priv->zoom_mode == XVIEWER_ZOOM_MODE_SHRINK_TO_FIT) {
2204 		GtkAllocation alloc;
2205 
2206 		alloc.width = event->width;
2207 		alloc.height = event->height;
2208 
2209 		set_zoom_fit (view);
2210 		check_scrollbar_visibility (view, &alloc);
2211 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2212 	} else {
2213 		int scaled_width, scaled_height;
2214 		int x_offset = 0;
2215 		int y_offset = 0;
2216 
2217 		compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
2218 
2219 		if (priv->xofs + event->width > scaled_width)
2220 			x_offset = scaled_width - event->width - priv->xofs;
2221 
2222 		if (priv->yofs + event->height > scaled_height)
2223 			y_offset = scaled_height - event->height - priv->yofs;
2224 
2225 		scroll_by (view, x_offset, y_offset);
2226 	}
2227 
2228 	update_scrollbar_values (view);
2229 }
2230 
2231 
2232 static gboolean
xviewer_scroll_view_focus_in_event(GtkWidget * widget,GdkEventFocus * event,gpointer data)2233 xviewer_scroll_view_focus_in_event (GtkWidget     *widget,
2234 			    GdkEventFocus *event,
2235 			    gpointer data)
2236 {
2237 	g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_in_event");
2238 	return FALSE;
2239 }
2240 
2241 static gboolean
xviewer_scroll_view_focus_out_event(GtkWidget * widget,GdkEventFocus * event,gpointer data)2242 xviewer_scroll_view_focus_out_event (GtkWidget     *widget,
2243 			     GdkEventFocus *event,
2244 			     gpointer data)
2245 {
2246 	g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_out_event");
2247 	return FALSE;
2248 }
2249 
2250 static gboolean
display_draw(GtkWidget * widget,cairo_t * cr,gpointer data)2251 display_draw (GtkWidget *widget, cairo_t *cr, gpointer data)
2252 {
2253 	XviewerScrollView *view;
2254 	XviewerScrollViewPrivate *priv;
2255 	GtkAllocation allocation;
2256 	int scaled_width, scaled_height;
2257 	int xofs, yofs;
2258 
2259 	g_return_val_if_fail (GTK_IS_DRAWING_AREA (widget), FALSE);
2260 	g_return_val_if_fail (XVIEWER_IS_SCROLL_VIEW (data), FALSE);
2261 
2262 	view = XVIEWER_SCROLL_VIEW (data);
2263 
2264 	priv = view->priv;
2265 
2266 	if (priv->pixbuf == NULL)
2267 		return TRUE;
2268 
2269 	xviewer_scroll_view_get_image_coords (view, &xofs, &yofs,
2270 	                                  &scaled_width, &scaled_height);
2271 
2272 	xviewer_debug_message (DEBUG_WINDOW, "zoom %.2f, xofs: %i, yofs: %i scaled w: %i h: %i\n",
2273 			   priv->zoom, xofs, yofs, scaled_width, scaled_height);
2274 
2275 	/* Paint the background */
2276 	cairo_set_source (cr, gdk_window_get_background_pattern (gtk_widget_get_window (priv->display)));
2277 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
2278 	cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
2279 	cairo_rectangle (cr, MAX (0, xofs), MAX (0, yofs),
2280 			 scaled_width, scaled_height);
2281 	cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
2282 	cairo_fill (cr);
2283 
2284 	if (gdk_pixbuf_get_has_alpha (priv->pixbuf)) {
2285 		if (priv->background_surface == NULL) {
2286 			priv->background_surface = create_background_surface (view);
2287 		}
2288 		cairo_set_source_surface (cr, priv->background_surface, xofs, yofs);
2289 		cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
2290 		cairo_rectangle (cr, xofs, yofs, scaled_width, scaled_height);
2291 		cairo_fill (cr);
2292 	}
2293 
2294 	/* Make sure the image is only drawn as large as needed.
2295 	 * This is especially necessary for SVGs where there might
2296 	 * be more image data available outside the image boundaries.
2297 	 */
2298 	cairo_rectangle (cr, xofs, yofs, scaled_width, scaled_height);
2299 	cairo_clip (cr);
2300 
2301 #ifdef HAVE_RSVG
2302 	if (xviewer_image_is_svg (view->priv->image)) {
2303 		cairo_matrix_t matrix, translate, scale, original;
2304 		XviewerTransform *transform = xviewer_image_get_transform (priv->image);
2305 		cairo_matrix_init_identity (&matrix);
2306 		if (transform) {
2307 			cairo_matrix_t affine;
2308 			double image_offset_x = 0., image_offset_y = 0.;
2309 
2310 			xviewer_transform_get_affine (transform, &affine);
2311 			cairo_matrix_multiply (&matrix, &affine, &matrix);
2312 
2313 			switch (xviewer_transform_get_transform_type (transform)) {
2314 			case XVIEWER_TRANSFORM_ROT_90:
2315 			case XVIEWER_TRANSFORM_FLIP_HORIZONTAL:
2316 				image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf);
2317 				break;
2318 			case XVIEWER_TRANSFORM_ROT_270:
2319 			case XVIEWER_TRANSFORM_FLIP_VERTICAL:
2320 				image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf);
2321 				break;
2322 			case XVIEWER_TRANSFORM_ROT_180:
2323 			case XVIEWER_TRANSFORM_TRANSPOSE:
2324 			case XVIEWER_TRANSFORM_TRANSVERSE:
2325 				image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf);
2326 				image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf);
2327 				break;
2328 			case XVIEWER_TRANSFORM_NONE:
2329 			default:
2330 				break;
2331 			}
2332 			cairo_matrix_init_translate (&translate, image_offset_x, image_offset_y);
2333 			cairo_matrix_multiply (&matrix, &matrix, &translate);
2334 		}
2335 		cairo_matrix_init_scale (&scale, priv->zoom, priv->zoom);
2336 		cairo_matrix_multiply (&matrix, &matrix, &scale);
2337 		cairo_matrix_init_translate (&translate, xofs, yofs);
2338 		cairo_matrix_multiply (&matrix, &matrix, &translate);
2339 
2340 		cairo_get_matrix (cr, &original);
2341 		cairo_matrix_multiply (&matrix, &matrix, &original);
2342 		cairo_set_matrix (cr, &matrix);
2343 
2344 		rsvg_handle_render_cairo (xviewer_image_get_svg (priv->image), cr);
2345 
2346 	} else
2347 #endif /* HAVE_RSVG */
2348 	{
2349 		cairo_filter_t interp_type;
2350 
2351 		if(!DOUBLE_EQUAL(priv->zoom, 1.0) && priv->force_unfiltered)
2352 		{
2353 			if ((is_zoomed_in (view) && priv->interp_type_in != CAIRO_FILTER_NEAREST) ||
2354 				(is_zoomed_out (view) && priv->interp_type_out != CAIRO_FILTER_NEAREST)) {
2355 				// CAIRO_FILTER_GOOD is too slow during zoom changes, so use CAIRO_FILTER_BILINEAR instead
2356 				/* interp_type = CAIRO_FILTER_BILINEAR; */
2357                 interp_type = CAIRO_FILTER_GOOD;    /* revert to using CAIRO_FILTER_GOOD to fix issue #24.
2358                                                         Using CAIRO_FILTER_BILINEAR results in a blank display
2359                                                         for the affected images - it also aeems to be slower than
2360                                                         CAIRO_FILTER_GOOD (as of 26.9.2021) in contradiction to the
2361                                                         earlier statement. */
2362 			}
2363 			else {
2364 				interp_type = CAIRO_FILTER_NEAREST;
2365 			}
2366 			_set_hq_redraw_timeout(view);
2367 		}
2368 		else
2369 		{
2370 			if (is_zoomed_in (view))
2371 				interp_type = priv->interp_type_in;
2372 			else
2373 				interp_type = priv->interp_type_out;
2374 
2375 			_clear_hq_redraw_timeout (view);
2376 			priv->force_unfiltered = TRUE;
2377 		}
2378 		cairo_scale (cr, priv->zoom, priv->zoom);
2379 		cairo_set_source_surface (cr, priv->surface, xofs/priv->zoom, yofs/priv->zoom);
2380 		cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
2381 		if (is_zoomed_in (view) || is_zoomed_out (view))
2382 			cairo_pattern_set_filter (cairo_get_source (cr), interp_type);
2383 		cairo_paint (cr);
2384 	}
2385 
2386 	return TRUE;
2387 }
2388 
2389 #if GTK_CHECK_VERSION (3, 14, 0)
2390 static void
zoom_gesture_begin_cb(GtkGestureZoom * gesture,GdkEventSequence * sequence,XviewerScrollView * view)2391 zoom_gesture_begin_cb (GtkGestureZoom   *gesture,
2392 		       GdkEventSequence *sequence,
2393 		       XviewerScrollView    *view)
2394 {
2395 	gdouble center_x, center_y;
2396 	XviewerScrollViewPrivate *priv;
2397 
2398 	priv = view->priv;
2399 
2400 	/* Displace dragging point to gesture center */
2401 	gtk_gesture_get_bounding_box_center (GTK_GESTURE (gesture),
2402                                              &center_x, &center_y);
2403 	priv->drag_anchor_x = center_x;
2404 	priv->drag_anchor_y = center_y;
2405 	priv->drag_ofs_x = priv->xofs;
2406 	priv->drag_ofs_y = priv->yofs;
2407 	priv->dragging = TRUE;
2408 	priv->initial_zoom = priv->zoom;
2409 
2410         gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
2411 }
2412 
2413 static void
zoom_gesture_update_cb(GtkGestureZoom * gesture,GdkEventSequence * sequence,XviewerScrollView * view)2414 zoom_gesture_update_cb (GtkGestureZoom   *gesture,
2415 			GdkEventSequence *sequence,
2416 			XviewerScrollView    *view)
2417 {
2418 	gdouble center_x, center_y, scale;
2419 	XviewerScrollViewPrivate *priv;
2420 
2421 	priv = view->priv;
2422 	scale = gtk_gesture_zoom_get_scale_delta (gesture);
2423 	gtk_gesture_get_bounding_box_center (GTK_GESTURE (gesture),
2424                                              &center_x, &center_y);
2425 
2426 	drag_to (view, center_x, center_y);
2427 	set_zoom (view, priv->initial_zoom * scale, TRUE,
2428 		  center_x, center_y, FALSE);
2429 }
2430 
2431 static void
zoom_gesture_end_cb(GtkGestureZoom * gesture,GdkEventSequence * sequence,XviewerScrollView * view)2432 zoom_gesture_end_cb (GtkGestureZoom   *gesture,
2433 		     GdkEventSequence *sequence,
2434 		     XviewerScrollView    *view)
2435 {
2436 	XviewerScrollViewPrivate *priv;
2437 
2438 	priv = view->priv;
2439 	priv->dragging = FALSE;
2440         xviewer_scroll_view_set_cursor (view, XVIEWER_SCROLL_VIEW_CURSOR_NORMAL);
2441 }
2442 
2443 static void
rotate_gesture_begin_cb(GtkGesture * gesture,GdkEventSequence * sequence,XviewerScrollView * view)2444 rotate_gesture_begin_cb (GtkGesture       *gesture,
2445 			 GdkEventSequence *sequence,
2446 			 XviewerScrollView    *view)
2447 {
2448 	XviewerScrollViewPrivate *priv;
2449 
2450 	priv = view->priv;
2451 	priv->rotate_state = XVIEWER_TRANSFORM_NONE;
2452 }
2453 
2454 static void
pan_gesture_pan_cb(GtkGesturePan * gesture,GtkPanDirection direction,gdouble offset,XviewerScrollView * view)2455 pan_gesture_pan_cb (GtkGesturePan   *gesture,
2456 		    GtkPanDirection  direction,
2457 		    gdouble          offset,
2458 		    XviewerScrollView   *view)
2459 {
2460 	XviewerScrollViewPrivate *priv;
2461 
2462 	if (xviewer_scroll_view_scrollbars_visible (view)) {
2463 		gtk_gesture_set_state (GTK_GESTURE (gesture),
2464 				       GTK_EVENT_SEQUENCE_DENIED);
2465 		return;
2466 	}
2467 
2468 #define PAN_ACTION_DISTANCE 200
2469 
2470 	priv = view->priv;
2471 	priv->pan_action = XVIEWER_PAN_ACTION_NONE;
2472 	gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
2473 
2474 	if (offset > PAN_ACTION_DISTANCE) {
2475 		if (direction == GTK_PAN_DIRECTION_LEFT ||
2476 		    gtk_widget_get_direction (GTK_WIDGET (view)) == GTK_TEXT_DIR_RTL)
2477 			priv->pan_action = XVIEWER_PAN_ACTION_NEXT;
2478 		else
2479 			priv->pan_action = XVIEWER_PAN_ACTION_PREV;
2480 	}
2481 #undef PAN_ACTION_DISTANCE
2482 }
2483 
2484 static void
pan_gesture_end_cb(GtkGesture * gesture,GdkEventSequence * sequence,XviewerScrollView * view)2485 pan_gesture_end_cb (GtkGesture       *gesture,
2486 		    GdkEventSequence *sequence,
2487 		    XviewerScrollView    *view)
2488 {
2489 	XviewerScrollViewPrivate *priv;
2490 
2491 	if (!gtk_gesture_handles_sequence (gesture, sequence))
2492 		return;
2493 
2494 	priv = view->priv;
2495 
2496 	if (priv->pan_action == XVIEWER_PAN_ACTION_PREV)
2497 		g_signal_emit (view, view_signals [SIGNAL_PREVIOUS_IMAGE], 0);
2498 	else if (priv->pan_action == XVIEWER_PAN_ACTION_NEXT)
2499 		g_signal_emit (view, view_signals [SIGNAL_NEXT_IMAGE], 0);
2500 
2501 	priv->pan_action = XVIEWER_PAN_ACTION_NONE;
2502 }
2503 #endif
2504 
2505 static gboolean
scroll_view_check_angle(gdouble angle,gdouble min,gdouble max,gdouble threshold)2506 scroll_view_check_angle (gdouble angle,
2507 			 gdouble min,
2508 			 gdouble max,
2509 			 gdouble threshold)
2510 {
2511 	if (min < max) {
2512 		return (angle > min - threshold &&
2513 			angle < max + threshold);
2514 	} else {
2515 		return (angle < max + threshold ||
2516 			angle > min - threshold);
2517 	}
2518 }
2519 
2520 static XviewerRotationState
scroll_view_get_rotate_state(XviewerScrollView * view,gdouble delta)2521 scroll_view_get_rotate_state (XviewerScrollView *view,
2522 			      gdouble        delta)
2523 {
2524 	XviewerScrollViewPrivate *priv;
2525 
2526 	priv = view->priv;
2527 
2528 #define THRESHOLD (G_PI / 16)
2529 	switch (priv->rotate_state) {
2530 	case XVIEWER_ROTATION_0:
2531 		if (scroll_view_check_angle (delta, G_PI * 7 / 4,
2532 					     G_PI / 4, THRESHOLD))
2533 			return priv->rotate_state;
2534 		break;
2535 	case XVIEWER_ROTATION_90:
2536 		if (scroll_view_check_angle (delta, G_PI / 4,
2537 					     G_PI * 3 / 4, THRESHOLD))
2538 			return priv->rotate_state;
2539 		break;
2540 	case XVIEWER_ROTATION_180:
2541 		if (scroll_view_check_angle (delta, G_PI * 3 / 4,
2542 					     G_PI * 5 / 4, THRESHOLD))
2543 			return priv->rotate_state;
2544 		break;
2545 	case XVIEWER_ROTATION_270:
2546 		if (scroll_view_check_angle (delta, G_PI * 5 / 4,
2547 					     G_PI * 7 / 4, THRESHOLD))
2548 			return priv->rotate_state;
2549 		break;
2550 	default:
2551 		g_assert_not_reached ();
2552 	}
2553 
2554 #undef THRESHOLD
2555 
2556 	if (scroll_view_check_angle (delta, G_PI / 4, G_PI * 3 / 4, 0))
2557 		return XVIEWER_ROTATION_90;
2558 	else if (scroll_view_check_angle (delta, G_PI * 3 / 4, G_PI * 5 / 4, 0))
2559 		return XVIEWER_ROTATION_180;
2560 	else if (scroll_view_check_angle (delta, G_PI * 5 / 4, G_PI * 7 / 4, 0))
2561 		return XVIEWER_ROTATION_270;
2562 
2563 	return XVIEWER_ROTATION_0;
2564 }
2565 
2566 #if GTK_CHECK_VERSION (3, 14, 0)
2567 static void
rotate_gesture_angle_changed_cb(GtkGestureRotate * rotate,gdouble angle,gdouble delta,XviewerScrollView * view)2568 rotate_gesture_angle_changed_cb (GtkGestureRotate *rotate,
2569 				 gdouble           angle,
2570 				 gdouble           delta,
2571 				 XviewerScrollView    *view)
2572 {
2573 	XviewerRotationState rotate_state;
2574 	XviewerScrollViewPrivate *priv;
2575 	gint angle_diffs [N_XVIEWER_ROTATIONS][N_XVIEWER_ROTATIONS] = {
2576 		{ 0,   90,  180, 270 },
2577 		{ 270, 0,   90,  180 },
2578 		{ 180, 270, 0,   90 },
2579 		{ 90,  180, 270, 0 }
2580 	};
2581 	gint rotate_angle;
2582 
2583 	priv = view->priv;
2584 	rotate_state = scroll_view_get_rotate_state (view, delta);
2585 
2586 	if (priv->rotate_state == rotate_state)
2587 		return;
2588 
2589 	rotate_angle = angle_diffs[priv->rotate_state][rotate_state];
2590 	g_signal_emit (view, view_signals [SIGNAL_ROTATION_CHANGED], 0, (gdouble) rotate_angle);
2591 	priv->rotate_state = rotate_state;
2592 }
2593 #endif
2594 
2595 /*==================================
2596 
2597    image loading callbacks
2598 
2599    -----------------------------------*/
2600 /*
2601 static void
2602 image_loading_update_cb (XviewerImage *img, int x, int y, int width, int height, gpointer data)
2603 {
2604 	XviewerScrollView *view;
2605 	XviewerScrollViewPrivate *priv;
2606 	GdkRectangle area;
2607 	int xofs, yofs;
2608 	int sx0, sy0, sx1, sy1;
2609 
2610 	view = (XviewerScrollView*) data;
2611 	priv = view->priv;
2612 
2613 	xviewer_debug_message (DEBUG_IMAGE_LOAD, "x: %i, y: %i, width: %i, height: %i\n",
2614 			   x, y, width, height);
2615 
2616 	if (priv->pixbuf == NULL) {
2617 		priv->pixbuf = xviewer_image_get_pixbuf (img);
2618 		set_zoom_fit (view);
2619 		check_scrollbar_visibility (view, NULL);
2620 	}
2621 	priv->progressive_state = PROGRESSIVE_LOADING;
2622 
2623 	get_image_offsets (view, &xofs, &yofs);
2624 
2625 	sx0 = floor (x * priv->zoom + xofs);
2626 	sy0 = floor (y * priv->zoom + yofs);
2627 	sx1 = ceil ((x + width) * priv->zoom + xofs);
2628 	sy1 = ceil ((y + height) * priv->zoom + yofs);
2629 
2630 	area.x = sx0;
2631 	area.y = sy0;
2632 	area.width = sx1 - sx0;
2633 	area.height = sy1 - sy0;
2634 
2635 	if (GTK_WIDGET_DRAWABLE (priv->display))
2636 		gdk_window_invalidate_rect (GTK_WIDGET (priv->display)->window, &area, FALSE);
2637 }
2638 
2639 
2640 static void
2641 image_loading_finished_cb (XviewerImage *img, gpointer data)
2642 {
2643 	XviewerScrollView *view;
2644 	XviewerScrollViewPrivate *priv;
2645 
2646 	view = (XviewerScrollView*) data;
2647 	priv = view->priv;
2648 
2649 	if (priv->pixbuf == NULL) {
2650 		priv->pixbuf = xviewer_image_get_pixbuf (img);
2651 		priv->progressive_state = PROGRESSIVE_NONE;
2652 		set_zoom_fit (view);
2653 		check_scrollbar_visibility (view, NULL);
2654 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2655 
2656 	}
2657 	else if (priv->interp_type != CAIRO_FILTER_NEAREST &&
2658 		 !is_unity_zoom (view))
2659 	{
2660 		// paint antialiased image version
2661 		priv->progressive_state = PROGRESSIVE_POLISHING;
2662 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2663 	}
2664 }
2665 
2666 static void
2667 image_loading_failed_cb (XviewerImage *img, char *msg, gpointer data)
2668 {
2669 	XviewerScrollViewPrivate *priv;
2670 
2671 	priv = XVIEWER_SCROLL_VIEW (data)->priv;
2672 
2673 	g_print ("loading failed: %s.\n", msg);
2674 
2675 	if (priv->pixbuf != 0) {
2676 		g_object_unref (priv->pixbuf);
2677 		priv->pixbuf = 0;
2678 	}
2679 
2680 	if (GTK_WIDGET_DRAWABLE (priv->display)) {
2681 		gdk_window_clear (GTK_WIDGET (priv->display)->window);
2682 	}
2683 }
2684 
2685 static void
2686 image_loading_cancelled_cb (XviewerImage *img, gpointer data)
2687 {
2688 	XviewerScrollViewPrivate *priv;
2689 
2690 	priv = XVIEWER_SCROLL_VIEW (data)->priv;
2691 
2692 	if (priv->pixbuf != NULL) {
2693 		g_object_unref (priv->pixbuf);
2694 		priv->pixbuf = NULL;
2695 	}
2696 
2697 	if (GTK_WIDGET_DRAWABLE (priv->display)) {
2698 		gdk_window_clear (GTK_WIDGET (priv->display)->window);
2699 	}
2700 }
2701 */
2702 
2703 /* Use when the pixbuf in the view is changed, to keep a
2704    reference to it and create its cairo surface. */
2705 static void
update_pixbuf(XviewerScrollView * view,GdkPixbuf * pixbuf)2706 update_pixbuf (XviewerScrollView *view, GdkPixbuf *pixbuf)
2707 {
2708 	XviewerScrollViewPrivate *priv;
2709 
2710 	priv = view->priv;
2711 
2712 	if (priv->pixbuf != NULL) {
2713 		g_object_unref (priv->pixbuf);
2714 		priv->pixbuf = NULL;
2715 	}
2716 
2717 	priv->pixbuf = pixbuf;
2718 
2719 	if (priv->surface) {
2720 		cairo_surface_destroy (priv->surface);
2721 	}
2722 	priv->surface = create_surface_from_pixbuf (view, priv->pixbuf);
2723 }
2724 
2725 static void
image_changed_cb(XviewerImage * img,gpointer data)2726 image_changed_cb (XviewerImage *img, gpointer data)
2727 {
2728 	update_pixbuf (XVIEWER_SCROLL_VIEW (data), xviewer_image_get_pixbuf (img));
2729 
2730 	_set_zoom_mode_internal (XVIEWER_SCROLL_VIEW (data),
2731 				 XVIEWER_ZOOM_MODE_SHRINK_TO_FIT);
2732 }
2733 
2734 /*===================================
2735          public API
2736   ---------------------------------*/
2737 
2738 void
xviewer_scroll_view_hide_cursor(XviewerScrollView * view)2739 xviewer_scroll_view_hide_cursor (XviewerScrollView *view)
2740 {
2741        xviewer_scroll_view_set_cursor (view, XVIEWER_SCROLL_VIEW_CURSOR_HIDDEN);
2742 }
2743 
2744 void
xviewer_scroll_view_show_cursor(XviewerScrollView * view)2745 xviewer_scroll_view_show_cursor (XviewerScrollView *view)
2746 {
2747        xviewer_scroll_view_set_cursor (view, XVIEWER_SCROLL_VIEW_CURSOR_NORMAL);
2748 }
2749 
2750 /* general properties */
2751 void
xviewer_scroll_view_set_zoom_upscale(XviewerScrollView * view,gboolean upscale)2752 xviewer_scroll_view_set_zoom_upscale (XviewerScrollView *view, gboolean upscale)
2753 {
2754 	XviewerScrollViewPrivate *priv;
2755 
2756 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2757 
2758 	priv = view->priv;
2759 
2760 	if (priv->upscale != upscale) {
2761 		priv->upscale = upscale;
2762 
2763 		if (priv->zoom_mode == XVIEWER_ZOOM_MODE_SHRINK_TO_FIT) {
2764 			set_zoom_fit (view);
2765 			gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2766 		}
2767 	}
2768 }
2769 
2770 void
xviewer_scroll_view_set_antialiasing_in(XviewerScrollView * view,gboolean state)2771 xviewer_scroll_view_set_antialiasing_in (XviewerScrollView *view, gboolean state)
2772 {
2773 	XviewerScrollViewPrivate *priv;
2774 	cairo_filter_t new_interp_type;
2775 
2776 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2777 
2778 	priv = view->priv;
2779 
2780 	new_interp_type = state ? CAIRO_FILTER_GOOD : CAIRO_FILTER_NEAREST;
2781 
2782 	if (priv->interp_type_in != new_interp_type) {
2783 		priv->interp_type_in = new_interp_type;
2784 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2785 		g_object_notify (G_OBJECT (view), "antialiasing-in");
2786 	}
2787 }
2788 
2789 void
xviewer_scroll_view_set_antialiasing_out(XviewerScrollView * view,gboolean state)2790 xviewer_scroll_view_set_antialiasing_out (XviewerScrollView *view, gboolean state)
2791 {
2792 	XviewerScrollViewPrivate *priv;
2793 	cairo_filter_t new_interp_type;
2794 
2795 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2796 
2797 	priv = view->priv;
2798 
2799 	new_interp_type = state ? CAIRO_FILTER_GOOD : CAIRO_FILTER_NEAREST;
2800 
2801 	if (priv->interp_type_out != new_interp_type) {
2802 		priv->interp_type_out = new_interp_type;
2803 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2804 		g_object_notify (G_OBJECT (view), "antialiasing-out");
2805 	}
2806 }
2807 
2808 static void
_transp_background_changed(XviewerScrollView * view)2809 _transp_background_changed (XviewerScrollView *view)
2810 {
2811 	XviewerScrollViewPrivate *priv = view->priv;
2812 
2813 	if (priv->pixbuf != NULL && gdk_pixbuf_get_has_alpha (priv->pixbuf)) {
2814 		if (priv->background_surface) {
2815 			cairo_surface_destroy (priv->background_surface);
2816 			/* Will be recreated if needed during redraw */
2817 			priv->background_surface = NULL;
2818 		}
2819 		gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2820 	}
2821 
2822 }
2823 
2824 void
xviewer_scroll_view_set_transparency_color(XviewerScrollView * view,GdkRGBA * color)2825 xviewer_scroll_view_set_transparency_color (XviewerScrollView *view, GdkRGBA *color)
2826 {
2827 	XviewerScrollViewPrivate *priv;
2828 
2829 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2830 
2831 	priv = view->priv;
2832 
2833 	if (!_xviewer_gdk_rgba_equal0 (&priv->transp_color, color)) {
2834 		priv->transp_color = *color;
2835 		if (priv->transp_style == XVIEWER_TRANSP_COLOR)
2836 			_transp_background_changed (view);
2837 
2838 		g_object_notify (G_OBJECT (view), "transparency-color");
2839 	}
2840 }
2841 
2842 void
xviewer_scroll_view_set_transparency(XviewerScrollView * view,XviewerTransparencyStyle style)2843 xviewer_scroll_view_set_transparency (XviewerScrollView        *view,
2844 				  XviewerTransparencyStyle  style)
2845 {
2846 	XviewerScrollViewPrivate *priv;
2847 
2848 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2849 
2850 	priv = view->priv;
2851 
2852 	if (priv->transp_style != style) {
2853 		priv->transp_style = style;
2854 		_transp_background_changed (view);
2855 		g_object_notify (G_OBJECT (view), "transparency-style");
2856 	}
2857 }
2858 
2859 /* zoom api */
2860 
2861 static double preferred_zoom_levels[] = {
2862 	1.0 / 100, 1.0 / 50, 1.0 / 20,
2863 	1.0 / 10.0, 1.0 / 5.0, 1.0 / 3.0, 1.0 / 2.0, 1.0 / 1.5,
2864         1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
2865         11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0
2866 };
2867 static const gint n_zoom_levels = (sizeof (preferred_zoom_levels) / sizeof (double));
2868 
2869 void
xviewer_scroll_view_zoom_in(XviewerScrollView * view,gboolean smooth)2870 xviewer_scroll_view_zoom_in (XviewerScrollView *view, gboolean smooth)
2871 {
2872 	XviewerScrollViewPrivate *priv;
2873 	double zoom;
2874 
2875 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2876 
2877 	priv = view->priv;
2878 
2879 	if (smooth) {
2880 		zoom = priv->zoom * priv->zoom_multiplier;
2881 	}
2882 	else {
2883 		int i;
2884 		int index = -1;
2885 
2886 		for (i = 0; i < n_zoom_levels; i++) {
2887 			if (preferred_zoom_levels [i] - priv->zoom
2888 					> DOUBLE_EQUAL_MAX_DIFF) {
2889 				index = i;
2890 				break;
2891 			}
2892 		}
2893 
2894 		if (index == -1) {
2895 			zoom = priv->zoom;
2896 		}
2897 		else {
2898 			zoom = preferred_zoom_levels [i];
2899 		}
2900 	}
2901 	set_zoom (view, zoom, FALSE, 0, 0, FALSE);
2902 
2903 }
2904 
2905 void
xviewer_scroll_view_zoom_out(XviewerScrollView * view,gboolean smooth)2906 xviewer_scroll_view_zoom_out (XviewerScrollView *view, gboolean smooth)
2907 {
2908 	XviewerScrollViewPrivate *priv;
2909 	double zoom;
2910 
2911 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2912 
2913 	priv = view->priv;
2914 
2915 	if (smooth) {
2916 		zoom = priv->zoom / priv->zoom_multiplier;
2917 	}
2918 	else {
2919 		int i;
2920 		int index = -1;
2921 
2922 		for (i = n_zoom_levels - 1; i >= 0; i--) {
2923 			if (priv->zoom - preferred_zoom_levels [i]
2924 					> DOUBLE_EQUAL_MAX_DIFF) {
2925 				index = i;
2926 				break;
2927 			}
2928 		}
2929 		if (index == -1) {
2930 			zoom = priv->zoom;
2931 		}
2932 		else {
2933 			zoom = preferred_zoom_levels [i];
2934 		}
2935 	}
2936 	set_zoom (view, zoom, FALSE, 0, 0, FALSE);
2937 }
2938 
2939 static void
xviewer_scroll_view_zoom_fit(XviewerScrollView * view)2940 xviewer_scroll_view_zoom_fit (XviewerScrollView *view)
2941 {
2942 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2943 
2944 	set_zoom_fit (view);
2945 	check_scrollbar_visibility (view, NULL);
2946 	gtk_widget_queue_draw (GTK_WIDGET (view->priv->display));
2947 }
2948 
2949 void
xviewer_scroll_view_set_zoom(XviewerScrollView * view,double zoom)2950 xviewer_scroll_view_set_zoom (XviewerScrollView *view, double zoom)
2951 {
2952 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
2953 
2954 	set_zoom (view, zoom, FALSE, 0, 0, FALSE);
2955 }
2956 
2957 double
xviewer_scroll_view_get_zoom(XviewerScrollView * view)2958 xviewer_scroll_view_get_zoom (XviewerScrollView *view)
2959 {
2960 	g_return_val_if_fail (XVIEWER_IS_SCROLL_VIEW (view), 0.0);
2961 
2962 	return view->priv->zoom;
2963 }
2964 
2965 gboolean
xviewer_scroll_view_get_zoom_is_min(XviewerScrollView * view)2966 xviewer_scroll_view_get_zoom_is_min (XviewerScrollView *view)
2967 {
2968 	g_return_val_if_fail (XVIEWER_IS_SCROLL_VIEW (view), FALSE);
2969 
2970 	set_minimum_zoom_factor (view);
2971 
2972 	return DOUBLE_EQUAL (view->priv->zoom, MIN_ZOOM_FACTOR) ||
2973 	       DOUBLE_EQUAL (view->priv->zoom, view->priv->min_zoom);
2974 }
2975 
2976 gboolean
xviewer_scroll_view_get_zoom_is_max(XviewerScrollView * view)2977 xviewer_scroll_view_get_zoom_is_max (XviewerScrollView *view)
2978 {
2979 	g_return_val_if_fail (XVIEWER_IS_SCROLL_VIEW (view), FALSE);
2980 
2981 	return DOUBLE_EQUAL (view->priv->zoom, MAX_ZOOM_FACTOR);
2982 }
2983 
2984 static void
display_next_frame_cb(XviewerImage * image,gint delay,gpointer data)2985 display_next_frame_cb (XviewerImage *image, gint delay, gpointer data)
2986 {
2987  	XviewerScrollViewPrivate *priv;
2988 	XviewerScrollView *view;
2989 
2990 	if (!XVIEWER_IS_SCROLL_VIEW (data))
2991 		return;
2992 
2993 	view = XVIEWER_SCROLL_VIEW (data);
2994 	priv = view->priv;
2995 
2996 	update_pixbuf (view, xviewer_image_get_pixbuf (image));
2997 
2998 	gtk_widget_queue_draw (GTK_WIDGET (priv->display));
2999 }
3000 
3001 void
xviewer_scroll_view_set_image(XviewerScrollView * view,XviewerImage * image)3002 xviewer_scroll_view_set_image (XviewerScrollView *view, XviewerImage *image)
3003 {
3004 	XviewerScrollViewPrivate *priv;
3005 
3006 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
3007 
3008 	priv = view->priv;
3009 
3010 	if (priv->image == image) {
3011 		return;
3012 	}
3013 
3014 	if (priv->image != NULL) {
3015 		free_image_resources (view);
3016 	}
3017 	g_assert (priv->image == NULL);
3018 	g_assert (priv->pixbuf == NULL);
3019 
3020 	/* priv->progressive_state = PROGRESSIVE_NONE; */
3021 	if (image != NULL) {
3022 		xviewer_image_data_ref (image);
3023 
3024 		if (priv->pixbuf == NULL) {
3025 			update_pixbuf (view, xviewer_image_get_pixbuf (image));
3026 			/* priv->progressive_state = PROGRESSIVE_NONE; */
3027 			_set_zoom_mode_internal (view,
3028 						 XVIEWER_ZOOM_MODE_SHRINK_TO_FIT);
3029 
3030 		}
3031 #if 0
3032 		else if ((is_zoomed_in (view) && priv->interp_type_in != CAIRO_FILTER_NEAREST) ||
3033 			 (is_zoomed_out (view) && priv->interp_type_out != CAIRO_FILTER_NEAREST))
3034 		{
3035 			/* paint antialiased image version */
3036 			priv->progressive_state = PROGRESSIVE_POLISHING;
3037 			gtk_widget_queue_draw (GTK_WIDGET (priv->display));
3038 		}
3039 #endif
3040 
3041 		priv->image_changed_id = g_signal_connect (image, "changed",
3042 							   (GCallback) image_changed_cb, view);
3043 		if (xviewer_image_is_animation (image) == TRUE ) {
3044 			xviewer_image_start_animation (image);
3045 			priv->frame_changed_id = g_signal_connect (image, "next-frame",
3046 								    (GCallback) display_next_frame_cb, view);
3047 		}
3048 	}
3049 
3050 	priv->image = image;
3051 
3052 	g_object_notify (G_OBJECT (view), "image");
3053 }
3054 
3055 /**
3056  * xviewer_scroll_view_get_image:
3057  * @view: An #XviewerScrollView.
3058  *
3059  * Gets the the currently displayed #XviewerImage.
3060  *
3061  * Returns: (transfer full): An #XviewerImage.
3062  **/
3063 XviewerImage*
xviewer_scroll_view_get_image(XviewerScrollView * view)3064 xviewer_scroll_view_get_image (XviewerScrollView *view)
3065 {
3066 	XviewerImage *img;
3067 
3068 	g_return_val_if_fail (XVIEWER_IS_SCROLL_VIEW (view), NULL);
3069 
3070 	img = view->priv->image;
3071 
3072 	if (img != NULL)
3073 		g_object_ref (img);
3074 
3075 	return img;
3076 }
3077 
3078 gboolean
xviewer_scroll_view_scrollbars_visible(XviewerScrollView * view)3079 xviewer_scroll_view_scrollbars_visible (XviewerScrollView *view)
3080 {
3081 	if (!gtk_widget_get_visible (GTK_WIDGET (view->priv->hbar)) &&
3082 	    !gtk_widget_get_visible (GTK_WIDGET (view->priv->vbar)))
3083 		return FALSE;
3084 
3085 	return TRUE;
3086 }
3087 
3088 /*===================================
3089     object creation/freeing
3090   ---------------------------------*/
3091 
3092 static gboolean
sv_string_to_rgba_mapping(GValue * value,GVariant * variant,gpointer user_data)3093 sv_string_to_rgba_mapping (GValue   *value,
3094 			    GVariant *variant,
3095 			    gpointer  user_data)
3096 {
3097 	GdkRGBA color;
3098 
3099 	g_return_val_if_fail (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING), FALSE);
3100 
3101 	if (gdk_rgba_parse (&color, g_variant_get_string (variant, NULL))) {
3102 		g_value_set_boxed (value, &color);
3103 		return TRUE;
3104 	}
3105 
3106 	return FALSE;
3107 }
3108 
3109 static GVariant*
sv_rgba_to_string_mapping(const GValue * value,const GVariantType * expected_type,gpointer user_data)3110 sv_rgba_to_string_mapping (const GValue       *value,
3111 			    const GVariantType *expected_type,
3112 			    gpointer            user_data)
3113 {
3114 	GVariant *variant = NULL;
3115 	GdkRGBA *color;
3116 	gchar *hex_val;
3117 
3118 	g_return_val_if_fail (G_VALUE_TYPE (value) == GDK_TYPE_RGBA, NULL);
3119 	g_return_val_if_fail (g_variant_type_equal (expected_type, G_VARIANT_TYPE_STRING), NULL);
3120 
3121 	color = g_value_get_boxed (value);
3122 	hex_val = gdk_rgba_to_string(color);
3123 	variant = g_variant_new_string (hex_val);
3124 	g_free (hex_val);
3125 
3126 	return variant;
3127 }
3128 
3129 static void
xviewer_scroll_view_init(XviewerScrollView * view)3130 xviewer_scroll_view_init (XviewerScrollView *view)
3131 {
3132 	GSettings *settings;
3133 	XviewerScrollViewPrivate *priv;
3134 
3135 	priv = view->priv = xviewer_scroll_view_get_instance_private (view);
3136 	settings = g_settings_new (XVIEWER_CONF_VIEW);
3137 
3138 	priv->zoom = 1.0;
3139 	priv->min_zoom = MIN_ZOOM_FACTOR;
3140 	priv->zoom_mode = XVIEWER_ZOOM_MODE_SHRINK_TO_FIT;
3141 	priv->upscale = FALSE;
3142 	//priv->uta = NULL;
3143 	priv->interp_type_in = CAIRO_FILTER_GOOD;
3144 	priv->interp_type_out = CAIRO_FILTER_GOOD;
3145 	priv->scroll_wheel_zoom = FALSE;
3146 	priv->zoom_multiplier = IMAGE_VIEW_ZOOM_MULTIPLIER;
3147 	priv->image = NULL;
3148 	priv->pixbuf = NULL;
3149 	priv->surface = NULL;
3150 	/* priv->progressive_state = PROGRESSIVE_NONE; */
3151 	priv->transp_style = XVIEWER_TRANSP_BACKGROUND;
3152 	g_warn_if_fail (gdk_rgba_parse(&priv->transp_color, CHECK_BLACK));
3153 	priv->cursor = XVIEWER_SCROLL_VIEW_CURSOR_NORMAL;
3154 	priv->menu = NULL;
3155 	priv->override_bg_color = NULL;
3156 	priv->background_surface = NULL;
3157 
3158 	priv->hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100));
3159 	g_signal_connect (priv->hadj, "value_changed",
3160 			  G_CALLBACK (adjustment_changed_cb),
3161 			  view);
3162 
3163 	priv->hbar = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, priv->hadj);
3164 	priv->vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100));
3165 	g_signal_connect (priv->vadj, "value_changed",
3166 			  G_CALLBACK (adjustment_changed_cb),
3167 			  view);
3168 
3169 	priv->vbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, priv->vadj);
3170 	priv->display = g_object_new (GTK_TYPE_DRAWING_AREA,
3171 				      "can-focus", TRUE,
3172 				      NULL);
3173 
3174 	gtk_widget_add_events (GTK_WIDGET (priv->display),
3175 			       GDK_EXPOSURE_MASK
3176 			       | GDK_BUTTON_PRESS_MASK
3177 			       | GDK_BUTTON_RELEASE_MASK
3178 			       | GDK_POINTER_MOTION_MASK
3179 			       | GDK_POINTER_MOTION_HINT_MASK
3180 			       | GDK_TOUCH_MASK
3181 			       | GDK_SCROLL_MASK
3182 			       | GDK_KEY_PRESS_MASK);
3183 	g_signal_connect (G_OBJECT (priv->display), "configure_event",
3184 			  G_CALLBACK (display_size_change), view);
3185 	g_signal_connect (G_OBJECT (priv->display), "draw",
3186 			  G_CALLBACK (display_draw), view);
3187 	g_signal_connect (G_OBJECT (priv->display), "map_event",
3188 			  G_CALLBACK (display_map_event), view);
3189 	g_signal_connect (G_OBJECT (priv->display), "button_press_event",
3190 			  G_CALLBACK (xviewer_scroll_view_button_press_event),
3191 			  view);
3192 	g_signal_connect (G_OBJECT (priv->display), "motion_notify_event",
3193 			  G_CALLBACK (xviewer_scroll_view_motion_event), view);
3194 	g_signal_connect (G_OBJECT (priv->display), "button_release_event",
3195 			  G_CALLBACK (xviewer_scroll_view_button_release_event),
3196 			  view);
3197 	g_signal_connect (G_OBJECT (priv->display), "scroll_event",
3198 			  G_CALLBACK (xviewer_scroll_view_scroll_event), view);
3199 	g_signal_connect (G_OBJECT (priv->display), "focus_in_event",
3200 			  G_CALLBACK (xviewer_scroll_view_focus_in_event), NULL);
3201 	g_signal_connect (G_OBJECT (priv->display), "focus_out_event",
3202 			  G_CALLBACK (xviewer_scroll_view_focus_out_event), NULL);
3203 
3204 	g_signal_connect (G_OBJECT (view), "key_press_event",
3205 			  G_CALLBACK (display_key_press_event), view);
3206 
3207 	gtk_drag_source_set (priv->display, GDK_BUTTON1_MASK,
3208 			     target_table, G_N_ELEMENTS (target_table),
3209 			     GDK_ACTION_COPY | GDK_ACTION_MOVE |
3210 			     GDK_ACTION_LINK | GDK_ACTION_ASK);
3211 	g_signal_connect (G_OBJECT (priv->display), "drag-data-get",
3212 			  G_CALLBACK (view_on_drag_data_get_cb), view);
3213 	g_signal_connect (G_OBJECT (priv->display), "drag-begin",
3214 			  G_CALLBACK (view_on_drag_begin_cb), view);
3215 
3216 	gtk_grid_attach (GTK_GRID (view), priv->display,
3217 			 0, 0, 1, 1);
3218 	gtk_widget_set_hexpand (priv->display, TRUE);
3219 	gtk_widget_set_vexpand (priv->display, TRUE);
3220 	gtk_grid_attach (GTK_GRID (view), priv->hbar,
3221 			 0, 1, 1, 1);
3222 	gtk_widget_set_hexpand (priv->hbar, TRUE);
3223 	gtk_grid_attach (GTK_GRID (view), priv->vbar,
3224 			 1, 0, 1, 1);
3225 	gtk_widget_set_vexpand (priv->vbar, TRUE);
3226 
3227 	g_settings_bind (settings, XVIEWER_CONF_VIEW_USE_BG_COLOR, view,
3228 			 "use-background-color", G_SETTINGS_BIND_DEFAULT);
3229 	g_settings_bind_with_mapping (settings, XVIEWER_CONF_VIEW_BACKGROUND_COLOR,
3230 				      view, "background-color",
3231 				      G_SETTINGS_BIND_DEFAULT,
3232 				      sv_string_to_rgba_mapping,
3233 				      sv_rgba_to_string_mapping, NULL, NULL);
3234 	g_settings_bind_with_mapping (settings, XVIEWER_CONF_VIEW_TRANS_COLOR,
3235 				      view, "transparency-color",
3236 				      G_SETTINGS_BIND_GET,
3237 				      sv_string_to_rgba_mapping,
3238 				      sv_rgba_to_string_mapping, NULL, NULL);
3239 	g_settings_bind (settings, XVIEWER_CONF_VIEW_TRANSPARENCY, view,
3240 			 "transparency-style", G_SETTINGS_BIND_GET);
3241 	g_settings_bind (settings, XVIEWER_CONF_VIEW_EXTRAPOLATE, view,
3242 			 "antialiasing-in", G_SETTINGS_BIND_GET);
3243 	g_settings_bind (settings, XVIEWER_CONF_VIEW_INTERPOLATE, view,
3244 			 "antialiasing-out", G_SETTINGS_BIND_GET);
3245 
3246 	g_object_unref (settings);
3247 
3248 #if GTK_CHECK_VERSION (3, 14, 0)
3249 	priv->zoom_gesture = gtk_gesture_zoom_new (GTK_WIDGET (view));
3250 	g_signal_connect (priv->zoom_gesture, "begin",
3251 			  G_CALLBACK (zoom_gesture_begin_cb), view);
3252 	g_signal_connect (priv->zoom_gesture, "update",
3253 			  G_CALLBACK (zoom_gesture_update_cb), view);
3254 	g_signal_connect (priv->zoom_gesture, "end",
3255 			  G_CALLBACK (zoom_gesture_end_cb), view);
3256 	g_signal_connect (priv->zoom_gesture, "cancel",
3257 			  G_CALLBACK (zoom_gesture_end_cb), view);
3258 	gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->zoom_gesture),
3259 						    GTK_PHASE_CAPTURE);
3260 
3261 	priv->rotate_gesture = gtk_gesture_rotate_new (GTK_WIDGET (view));
3262 	gtk_gesture_group (priv->rotate_gesture, priv->zoom_gesture);
3263 	g_signal_connect (priv->rotate_gesture, "angle-changed",
3264 			  G_CALLBACK (rotate_gesture_angle_changed_cb), view);
3265 	g_signal_connect (priv->rotate_gesture, "begin",
3266 			  G_CALLBACK (rotate_gesture_begin_cb), view);
3267 	gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->rotate_gesture),
3268 						    GTK_PHASE_CAPTURE);
3269 
3270 	priv->pan_gesture = gtk_gesture_pan_new (GTK_WIDGET (view),
3271 						 GTK_ORIENTATION_HORIZONTAL);
3272 	g_signal_connect (priv->pan_gesture, "pan",
3273 			  G_CALLBACK (pan_gesture_pan_cb), view);
3274 	g_signal_connect (priv->pan_gesture, "end",
3275 			  G_CALLBACK (pan_gesture_end_cb), view);
3276 	gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->pan_gesture),
3277 					   TRUE);
3278 	gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->pan_gesture),
3279 						    GTK_PHASE_CAPTURE);
3280 #endif
3281 }
3282 
3283 static void
xviewer_scroll_view_dispose(GObject * object)3284 xviewer_scroll_view_dispose (GObject *object)
3285 {
3286 	XviewerScrollView *view;
3287 	XviewerScrollViewPrivate *priv;
3288 
3289 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (object));
3290 
3291 	view = XVIEWER_SCROLL_VIEW (object);
3292 	priv = view->priv;
3293 
3294 #if 0
3295 	if (priv->uta != NULL) {
3296 		xviewer_uta_free (priv->uta);
3297 		priv->uta = NULL;
3298 	}
3299 #endif
3300 
3301 	_clear_hq_redraw_timeout (view);
3302 
3303 	if (priv->idle_id != 0) {
3304 		g_source_remove (priv->idle_id);
3305 		priv->idle_id = 0;
3306 	}
3307 
3308 	if (priv->background_color != NULL) {
3309 		gdk_rgba_free (priv->background_color);
3310 		priv->background_color = NULL;
3311 	}
3312 
3313 	if (priv->override_bg_color != NULL) {
3314 		gdk_rgba_free (priv->override_bg_color);
3315 		priv->override_bg_color = NULL;
3316 	}
3317 
3318 	if (priv->background_surface != NULL) {
3319 		cairo_surface_destroy (priv->background_surface);
3320 		priv->background_surface = NULL;
3321 	}
3322 
3323 	free_image_resources (view);
3324 
3325 #if GTK_CHECK_VERSION (3, 14, 0)
3326 	if (priv->zoom_gesture) {
3327 		g_object_unref (priv->zoom_gesture);
3328 		priv->zoom_gesture = NULL;
3329 	}
3330 
3331 	if (priv->rotate_gesture) {
3332 		g_object_unref (priv->rotate_gesture);
3333 		priv->rotate_gesture = NULL;
3334 	}
3335 
3336 	if (priv->pan_gesture) {
3337 		g_object_unref (priv->pan_gesture);
3338 		priv->pan_gesture = NULL;
3339 	}
3340 #endif
3341 
3342 	G_OBJECT_CLASS (xviewer_scroll_view_parent_class)->dispose (object);
3343 }
3344 
3345 static void
xviewer_scroll_view_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)3346 xviewer_scroll_view_get_property (GObject *object, guint property_id,
3347 			      GValue *value, GParamSpec *pspec)
3348 {
3349 	XviewerScrollView *view;
3350 	XviewerScrollViewPrivate *priv;
3351 
3352 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (object));
3353 
3354 	view = XVIEWER_SCROLL_VIEW (object);
3355 	priv = view->priv;
3356 
3357 	switch (property_id) {
3358 	case PROP_ANTIALIAS_IN:
3359 	{
3360 		gboolean filter = (priv->interp_type_in != CAIRO_FILTER_NEAREST);
3361 		g_value_set_boolean (value, filter);
3362 		break;
3363 	}
3364 	case PROP_ANTIALIAS_OUT:
3365 	{
3366 		gboolean filter = (priv->interp_type_out != CAIRO_FILTER_NEAREST);
3367 		g_value_set_boolean (value, filter);
3368 		break;
3369 	}
3370 	case PROP_USE_BG_COLOR:
3371 		g_value_set_boolean (value, priv->use_bg_color);
3372 		break;
3373 	case PROP_BACKGROUND_COLOR:
3374 		//FIXME: This doesn't really handle the NULL color.
3375 		g_value_set_boxed (value, priv->background_color);
3376 		break;
3377 	case PROP_SCROLLWHEEL_ZOOM:
3378 		g_value_set_boolean (value, priv->scroll_wheel_zoom);
3379 		break;
3380 	case PROP_TRANSPARENCY_STYLE:
3381 		g_value_set_enum (value, priv->transp_style);
3382 		break;
3383 	case PROP_ZOOM_MODE:
3384 		g_value_set_enum (value, priv->zoom_mode);
3385 		break;
3386 	case PROP_ZOOM_MULTIPLIER:
3387 		g_value_set_double (value, priv->zoom_multiplier);
3388 		break;
3389 	case PROP_IMAGE:
3390 		g_value_set_object (value, priv->image);
3391 		break;
3392 	default:
3393 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
3394 	}
3395 }
3396 
3397 static void
xviewer_scroll_view_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)3398 xviewer_scroll_view_set_property (GObject *object, guint property_id,
3399 			      const GValue *value, GParamSpec *pspec)
3400 {
3401 	XviewerScrollView *view;
3402 
3403 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (object));
3404 
3405 	view = XVIEWER_SCROLL_VIEW (object);
3406 
3407 	switch (property_id) {
3408 	case PROP_ANTIALIAS_IN:
3409 		xviewer_scroll_view_set_antialiasing_in (view, g_value_get_boolean (value));
3410 		break;
3411 	case PROP_ANTIALIAS_OUT:
3412 		xviewer_scroll_view_set_antialiasing_out (view, g_value_get_boolean (value));
3413 		break;
3414 	case PROP_USE_BG_COLOR:
3415 		xviewer_scroll_view_set_use_bg_color (view, g_value_get_boolean (value));
3416 		break;
3417 	case PROP_BACKGROUND_COLOR:
3418 	{
3419 		const GdkRGBA *color = g_value_get_boxed (value);
3420 		xviewer_scroll_view_set_background_color (view, color);
3421 		break;
3422 	}
3423 	case PROP_SCROLLWHEEL_ZOOM:
3424 		xviewer_scroll_view_set_scroll_wheel_zoom (view, g_value_get_boolean (value));
3425 		break;
3426 	case PROP_TRANSP_COLOR:
3427 		xviewer_scroll_view_set_transparency_color (view, g_value_get_boxed (value));
3428 		break;
3429 	case PROP_TRANSPARENCY_STYLE:
3430 		xviewer_scroll_view_set_transparency (view, g_value_get_enum (value));
3431 		break;
3432 	case PROP_ZOOM_MODE:
3433 		xviewer_scroll_view_set_zoom_mode (view, g_value_get_enum (value));
3434 		break;
3435 	case PROP_ZOOM_MULTIPLIER:
3436 		xviewer_scroll_view_set_zoom_multiplier (view, g_value_get_double (value));
3437 		break;
3438 	case PROP_IMAGE:
3439 		xviewer_scroll_view_set_image (view, g_value_get_object (value));
3440 		break;
3441 	default:
3442 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
3443 	}
3444 }
3445 
3446 
3447 static void
xviewer_scroll_view_class_init(XviewerScrollViewClass * klass)3448 xviewer_scroll_view_class_init (XviewerScrollViewClass *klass)
3449 {
3450 	GObjectClass *gobject_class;
3451 	GtkWidgetClass *widget_class;
3452 
3453 	gobject_class = (GObjectClass*) klass;
3454 	widget_class = (GtkWidgetClass*) klass;
3455 
3456 	gobject_class->dispose = xviewer_scroll_view_dispose;
3457         gobject_class->set_property = xviewer_scroll_view_set_property;
3458         gobject_class->get_property = xviewer_scroll_view_get_property;
3459 
3460 	/**
3461 	 * XviewerScrollView:antialiasing-in:
3462 	 *
3463 	 * If %TRUE the displayed image will be filtered in a second pass
3464 	 * while being zoomed in.
3465 	 */
3466 	g_object_class_install_property (
3467 		gobject_class, PROP_ANTIALIAS_IN,
3468 		g_param_spec_boolean ("antialiasing-in", NULL, NULL, TRUE,
3469 				      G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3470 	/**
3471 	 * XviewerScrollView:antialiasing-out:
3472 	 *
3473 	 * If %TRUE the displayed image will be filtered in a second pass
3474 	 * while being zoomed out.
3475 	 */
3476 	g_object_class_install_property (
3477 		gobject_class, PROP_ANTIALIAS_OUT,
3478 		g_param_spec_boolean ("antialiasing-out", NULL, NULL, TRUE,
3479 				      G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3480 
3481 	/**
3482 	 * XviewerScrollView:background-color:
3483 	 *
3484 	 * This is the default background color used for painting the background
3485 	 * of the image view. If set to %NULL the color is determined by the
3486 	 * active GTK theme.
3487 	 */
3488 	g_object_class_install_property (
3489 		gobject_class, PROP_BACKGROUND_COLOR,
3490 		g_param_spec_boxed ("background-color", NULL, NULL,
3491 				    GDK_TYPE_RGBA,
3492 				    G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3493 
3494 	g_object_class_install_property (
3495 		gobject_class, PROP_USE_BG_COLOR,
3496 		g_param_spec_boolean ("use-background-color", NULL, NULL, FALSE,
3497 				      G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3498 
3499 	/**
3500 	 * XviewerScrollView:zoom-multiplier:
3501 	 *
3502 	 * The current zoom factor is multiplied with this value + 1.0 when
3503 	 * scrolling with the scrollwheel to determine the next zoom factor.
3504 	 */
3505 	g_object_class_install_property (
3506 		gobject_class, PROP_ZOOM_MULTIPLIER,
3507 		g_param_spec_double ("zoom-multiplier", NULL, NULL,
3508 				     -G_MAXDOUBLE, G_MAXDOUBLE - 1.0, 0.05,
3509 				     G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3510 
3511 	/**
3512 	 * XviewerScrollView:scrollwheel-zoom:
3513 	 *
3514 	 * If %TRUE the scrollwheel will zoom the view, otherwise it will be
3515 	 * used for scrolling a zoomed image.
3516 	 */
3517 	g_object_class_install_property (
3518 		gobject_class, PROP_SCROLLWHEEL_ZOOM,
3519 		g_param_spec_boolean ("scrollwheel-zoom", NULL, NULL, TRUE,
3520 				      G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3521 
3522 	/**
3523 	 * XviewerScrollView:image:
3524 	 *
3525 	 * This is the currently display #XviewerImage.
3526 	 */
3527 	g_object_class_install_property (
3528 		gobject_class, PROP_IMAGE,
3529 		g_param_spec_object ("image", NULL, NULL, XVIEWER_TYPE_IMAGE,
3530 				     G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3531 
3532 	/**
3533 	 * XviewerScrollView:transparency-color:
3534 	 *
3535 	 * This is the color used to fill the transparent parts of an image
3536 	 * if #XviewerScrollView:transparency-style is set to %XVIEWER_TRANSP_COLOR.
3537 	 */
3538 	g_object_class_install_property (
3539 		gobject_class, PROP_TRANSP_COLOR,
3540 		g_param_spec_boxed ("transparency-color", NULL, NULL,
3541 				    GDK_TYPE_RGBA,
3542 				    G_PARAM_WRITABLE | G_PARAM_STATIC_NAME));
3543 
3544 	/**
3545 	 * XviewerScrollView:transparency-style:
3546 	 *
3547 	 * Determines how to fill the shown image's transparent areas.
3548 	 */
3549 	g_object_class_install_property (
3550 		gobject_class, PROP_TRANSPARENCY_STYLE,
3551 		g_param_spec_enum ("transparency-style", NULL, NULL,
3552 				   XVIEWER_TYPE_TRANSPARENCY_STYLE,
3553 				   XVIEWER_TRANSP_CHECKED,
3554 				   G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3555 
3556 	g_object_class_install_property (
3557 		gobject_class, PROP_ZOOM_MODE,
3558 		g_param_spec_enum ("zoom-mode", NULL, NULL,
3559 				   XVIEWER_TYPE_ZOOM_MODE,
3560 				   XVIEWER_ZOOM_MODE_SHRINK_TO_FIT,
3561 				   G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
3562 
3563 	view_signals [SIGNAL_ZOOM_CHANGED] =
3564 		g_signal_new ("zoom_changed",
3565 			      XVIEWER_TYPE_SCROLL_VIEW,
3566 			      G_SIGNAL_RUN_LAST,
3567 			      G_STRUCT_OFFSET (XviewerScrollViewClass, zoom_changed),
3568 			      NULL, NULL,
3569 			      g_cclosure_marshal_VOID__DOUBLE,
3570 			      G_TYPE_NONE, 1,
3571 			      G_TYPE_DOUBLE);
3572 	view_signals [SIGNAL_ROTATION_CHANGED] =
3573 		g_signal_new ("rotation-changed",
3574 			      XVIEWER_TYPE_SCROLL_VIEW,
3575 			      G_SIGNAL_RUN_LAST,
3576 			      G_STRUCT_OFFSET (XviewerScrollViewClass, rotation_changed),
3577 			      NULL, NULL,
3578 			      g_cclosure_marshal_VOID__DOUBLE,
3579 			      G_TYPE_NONE, 1,
3580 			      G_TYPE_DOUBLE);
3581 
3582 	view_signals [SIGNAL_NEXT_IMAGE] =
3583 		g_signal_new ("next-image",
3584 			      XVIEWER_TYPE_SCROLL_VIEW,
3585 			      G_SIGNAL_RUN_LAST,
3586 			      G_STRUCT_OFFSET (XviewerScrollViewClass, next_image),
3587 			      NULL, NULL,
3588 			      g_cclosure_marshal_VOID__VOID,
3589 			      G_TYPE_NONE, 0);
3590 	view_signals [SIGNAL_PREVIOUS_IMAGE] =
3591 		g_signal_new ("previous-image",
3592 			      XVIEWER_TYPE_SCROLL_VIEW,
3593 			      G_SIGNAL_RUN_LAST,
3594 			      G_STRUCT_OFFSET (XviewerScrollViewClass, previous_image),
3595 			      NULL, NULL,
3596 			      g_cclosure_marshal_VOID__VOID,
3597 			      G_TYPE_NONE, 0);
3598 
3599 	widget_class->size_allocate = xviewer_scroll_view_size_allocate;
3600 	widget_class->style_set = xviewer_scroll_view_style_set;
3601 }
3602 
3603 static void
view_on_drag_begin_cb(GtkWidget * widget,GdkDragContext * context,gpointer user_data)3604 view_on_drag_begin_cb (GtkWidget        *widget,
3605 		       GdkDragContext   *context,
3606 		       gpointer          user_data)
3607 {
3608 	XviewerScrollView *view;
3609 	XviewerImage *image;
3610 	GdkPixbuf *thumbnail;
3611 	gint width, height;
3612 
3613 	view = XVIEWER_SCROLL_VIEW (user_data);
3614 	image = view->priv->image;
3615 
3616 	thumbnail = xviewer_image_get_thumbnail (image);
3617 
3618 	if  (thumbnail) {
3619 		width = gdk_pixbuf_get_width (thumbnail);
3620 		height = gdk_pixbuf_get_height (thumbnail);
3621 		gtk_drag_set_icon_pixbuf (context, thumbnail, width/2, height/2);
3622 		g_object_unref (thumbnail);
3623 	}
3624 }
3625 
3626 static void
view_on_drag_data_get_cb(GtkWidget * widget,GdkDragContext * drag_context,GtkSelectionData * data,guint info,guint time,gpointer user_data)3627 view_on_drag_data_get_cb (GtkWidget        *widget,
3628 			  GdkDragContext   *drag_context,
3629 			  GtkSelectionData *data,
3630 			  guint             info,
3631 			  guint             time,
3632 			  gpointer          user_data)
3633 {
3634 	XviewerScrollView *view;
3635 	XviewerImage *image;
3636 	gchar *uris[2];
3637 	GFile *file;
3638 
3639 	view = XVIEWER_SCROLL_VIEW (user_data);
3640 
3641 	image = view->priv->image;
3642 
3643 	file = xviewer_image_get_file (image);
3644 	uris[0] = g_file_get_uri (file);
3645 	uris[1] = NULL;
3646 
3647 	gtk_selection_data_set_uris (data, uris);
3648 
3649 	g_free (uris[0]);
3650 	g_object_unref (file);
3651 }
3652 
3653 GtkWidget*
xviewer_scroll_view_new(void)3654 xviewer_scroll_view_new (void)
3655 {
3656 	GtkWidget *widget;
3657 
3658 	widget = g_object_new (XVIEWER_TYPE_SCROLL_VIEW,
3659 			       "can-focus", TRUE,
3660 			       "row-homogeneous", FALSE,
3661 			       "column-homogeneous", FALSE,
3662 			       NULL);
3663 
3664 	return widget;
3665 }
3666 
3667 static void
xviewer_scroll_view_popup_menu(XviewerScrollView * view,GdkEventButton * event)3668 xviewer_scroll_view_popup_menu (XviewerScrollView *view, GdkEventButton *event)
3669 {
3670 	GtkWidget *popup;
3671 	int button, event_time;
3672 
3673 	popup = view->priv->menu;
3674 
3675 	if (event) {
3676 		button = event->button;
3677 		event_time = event->time;
3678 	} else {
3679 		button = 0;
3680 		event_time = gtk_get_current_event_time ();
3681 	}
3682 
3683 	gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL,
3684 			button, event_time);
3685 }
3686 
3687 static gboolean
view_on_button_press_event_cb(GtkWidget * view,GdkEventButton * event,gpointer user_data)3688 view_on_button_press_event_cb (GtkWidget *view, GdkEventButton *event,
3689 			       gpointer user_data)
3690 {
3691     /* Ignore double-clicks and triple-clicks */
3692     if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3693     {
3694 	    xviewer_scroll_view_popup_menu (XVIEWER_SCROLL_VIEW (view), event);
3695 
3696 	    return TRUE;
3697     }
3698 
3699     return FALSE;
3700 }
3701 
3702 void
xviewer_scroll_view_set_popup(XviewerScrollView * view,GtkMenu * menu)3703 xviewer_scroll_view_set_popup (XviewerScrollView *view,
3704 			   GtkMenu *menu)
3705 {
3706 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
3707 	g_return_if_fail (view->priv->menu == NULL);
3708 
3709 	view->priv->menu = g_object_ref (menu);
3710 
3711 	gtk_menu_attach_to_widget (GTK_MENU (view->priv->menu),
3712 				   GTK_WIDGET (view),
3713 				   NULL);
3714 
3715 	g_signal_connect (G_OBJECT (view), "button_press_event",
3716 			  G_CALLBACK (view_on_button_press_event_cb), NULL);
3717 }
3718 
3719 static gboolean
_xviewer_gdk_rgba_equal0(const GdkRGBA * a,const GdkRGBA * b)3720 _xviewer_gdk_rgba_equal0 (const GdkRGBA *a, const GdkRGBA *b)
3721 {
3722 	if (a == NULL || b == NULL)
3723 		return (a == b);
3724 
3725 	return gdk_rgba_equal (a, b);
3726 }
3727 
3728 static gboolean
_xviewer_replace_gdk_rgba(GdkRGBA ** dest,const GdkRGBA * src)3729 _xviewer_replace_gdk_rgba (GdkRGBA **dest, const GdkRGBA *src)
3730 {
3731 	GdkRGBA *old = *dest;
3732 
3733 	if (_xviewer_gdk_rgba_equal0 (old, src))
3734 		return FALSE;
3735 
3736 	if (old != NULL)
3737 		gdk_rgba_free (old);
3738 
3739 	*dest = (src) ? gdk_rgba_copy (src) : NULL;
3740 
3741 	return TRUE;
3742 }
3743 
3744 static void
_xviewer_scroll_view_update_bg_color(XviewerScrollView * view)3745 _xviewer_scroll_view_update_bg_color (XviewerScrollView *view)
3746 {
3747 	const GdkRGBA *selected;
3748 	XviewerScrollViewPrivate *priv = view->priv;
3749 
3750 	if (priv->override_bg_color)
3751 		selected = priv->override_bg_color;
3752 	else if (priv->use_bg_color)
3753 		selected = priv->background_color;
3754 	else
3755 		selected = NULL;
3756 
3757 	if (priv->transp_style == XVIEWER_TRANSP_BACKGROUND
3758 	    && priv->background_surface != NULL) {
3759 		/* Delete the SVG background to have it recreated with
3760 		 * the correct color during the next SVG redraw */
3761 		cairo_surface_destroy (priv->background_surface);
3762 		priv->background_surface = NULL;
3763 	}
3764 
3765 	/*gtk_widget_modify_bg (GTK_WIDGET (priv->display),
3766 			      GTK_STATE_NORMAL,
3767 			      selected);*/
3768 	gtk_widget_override_background_color(GTK_WIDGET (priv->display), GTK_STATE_FLAG_NORMAL, selected);
3769 }
3770 
3771 void
xviewer_scroll_view_set_background_color(XviewerScrollView * view,const GdkRGBA * color)3772 xviewer_scroll_view_set_background_color (XviewerScrollView *view,
3773 				      const GdkRGBA *color)
3774 {
3775 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
3776 
3777 	if (_xviewer_replace_gdk_rgba (&view->priv->background_color, color))
3778 		_xviewer_scroll_view_update_bg_color (view);
3779 }
3780 
3781 void
xviewer_scroll_view_override_bg_color(XviewerScrollView * view,const GdkRGBA * color)3782 xviewer_scroll_view_override_bg_color (XviewerScrollView *view,
3783 				   const GdkRGBA *color)
3784 {
3785 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
3786 
3787 	if (_xviewer_replace_gdk_rgba (&view->priv->override_bg_color, color))
3788 		_xviewer_scroll_view_update_bg_color (view);
3789 }
3790 
3791 void
xviewer_scroll_view_set_use_bg_color(XviewerScrollView * view,gboolean use)3792 xviewer_scroll_view_set_use_bg_color (XviewerScrollView *view, gboolean use)
3793 {
3794 	XviewerScrollViewPrivate *priv;
3795 
3796 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
3797 
3798 	priv = view->priv;
3799 
3800 	if (use != priv->use_bg_color) {
3801 		priv->use_bg_color = use;
3802 
3803 		_xviewer_scroll_view_update_bg_color (view);
3804 
3805 		g_object_notify (G_OBJECT (view), "use-background-color");
3806 	}
3807 }
3808 
3809 void
xviewer_scroll_view_set_scroll_wheel_zoom(XviewerScrollView * view,gboolean scroll_wheel_zoom)3810 xviewer_scroll_view_set_scroll_wheel_zoom (XviewerScrollView *view,
3811 				       gboolean       scroll_wheel_zoom)
3812 {
3813 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
3814 
3815 	if (view->priv->scroll_wheel_zoom != scroll_wheel_zoom) {
3816 	        view->priv->scroll_wheel_zoom = scroll_wheel_zoom;
3817 		g_object_notify (G_OBJECT (view), "scrollwheel-zoom");
3818 	}
3819 }
3820 
3821 void
xviewer_scroll_view_set_zoom_multiplier(XviewerScrollView * view,gdouble zoom_multiplier)3822 xviewer_scroll_view_set_zoom_multiplier (XviewerScrollView *view,
3823 				     gdouble        zoom_multiplier)
3824 {
3825 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
3826 
3827         view->priv->zoom_multiplier = 1.0 + zoom_multiplier;
3828 
3829 	g_object_notify (G_OBJECT (view), "zoom-multiplier");
3830 }
3831 
3832 /* Helper to cause a redraw even if the zoom mode is unchanged */
3833 static void
_set_zoom_mode_internal(XviewerScrollView * view,XviewerZoomMode mode)3834 _set_zoom_mode_internal (XviewerScrollView *view, XviewerZoomMode mode)
3835 {
3836 	gboolean notify = (mode != view->priv->zoom_mode);
3837 
3838 
3839 	if (mode == XVIEWER_ZOOM_MODE_SHRINK_TO_FIT)
3840 		xviewer_scroll_view_zoom_fit (view);
3841 	else
3842 		view->priv->zoom_mode = mode;
3843 
3844 	if (notify)
3845 		g_object_notify (G_OBJECT (view), "zoom-mode");
3846 }
3847 
3848 
3849 void
xviewer_scroll_view_set_zoom_mode(XviewerScrollView * view,XviewerZoomMode mode)3850 xviewer_scroll_view_set_zoom_mode (XviewerScrollView *view, XviewerZoomMode mode)
3851 {
3852 	g_return_if_fail (XVIEWER_IS_SCROLL_VIEW (view));
3853 
3854 	if (view->priv->zoom_mode == mode)
3855 		return;
3856 
3857 	_set_zoom_mode_internal (view, mode);
3858 }
3859 
3860 XviewerZoomMode
xviewer_scroll_view_get_zoom_mode(XviewerScrollView * view)3861 xviewer_scroll_view_get_zoom_mode (XviewerScrollView *view)
3862 {
3863 	g_return_val_if_fail (XVIEWER_IS_SCROLL_VIEW (view),
3864 			      XVIEWER_ZOOM_MODE_SHRINK_TO_FIT);
3865 
3866 	return view->priv->zoom_mode;
3867 }
3868 
3869 static gboolean
xviewer_scroll_view_get_image_coords(XviewerScrollView * view,gint * x,gint * y,gint * width,gint * height)3870 xviewer_scroll_view_get_image_coords (XviewerScrollView *view, gint *x, gint *y,
3871                                   gint *width, gint *height)
3872 {
3873 	XviewerScrollViewPrivate *priv = view->priv;
3874 	GtkAllocation allocation;
3875 	gint scaled_width, scaled_height, xofs, yofs;
3876 
3877 	compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
3878 
3879 	if (G_LIKELY (width))
3880 		*width = scaled_width;
3881 	if (G_LIKELY (height))
3882 		*height = scaled_height;
3883 
3884 	/* If only width and height are needed stop here. */
3885 	if (x == NULL && y == NULL)
3886 		return TRUE;
3887 
3888 	gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
3889 
3890 	/* Compute image offsets with respect to the window */
3891 
3892 	if (scaled_width <= allocation.width)
3893 		xofs = (allocation.width - scaled_width) / 2;
3894 	else
3895 		xofs = -priv->xofs;
3896 
3897 	if (scaled_height <= allocation.height)
3898 		yofs = (allocation.height - scaled_height) / 2;
3899 	else
3900 		yofs = -priv->yofs;
3901 
3902 	if (G_LIKELY (x))
3903 		*x = xofs;
3904 	if (G_LIKELY (y))
3905 		*y = yofs;
3906 
3907 	return TRUE;
3908 }
3909 
3910 /**
3911  * xviewer_scroll_view_event_is_over_image:
3912  * @view: An #XviewerScrollView that has an image loaded.
3913  * @ev: A #GdkEvent which must have window-relative coordinates.
3914  *
3915  * Tells if @ev's originates from inside the image area. @view must be
3916  * realized and have an image set for this to work.
3917  *
3918  * It only works with #GdkEvent<!-- -->s that supply coordinate data,
3919  * i.e. #GdkEventButton.
3920  *
3921  * Returns: %TRUE if @ev originates from over the image, %FALSE otherwise.
3922  */
3923 gboolean
xviewer_scroll_view_event_is_over_image(XviewerScrollView * view,const GdkEvent * ev)3924 xviewer_scroll_view_event_is_over_image (XviewerScrollView *view, const GdkEvent *ev)
3925 {
3926 	XviewerScrollViewPrivate *priv;
3927 	GdkWindow *window;
3928 	gdouble evx, evy;
3929 	gint x, y, width, height;
3930 
3931 	g_return_val_if_fail (XVIEWER_IS_SCROLL_VIEW (view), FALSE);
3932 	g_return_val_if_fail (gtk_widget_get_realized(GTK_WIDGET(view)), FALSE);
3933 	g_return_val_if_fail (ev != NULL, FALSE);
3934 
3935 	priv = view->priv;
3936 	window = gtk_widget_get_window (GTK_WIDGET (priv->display));
3937 
3938 	if (G_UNLIKELY (priv->pixbuf == NULL
3939 	    || window != ((GdkEventAny*) ev)->window))
3940 		return FALSE;
3941 
3942 	if (G_UNLIKELY (!gdk_event_get_coords (ev, &evx, &evy)))
3943 		return FALSE;
3944 
3945 	if (!xviewer_scroll_view_get_image_coords (view, &x, &y, &width, &height))
3946 		return FALSE;
3947 
3948 	if (evx < x || evy < y || evx > (x + width) || evy > (y + height))
3949 		return FALSE;
3950 
3951 	return TRUE;
3952 }
3953