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