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