1 /*
2  * Copyright © 2009-2018 Siyan Panayotov <contact@siyanpanayotov.com>
3  *
4  * Based on code by (see README for details):
5  * - Björn Lindqvist <bjourne@gmail.com>
6  *
7  * This file is part of Viewnior.
8  *
9  * Viewnior is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Viewnior is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Viewnior.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <gtk/gtk.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <math.h>
26 #include <stdlib.h>
27 
28 #include "uni-dragger.h"
29 #include "uni-image-view.h"
30 #include "uni-dragger.h"
31 #include "uni-anim-view.h"
32 #include "uni-marshal.h"
33 #include "uni-zoom.h"
34 #include "uni-utils.h"
35 #include "vnr-window.h"
36 
37 #define g_signal_handlers_disconnect_by_data(instance, data) \
38     g_signal_handlers_disconnect_matched ((instance), G_SIGNAL_MATCH_DATA, \
39                                           0, 0, NULL, NULL, (data))
40 #define g_signal_handlers_block_by_data(instance, data) \
41     g_signal_handlers_block_matched ((instance), G_SIGNAL_MATCH_DATA, \
42                                      0, 0, NULL, NULL, (data))
43 #define g_signal_handlers_unblock_by_data(instance, data) \
44     g_signal_handlers_unblock_matched ((instance), G_SIGNAL_MATCH_DATA, \
45                                        0, 0, NULL, NULL, (data))
46 
47 /*************************************************************/
48 /***** Private data ******************************************/
49 /*************************************************************/
50 enum {
51     SET_ZOOM,
52     ZOOM_IN,
53     ZOOM_OUT,
54     SET_FITTING,
55     SCROLL,
56     ZOOM_CHANGED,
57     PIXBUF_CHANGED,
58     LAST_SIGNAL
59 };
60 
61 static guint uni_image_view_signals[LAST_SIGNAL] = { 0 };
62 
63 G_DEFINE_TYPE (UniImageView, uni_image_view, GTK_TYPE_WIDGET);
64 
65 /*************************************************************/
66 /***** Static stuff ******************************************/
67 /*************************************************************/
68 
69 static Size
uni_image_view_get_pixbuf_size(UniImageView * view)70 uni_image_view_get_pixbuf_size (UniImageView * view)
71 {
72     Size s = { 0, 0 };
73     if (!view->pixbuf)
74         return s;
75 
76     s.width = gdk_pixbuf_get_width (view->pixbuf);
77     s.height = gdk_pixbuf_get_height (view->pixbuf);
78     return s;
79 }
80 
81 static Size
uni_image_view_get_allocated_size(UniImageView * view)82 uni_image_view_get_allocated_size (UniImageView * view)
83 {
84     GtkAllocation allocation;
85     gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
86     Size size = {
87         .width = allocation.width,
88         .height = allocation.height
89     };
90     return size;
91 }
92 
93 static Size
uni_image_view_get_zoomed_size(UniImageView * view)94 uni_image_view_get_zoomed_size (UniImageView * view)
95 {
96     Size size = uni_image_view_get_pixbuf_size (view);
97     size.width = (int) (size.width * view->zoom + 0.5);
98     size.height = (int) (size.height * view->zoom + 0.5);
99     return size;
100 }
101 
102 static void
uni_image_view_clamp_offset(UniImageView * view,gdouble * x,gdouble * y)103 uni_image_view_clamp_offset (UniImageView * view, gdouble * x, gdouble * y)
104 {
105     Size alloc = uni_image_view_get_allocated_size (view);
106     Size zoomed = uni_image_view_get_zoomed_size (view);
107 
108     *x = MIN (*x, zoomed.width - alloc.width);
109     *y = MIN (*y, zoomed.height - alloc.height);
110     *x = MAX (*x, 0);
111     *y = MAX (*y, 0);
112 }
113 
114 static void
uni_image_view_update_adjustments(UniImageView * view)115 uni_image_view_update_adjustments (UniImageView * view)
116 {
117     Size zoomed = uni_image_view_get_zoomed_size (view);
118     Size alloc = uni_image_view_get_allocated_size (view);
119 
120     gtk_adjustment_configure (view->hadj,
121                               view->offset_x,
122                               0.0,
123                               zoomed.width,
124                               20.0,
125                               alloc.width / 2,
126                               alloc.width);
127 
128     gtk_adjustment_configure (view->vadj,
129                               view->offset_y,
130                               0.0,
131                               zoomed.height,
132                               20.0,
133                               alloc.height / 2,
134                               alloc.height);
135 
136     g_signal_handlers_block_by_data (G_OBJECT (view->hadj), view);
137     g_signal_handlers_block_by_data (G_OBJECT (view->vadj), view);
138     gtk_adjustment_changed (view->hadj);
139     gtk_adjustment_changed (view->vadj);
140     g_signal_handlers_unblock_by_data (G_OBJECT (view->hadj), view);
141     g_signal_handlers_unblock_by_data (G_OBJECT (view->vadj), view);
142 }
143 
144 /**
145  * This method must only be used by uni_image_view_zoom_to_fit () and
146  * uni_image_view_set_zoom ().
147  **/
148 static void
uni_image_view_set_zoom_with_center(UniImageView * view,gdouble zoom,gdouble center_x,gdouble center_y,gboolean is_allocating)149 uni_image_view_set_zoom_with_center (UniImageView * view,
150                                      gdouble zoom,
151                                      gdouble center_x,
152                                      gdouble center_y, gboolean is_allocating)
153 {
154     gdouble zoom_ratio = zoom / view->zoom;
155 
156     Size zoomed = uni_image_view_get_zoomed_size (view);
157     Size alloc = uni_image_view_get_allocated_size (view);
158     gint x, y;
159 
160     x = alloc.width - zoomed.width;
161     y = alloc.height - zoomed.height;
162     x = (x<0)?0:x;
163     y = (y<0)?0:y;
164 
165     gdouble offset_x, offset_y;
166     offset_x = (view->offset_x + center_x -x/2) * zoom_ratio - center_x;
167     offset_y = (view->offset_y + center_y -y/2) * zoom_ratio - center_y;
168     view->zoom = zoom;
169 
170     uni_image_view_clamp_offset (view, &offset_x, &offset_y);
171     view->offset_x = offset_x;
172     view->offset_y = offset_y;
173 
174     if (!is_allocating && zoom_ratio != 1.0)
175     {
176         view->fitting = UNI_FITTING_NONE;
177         uni_image_view_update_adjustments (view);
178         gtk_widget_queue_draw (GTK_WIDGET (view));
179     }
180 
181     g_signal_emit (G_OBJECT (view),
182                    uni_image_view_signals[ZOOM_CHANGED], 0);
183 }
184 
185 static void
uni_image_view_set_zoom_no_center(UniImageView * view,gdouble zoom,gboolean is_allocating)186 uni_image_view_set_zoom_no_center (UniImageView * view,
187                                    gdouble zoom, gboolean is_allocating)
188 {
189     Size alloc = uni_image_view_get_allocated_size (view);
190     gdouble center_x = alloc.width / 2.0;
191     gdouble center_y = alloc.height / 2.0;
192     uni_image_view_set_zoom_with_center (view, zoom,
193                                          center_x, center_y, is_allocating);
194 }
195 
196 static void
uni_image_view_zoom_to_fit(UniImageView * view,gboolean is_allocating)197 uni_image_view_zoom_to_fit (UniImageView * view, gboolean is_allocating)
198 {
199     Size img = uni_image_view_get_pixbuf_size (view);
200     Size alloc = uni_image_view_get_allocated_size (view);
201 
202     gdouble ratio_x = (gdouble) alloc.width / img.width;
203     gdouble ratio_y = (gdouble) alloc.height / img.height;
204 
205     gdouble zoom = MIN (ratio_y, ratio_x);
206 
207     if (view->fitting == UNI_FITTING_NORMAL)
208         zoom = CLAMP (zoom, UNI_ZOOM_MIN, 1.0);
209     else if (view->fitting == UNI_FITTING_FULL)
210         zoom = CLAMP (zoom, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
211 
212     uni_image_view_set_zoom_no_center (view, zoom, is_allocating);
213 }
214 
215 static void
uni_image_view_draw_background(UniImageView * view,GdkRectangle * image_area,Size alloc)216 uni_image_view_draw_background (UniImageView * view,
217                                 GdkRectangle * image_area, Size alloc)
218 {
219     GtkWidget *widget = GTK_WIDGET (view);
220     int n;
221 
222     GtkStyle *style = gtk_widget_get_style (widget);
223     GdkGC *gc = style->bg_gc[GTK_STATE_NORMAL];
224 
225     GdkWindow *window = gtk_widget_get_window (widget);
226 
227     GdkRectangle borders[4];
228     GdkRectangle outer = { 0, 0, alloc.width, alloc.height };
229     uni_rectangle_get_rects_around (&outer, image_area, borders);
230     for (n = 0; n < 4; n++)
231     {
232         // Not sure why incrementing the size is necessary.
233         borders[n].width++;
234         borders[n].height++;
235         uni_draw_rect (window, gc, TRUE, &borders[n]);
236     }
237 }
238 
239 /**
240  * uni_image_view_repaint_area:
241  * @paint_rect: The rectangle on the widget that needs to be redrawn.
242  *
243  * Redraws the porition of the widget defined by @paint_rect.
244  **/
245 static int
uni_image_view_repaint_area(UniImageView * view,GdkRectangle * paint_rect)246 uni_image_view_repaint_area (UniImageView * view, GdkRectangle * paint_rect)
247 {
248     if (view->is_rendering)
249         return FALSE;
250 
251     // Do not draw zero size rectangles.
252     if (!paint_rect->width || !paint_rect->height)
253         return FALSE;
254 
255     view->is_rendering = TRUE;
256 
257     // Image area is the area on the widget occupied by the pixbuf.
258     GdkRectangle image_area;
259     Size alloc = uni_image_view_get_allocated_size (view);
260     uni_image_view_get_draw_rect (view, &image_area);
261     if (image_area.x > 0 ||
262         image_area.y > 0 ||
263         image_area.width < alloc.width || image_area.height < alloc.height)
264     {
265         uni_image_view_draw_background (view, &image_area, alloc);
266     }
267     GtkWidget *widget = GTK_WIDGET (view);
268 
269     // Paint area is the area on the widget that should be redrawn.
270     GdkRectangle paint_area;
271     gboolean intersects = gdk_rectangle_intersect (&image_area,
272                                                    paint_rect,
273                                                    &paint_area);
274     if (intersects && view->pixbuf)
275     {
276         int src_x =
277             (int) ((view->offset_x + (gdouble) paint_area.x -
278                     (gdouble) image_area.x) + 0.5);
279         int src_y =
280             (int) ((view->offset_y + (gdouble) paint_area.y -
281                     (gdouble) image_area.y) + 0.5);
282 
283         UniPixbufDrawOpts opts = {
284             view->zoom,
285             (GdkRectangle) {src_x, src_y,
286                             paint_area.width, paint_area.height},
287             paint_area.x, paint_area.y,
288             view->interp,
289             view->pixbuf
290         };
291         uni_dragger_paint_image (UNI_DRAGGER(view->tool), &opts,
292                                  gtk_widget_get_window (widget));
293     }
294 
295     view->is_rendering = FALSE;
296     return TRUE;
297 }
298 
299 /**
300  * uni_image_view_fast_scroll:
301  *
302  * Actually scroll the views window using gdk_draw_drawable().
303  * GTK_WIDGET (view)->window is guaranteed to be non-NULL in this
304  * function.
305  **/
306 static void
uni_image_view_fast_scroll(UniImageView * view,int delta_x,int delta_y)307 uni_image_view_fast_scroll (UniImageView * view, int delta_x, int delta_y)
308 {
309     GdkDrawable *drawable = gtk_widget_get_window (GTK_WIDGET (view));
310 
311     int src_x, src_y;
312     int dest_x, dest_y;
313     if (delta_x < 0)
314     {
315         src_x = 0;
316         dest_x = -delta_x;
317     }
318     else
319     {
320         src_x = delta_x;
321         dest_x = 0;
322     }
323     if (delta_y < 0)
324     {
325         src_y = 0;
326         dest_y = -delta_y;
327     }
328     else
329     {
330         src_y = delta_y;
331         dest_y = 0;
332     }
333 
334     /* First move the part of the image that did not become hidden or
335        shown by this operation. gdk_draw_drawable is probably very
336        fast because it does not involve sending any data to the X11
337        server.
338 
339        Remember that X11 is weird shit. It does not remember how
340        windows beneath other windows look like. So if another window
341        overlaps this window, it will temporarily look corrupted. We
342        fix that later by turning on "exposures." See below. */
343 
344     GdkGC *gc = gdk_gc_new (drawable);
345     Size alloc = uni_image_view_get_allocated_size (view);
346 
347     gdk_gc_set_exposures (gc, TRUE);
348     gdk_draw_drawable (drawable,
349                        gc,
350                        drawable,
351                        src_x, src_y,
352                        dest_x, dest_y,
353                        alloc.width - abs (delta_x),
354                        alloc.height - abs (delta_y));
355     g_object_unref (gc);
356 
357     /* If we moved in both the x and y directions, two "strips" of the
358        image becomes visible. One horizontal strip and one vertical
359        strip. */
360     GdkRectangle horiz_strip = {
361         0,
362         (delta_y < 0) ? 0 : alloc.height - abs (delta_y),
363         alloc.width,
364         abs (delta_y)
365     };
366     uni_image_view_repaint_area (view, &horiz_strip);
367 
368     GdkRectangle vert_strip = {
369         (delta_x < 0) ? 0 : alloc.width - abs (delta_x),
370         0,
371         abs (delta_x),
372         alloc.height
373     };
374     uni_image_view_repaint_area (view, &vert_strip);
375 
376     /* Here is where we fix the weirdness mentioned above. I do not
377      * really know why it works, but it does! */
378     GdkEvent *ev;
379     while ((ev = gdk_event_get_graphics_expose (drawable)) != NULL)
380     {
381         GdkEventExpose *expose = (GdkEventExpose *) ev;
382         int exp_count = expose->count;
383         uni_image_view_repaint_area (view, &expose->area);
384         gdk_event_free (ev);
385         if (exp_count == 0)
386             break;
387     }
388 }
389 
390 /**
391  * uni_image_view_scroll_to:
392  * @offset_x: X part of the offset in zoom space coordinates.
393  * @offset_y: Y part of the offset in zoom space coordinates.
394  * @set_adjustments: whether to update the adjustments. Because this
395  *   function is called from the adjustments callbacks, it needs to be
396  *   %FALSE to prevent infinite recursion.
397  * @invalidate: whether to invalidate the view or redraw immedately,
398  *  see uni_image_view_set_offset()
399  *
400  * Set the offset of where in the image the #UniImageView should begin
401  * to display image data.
402  **/
403 static void
uni_image_view_scroll_to(UniImageView * view,gdouble offset_x,gdouble offset_y,gboolean set_adjustments,gboolean invalidate)404 uni_image_view_scroll_to (UniImageView * view,
405                           gdouble offset_x,
406                           gdouble offset_y,
407                           gboolean set_adjustments, gboolean invalidate)
408 {
409     GdkWindow *window;
410     int delta_x, delta_y;
411 
412     uni_image_view_clamp_offset (view, &offset_x, &offset_y);
413 
414     /* Round avoids floating point to integer conversion errors. See
415      */
416     delta_x = floor (offset_x - view->offset_x + 0.5);
417     delta_y = floor (offset_y - view->offset_y + 0.5);
418 
419     /* Exit early if the scroll was smaller than one (zoom space)
420        pixel. */
421     if (delta_x == 0 && delta_y == 0)
422         return;
423 
424     view->offset_x = offset_x;
425     view->offset_y = offset_y;
426 
427     window = gtk_widget_get_window (GTK_WIDGET (view));
428     if (window)
429     {
430         if (invalidate)
431             gdk_window_invalidate_rect (window, NULL, TRUE);
432         uni_image_view_fast_scroll (view, delta_x, delta_y);
433     }
434 
435     if (!set_adjustments)
436         return;
437 
438     g_signal_handlers_block_by_data (G_OBJECT (view->hadj), view);
439     g_signal_handlers_block_by_data (G_OBJECT (view->vadj), view);
440     gtk_adjustment_set_value (view->hadj, view->offset_x);
441     gtk_adjustment_set_value (view->vadj, view->offset_y);
442     g_signal_handlers_unblock_by_data (G_OBJECT (view->hadj), view);
443     g_signal_handlers_unblock_by_data (G_OBJECT (view->vadj), view);
444 }
445 
446 static void
uni_image_view_scroll(UniImageView * view,GtkScrollType xscroll,GtkScrollType yscroll)447 uni_image_view_scroll (UniImageView * view,
448                        GtkScrollType xscroll, GtkScrollType yscroll)
449 {
450     GtkAdjustment *hadj = view->hadj;
451     GtkAdjustment *vadj = view->vadj;
452 
453     gdouble h_step = gtk_adjustment_get_step_increment (hadj);
454     gdouble v_step = gtk_adjustment_get_step_increment (vadj);
455     gdouble h_page = gtk_adjustment_get_page_increment (hadj);
456     gdouble v_page = gtk_adjustment_get_page_increment (vadj);
457 
458     int xstep = 0;
459     if (xscroll == GTK_SCROLL_STEP_LEFT)
460         xstep = -h_step;
461     else if (xscroll == GTK_SCROLL_STEP_RIGHT)
462         xstep = h_step;
463     else if (xscroll == GTK_SCROLL_PAGE_LEFT)
464         xstep = -h_page;
465     else if (xscroll == GTK_SCROLL_PAGE_RIGHT)
466         xstep = h_page;
467 
468     int ystep = 0;
469     if (yscroll == GTK_SCROLL_STEP_UP)
470         ystep = -v_step;
471     else if (yscroll == GTK_SCROLL_STEP_DOWN)
472         ystep = v_step;
473     else if (yscroll == GTK_SCROLL_PAGE_UP)
474         ystep = -v_page;
475     else if (yscroll == GTK_SCROLL_PAGE_DOWN)
476         ystep = v_page;
477 
478     uni_image_view_scroll_to (view,
479                               view->offset_x + xstep,
480                               view->offset_y + ystep, TRUE, FALSE);
481 }
482 
483 /*************************************************************/
484 /***** Private signal handlers *******************************/
485 /*************************************************************/
486 static void
uni_image_view_realize(GtkWidget * widget)487 uni_image_view_realize (GtkWidget * widget)
488 {
489     UniImageView *view = UNI_IMAGE_VIEW (widget);
490     gtk_widget_set_realized(widget, TRUE);
491 
492     GtkAllocation allocation;
493     gtk_widget_get_allocation (widget, &allocation);
494 
495     GdkWindowAttr attrs;
496     attrs.window_type = GDK_WINDOW_CHILD;
497     attrs.x = allocation.x;
498     attrs.y = allocation.y;
499     attrs.width = allocation.width;
500     attrs.height = allocation.height;
501     attrs.wclass = GDK_INPUT_OUTPUT;
502     attrs.visual = gtk_widget_get_visual (widget);
503     attrs.colormap = gtk_widget_get_colormap (widget);
504     attrs.event_mask = (gtk_widget_get_events (widget)
505                         | GDK_EXPOSURE_MASK
506                         | GDK_BUTTON_MOTION_MASK
507                         | GDK_BUTTON_PRESS_MASK
508                         | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
509 
510     int attr_mask = (GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP);
511     GdkWindow *parent = gtk_widget_get_parent_window (widget);
512 
513     GdkWindow *window = gdk_window_new (parent, &attrs, attr_mask);
514     gtk_widget_set_window (widget, window);
515     gdk_window_set_user_data (window, view);
516 
517     GtkStyle *style = gtk_widget_get_style (widget);
518     style = gtk_style_attach (style, window);
519     gtk_widget_set_style (widget, style);
520     gtk_style_set_background (style, window, GTK_STATE_NORMAL);
521 
522     view->void_cursor = gdk_cursor_new (GDK_ARROW);
523 }
524 
525 static void
uni_image_view_unrealize(GtkWidget * widget)526 uni_image_view_unrealize (GtkWidget * widget)
527 {
528     UniImageView *view = UNI_IMAGE_VIEW (widget);
529     gdk_cursor_unref (view->void_cursor);
530     GTK_WIDGET_CLASS (uni_image_view_parent_class)->unrealize (widget);
531 }
532 
533 static void
uni_image_view_size_allocate(GtkWidget * widget,GtkAllocation * alloc)534 uni_image_view_size_allocate (GtkWidget * widget, GtkAllocation * alloc)
535 {
536     UniImageView *view = UNI_IMAGE_VIEW (widget);
537     gtk_widget_set_allocation (widget, alloc);
538 
539     if (view->pixbuf && view->fitting != UNI_FITTING_NONE)
540         uni_image_view_zoom_to_fit (view, TRUE);
541 
542     uni_image_view_clamp_offset (view, &view->offset_x, &view->offset_y);
543 
544     uni_image_view_update_adjustments (view);
545 
546     if (gtk_widget_get_realized (widget))
547         gdk_window_move_resize (gtk_widget_get_window (widget),
548                                 alloc->x, alloc->y,
549                                 alloc->width, alloc->height);
550 }
551 
552 static int
uni_image_view_expose(GtkWidget * widget,GdkEventExpose * ev)553 uni_image_view_expose (GtkWidget * widget, GdkEventExpose * ev)
554 {
555     return uni_image_view_repaint_area (UNI_IMAGE_VIEW (widget), &ev->area);
556 }
557 
558 static int
uni_image_view_button_press(GtkWidget * widget,GdkEventButton * ev)559 uni_image_view_button_press (GtkWidget * widget, GdkEventButton * ev)
560 {
561     gtk_widget_grab_focus(widget);
562     UniImageView *view = UNI_IMAGE_VIEW (widget);
563     VnrWindow *vnr_win = VNR_WINDOW(gtk_widget_get_toplevel(widget));
564     g_assert(gtk_widget_is_toplevel(GTK_WIDGET(vnr_win)));
565 
566     if(ev->type == GDK_2BUTTON_PRESS && ev->button == 1 && vnr_win->prefs->behavior_click == VNR_PREFS_CLICK_FULLSCREEN)
567     {
568         vnr_window_toggle_fullscreen(vnr_win);
569         return 1;
570     }
571     else if(ev->type == GDK_2BUTTON_PRESS && ev->button == 1 && vnr_win->prefs->behavior_click == VNR_PREFS_CLICK_NEXT)
572     {
573 
574         int width;
575         gdk_drawable_get_size(GDK_DRAWABLE(gtk_widget_get_window(widget)), &width, NULL);
576 
577         if(ev->x/width < 0.5)
578             vnr_window_prev(vnr_win);
579         else
580             vnr_window_next(vnr_win, TRUE);
581 
582         return 1;
583     }
584     else if (ev->type == GDK_BUTTON_PRESS && ev->button == 1)
585     {
586         return uni_dragger_button_press (UNI_DRAGGER(view->tool), ev);
587     }
588     else if (ev->type == GDK_2BUTTON_PRESS && ev->button == 1)
589     {
590         if (view->fitting == UNI_FITTING_FULL ||
591             (view->fitting == UNI_FITTING_NORMAL && view->zoom != 1.0))
592             uni_image_view_set_zoom_with_center (view, 1., ev->x, ev->y,
593                                                  FALSE);
594         else
595             uni_image_view_set_fitting (view, UNI_FITTING_FULL);
596         return 1;
597     }
598     else if(ev->type == GDK_BUTTON_PRESS && ev->button == 3)
599     {
600         gtk_menu_popup(GTK_MENU(VNR_WINDOW(gtk_widget_get_toplevel (widget))->popup_menu),
601                 NULL, NULL, NULL, NULL, ev->button,
602                 gtk_get_current_event_time());
603 
604     }
605     else if(ev->type == GDK_BUTTON_PRESS && ev->button == 8)
606     {
607         vnr_window_prev(vnr_win);
608     }
609     else if(ev->type == GDK_BUTTON_PRESS && ev->button == 9)
610     {
611         vnr_window_next(vnr_win, TRUE);
612     }
613     return 0;
614 }
615 
616 static int
uni_image_view_button_release(GtkWidget * widget,GdkEventButton * ev)617 uni_image_view_button_release (GtkWidget * widget, GdkEventButton * ev)
618 {
619     UniImageView *view = UNI_IMAGE_VIEW (widget);
620     return uni_dragger_button_release (UNI_DRAGGER(view->tool), ev);
621 }
622 
623 static int
uni_image_view_motion_notify(GtkWidget * widget,GdkEventMotion * ev)624 uni_image_view_motion_notify (GtkWidget * widget, GdkEventMotion * ev)
625 {
626     UniImageView *view = UNI_IMAGE_VIEW (widget);
627     if (view->is_rendering)
628         return FALSE;
629     return uni_dragger_motion_notify (UNI_DRAGGER(view->tool), ev);
630 }
631 
632 static gboolean
uni_image_view_hadj_changed_cb(GtkObject * adj,UniImageView * view)633 uni_image_view_hadj_changed_cb (GtkObject * adj, UniImageView * view)
634 {
635     int offset_x;
636     offset_x = gtk_adjustment_get_value (GTK_ADJUSTMENT (adj));
637     uni_image_view_scroll_to (view, offset_x, view->offset_y, FALSE, FALSE);
638     return FALSE;
639 }
640 
641 static gboolean
uni_image_view_vadj_changed_cb(GtkObject * adj,UniImageView * view)642 uni_image_view_vadj_changed_cb (GtkObject * adj, UniImageView * view)
643 {
644     int offset_y;
645     offset_y = gtk_adjustment_get_value (GTK_ADJUSTMENT (adj));
646     uni_image_view_scroll_to (view, view->offset_x, offset_y, FALSE, FALSE);
647     return FALSE;
648 }
649 
650 static int
uni_image_view_scroll_event(GtkWidget * widget,GdkEventScroll * ev)651 uni_image_view_scroll_event (GtkWidget * widget, GdkEventScroll * ev)
652 {
653     gdouble zoom;
654     UniImageView *view = UNI_IMAGE_VIEW (widget);
655     VnrWindow *vnr_win = VNR_WINDOW(gtk_widget_get_toplevel(widget));
656     g_assert(gtk_widget_is_toplevel(GTK_WIDGET(vnr_win)));
657 
658     /* Horizontal scroll left is equivalent to scroll up and right is
659      * like scroll down. No idea if that is correct -- I have no input
660      * device that can do horizontal scrolls. */
661 
662 	if (vnr_win->prefs->behavior_wheel == VNR_PREFS_WHEEL_ZOOM || (ev->state & GDK_CONTROL_MASK) != 0)
663 	{
664         switch (ev->direction)
665         {
666             case GDK_SCROLL_LEFT:
667                 // In Zoom mode left/right scroll is used for navigation
668                 vnr_window_prev(vnr_win);
669                 break;
670             case GDK_SCROLL_RIGHT:
671                 vnr_window_next(vnr_win, TRUE);
672                 break;
673             case GDK_SCROLL_UP:
674                 if( ev->state & GDK_SHIFT_MASK ) {
675                     vnr_window_prev(vnr_win);
676                 } else {
677                     zoom = CLAMP (view->zoom * UNI_ZOOM_STEP, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
678                     uni_image_view_set_zoom_with_center (view, zoom, ev->x, ev->y, FALSE);
679                 }
680                 break;
681             default:
682                 if( ev->state & GDK_SHIFT_MASK ) {
683                     vnr_window_next(vnr_win, TRUE);
684                 } else {
685                     zoom = CLAMP (view->zoom / UNI_ZOOM_STEP, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
686                     uni_image_view_set_zoom_with_center (view, zoom, ev->x, ev->y, FALSE);
687                 }
688         }
689 
690 	}
691 	else if(vnr_win->prefs->behavior_wheel == VNR_PREFS_WHEEL_NAVIGATE)
692 	{
693         switch (ev->direction)
694         {
695             case GDK_SCROLL_LEFT:
696                 zoom = CLAMP (view->zoom * UNI_ZOOM_STEP, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
697                 uni_image_view_set_zoom_with_center (view, zoom, ev->x, ev->y, FALSE);
698                 break;
699 
700             case GDK_SCROLL_RIGHT:
701                 zoom = CLAMP (view->zoom / UNI_ZOOM_STEP, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
702                 uni_image_view_set_zoom_with_center (view, zoom, ev->x, ev->y, FALSE);
703                 break;
704 
705             case GDK_SCROLL_UP:
706                 if( ev->state & GDK_SHIFT_MASK )
707                 {
708                     zoom = CLAMP (view->zoom * UNI_ZOOM_STEP, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
709                     uni_image_view_set_zoom_with_center (view, zoom, ev->x, ev->y, FALSE);
710                 }
711                 else
712                     vnr_window_prev(vnr_win);
713 
714                 break;
715 
716             default:
717                 if( ev->state & GDK_SHIFT_MASK )
718                 {
719                     zoom = CLAMP (view->zoom / UNI_ZOOM_STEP, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
720                     uni_image_view_set_zoom_with_center (view, zoom, ev->x, ev->y, FALSE);
721                 }
722                 else
723                     vnr_window_next(vnr_win, TRUE);
724         }
725 	}
726 	else
727 	{
728 		switch (ev->direction)
729 		{
730 			case GDK_SCROLL_LEFT:
731                 uni_image_view_scroll (view, GTK_SCROLL_PAGE_LEFT, GTK_SCROLL_NONE);
732                 break;
733 			case GDK_SCROLL_RIGHT:
734                 uni_image_view_scroll (view, GTK_SCROLL_PAGE_RIGHT, GTK_SCROLL_NONE);
735                 break;
736             case GDK_SCROLL_UP:
737                 if( ev->state & GDK_SHIFT_MASK )
738                     uni_image_view_scroll (view, GTK_SCROLL_PAGE_LEFT, GTK_SCROLL_NONE);
739                 else
740                     uni_image_view_scroll (view, GTK_SCROLL_NONE, GTK_SCROLL_PAGE_UP);
741                 break;
742             default:
743                 if( ev->state & GDK_SHIFT_MASK )
744                     uni_image_view_scroll (view, GTK_SCROLL_PAGE_RIGHT, GTK_SCROLL_NONE);
745                 else
746                     uni_image_view_scroll (view, GTK_SCROLL_NONE, GTK_SCROLL_PAGE_DOWN);
747 		}
748 	}
749 
750     return TRUE;
751 }
752 
753 static void
uni_image_view_set_scroll_adjustments(UniImageView * view,GtkAdjustment * hadj,GtkAdjustment * vadj)754 uni_image_view_set_scroll_adjustments (UniImageView * view,
755                                        GtkAdjustment * hadj,
756                                        GtkAdjustment * vadj)
757 {
758     if (hadj && view->hadj && view->hadj != hadj)
759     {
760         g_signal_handlers_disconnect_by_data (G_OBJECT (view->hadj), view);
761         g_object_unref (view->hadj);
762         g_signal_connect (G_OBJECT (hadj),
763                           "value_changed",
764                           G_CALLBACK (uni_image_view_hadj_changed_cb), view);
765         view->hadj = hadj;
766         g_object_ref_sink (view->hadj);
767     }
768     if (vadj && view->vadj && view->vadj != vadj)
769     {
770         g_signal_handlers_disconnect_by_data (G_OBJECT (view->vadj), view);
771         g_object_unref (view->vadj);
772         g_signal_connect (G_OBJECT (vadj),
773                           "value_changed",
774                           G_CALLBACK (uni_image_view_vadj_changed_cb), view);
775         view->vadj = vadj;
776         g_object_ref_sink (view->vadj);
777     }
778 }
779 
780 
781 /*************************************************************/
782 /***** Stuff that deals with the type ************************/
783 /*************************************************************/
784 static void
uni_image_view_init(UniImageView * view)785 uni_image_view_init (UniImageView * view)
786 {
787     gtk_widget_set_can_focus (GTK_WIDGET(view), TRUE);
788 
789     view->interp = GDK_INTERP_BILINEAR;
790     view->fitting = UNI_FITTING_NORMAL;
791     view->pixbuf = NULL;
792     view->zoom = 1.0;
793     view->offset_x = 0.0;
794     view->offset_y = 0.0;
795     view->is_rendering = FALSE;
796     view->show_cursor = TRUE;
797     view->void_cursor = NULL;
798     view->tool = G_OBJECT (uni_dragger_new ((GtkWidget *) view));
799 
800     view->hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 1.0, 0.0,
801                                                      1.0, 1.0, 1.0));
802     view->vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 1.0, 0.0,
803                                                      1.0, 1.0, 1.0));
804     g_object_ref_sink (view->hadj);
805     g_object_ref_sink (view->vadj);
806 
807     GtkWidget *widget = (GtkWidget *) view;
808     GtkAllocation allocation;
809     gtk_widget_get_allocation (widget, &allocation);
810     allocation.width = 0;
811     allocation.height = 0;
812     gtk_widget_set_allocation (widget, &allocation);
813 }
814 
815 static void
uni_image_view_finalize(GObject * object)816 uni_image_view_finalize (GObject * object)
817 {
818     UniImageView *view = UNI_IMAGE_VIEW (object);
819     if (view->hadj)
820     {
821         g_signal_handlers_disconnect_by_data (G_OBJECT (view->hadj), view);
822         g_object_unref (view->hadj);
823         view->hadj = NULL;
824     }
825     if (view->vadj)
826     {
827         g_signal_handlers_disconnect_by_data (G_OBJECT (view->vadj), view);
828         g_object_unref (view->vadj);
829         view->vadj = NULL;
830     }
831     if (view->pixbuf)
832     {
833         g_object_unref (view->pixbuf);
834         view->pixbuf = NULL;
835     }
836     g_object_unref (view->tool);
837     /* Chain up. */
838     G_OBJECT_CLASS (uni_image_view_parent_class)->finalize (object);
839 }
840 
841 static void
uni_image_view_init_signals(UniImageViewClass * klass)842 uni_image_view_init_signals (UniImageViewClass * klass)
843 {
844     uni_image_view_signals[SET_ZOOM] =
845         g_signal_new ("set_zoom",
846                       G_TYPE_FROM_CLASS (klass),
847                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
848                       G_STRUCT_OFFSET (UniImageViewClass, set_zoom),
849                       NULL, NULL,
850                       g_cclosure_marshal_VOID__DOUBLE,
851                       G_TYPE_NONE, 1, G_TYPE_DOUBLE);
852     uni_image_view_signals[ZOOM_IN] =
853         g_signal_new ("zoom_in",
854                       G_TYPE_FROM_CLASS (klass),
855                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
856                       G_STRUCT_OFFSET (UniImageViewClass, zoom_in),
857                       NULL, NULL,
858                       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
859     uni_image_view_signals[ZOOM_OUT] =
860         g_signal_new ("zoom_out",
861                       G_TYPE_FROM_CLASS (klass),
862                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
863                       G_STRUCT_OFFSET (UniImageViewClass, zoom_out),
864                       NULL, NULL,
865                       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
866     uni_image_view_signals[SET_FITTING] =
867         g_signal_new ("set_fitting",
868                       G_TYPE_FROM_CLASS (klass),
869                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
870                       G_STRUCT_OFFSET (UniImageViewClass, set_fitting),
871                       NULL, NULL,
872                       g_cclosure_marshal_VOID__ENUM,
873                       G_TYPE_NONE, 1, G_TYPE_INT);
874     uni_image_view_signals[SCROLL] =
875         g_signal_new ("scroll",
876                       G_TYPE_FROM_CLASS (klass),
877                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
878                       G_STRUCT_OFFSET (UniImageViewClass, scroll),
879                       NULL, NULL,
880                       uni_marshal_VOID__ENUM_ENUM,
881                       G_TYPE_NONE,
882                       2, GTK_TYPE_SCROLL_TYPE, GTK_TYPE_SCROLL_TYPE);
883     uni_image_view_signals[ZOOM_CHANGED] =
884         g_signal_new ("zoom_changed",
885                       G_TYPE_FROM_CLASS (klass),
886                       G_SIGNAL_RUN_LAST,
887                       0,
888                       NULL, NULL,
889                       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
890     /**
891      * UniImageView::pixbuf-changed:
892      * @view: The view that emitted the signal.
893      *
894      * The ::pixbuf-changed signal is emitted when the pixbuf the
895      * image view shows is changed and when its image data is changed.
896      * Listening to this signal is useful if you, for example, have a
897      * label that displays the width and height of the pixbuf in the
898      * view.
899      **/
900     uni_image_view_signals[PIXBUF_CHANGED] =
901         g_signal_new ("pixbuf_changed",
902                       G_TYPE_FROM_CLASS (klass),
903                       G_SIGNAL_RUN_LAST,
904                       G_STRUCT_OFFSET (UniImageViewClass, pixbuf_changed),
905                       NULL, NULL,
906                       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
907 }
908 
909 static void
uni_image_view_class_init(UniImageViewClass * klass)910 uni_image_view_class_init (UniImageViewClass * klass)
911 {
912     uni_image_view_init_signals (klass);
913 
914     GObjectClass *object_class = (GObjectClass *) klass;
915     object_class->finalize = uni_image_view_finalize;
916 
917     GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
918     widget_class->button_press_event = uni_image_view_button_press;
919     widget_class->button_release_event = uni_image_view_button_release;
920     widget_class->expose_event = uni_image_view_expose;
921     widget_class->motion_notify_event = uni_image_view_motion_notify;
922     widget_class->realize = uni_image_view_realize;
923     widget_class->scroll_event = uni_image_view_scroll_event;
924     widget_class->size_allocate = uni_image_view_size_allocate;
925     widget_class->unrealize = uni_image_view_unrealize;
926 
927     klass->set_zoom = uni_image_view_set_zoom;
928     klass->zoom_in = uni_image_view_zoom_in;
929     klass->zoom_out = uni_image_view_zoom_out;
930     klass->set_fitting = uni_image_view_set_fitting;
931     klass->scroll = uni_image_view_scroll;
932     klass->pixbuf_changed = NULL;
933 
934     /**
935      * UniImageView::set-scroll-adjustments
936      *
937      * Do we really need this signal? It should be intrinsic to the
938      * GtkWidget class, shouldn't it?
939      **/
940     widget_class->set_scroll_adjustments_signal =
941         g_signal_new ("set_scroll_adjustments",
942                       G_TYPE_FROM_CLASS (klass),
943                       G_SIGNAL_RUN_LAST,
944                       G_STRUCT_OFFSET (UniImageViewClass,
945                                        set_scroll_adjustments),
946                       NULL, NULL,
947                       uni_marshal_VOID__POINTER_POINTER,
948                       G_TYPE_NONE,
949                       2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
950     klass->set_scroll_adjustments = uni_image_view_set_scroll_adjustments;
951 
952     /* Add keybindings. */
953     GtkBindingSet *binding_set = gtk_binding_set_by_class (klass);
954 
955     /* Set zoom. */
956     gtk_binding_entry_add_signal (binding_set, GDK_KEY_1, 0,
957                                   "set_zoom", 1, G_TYPE_DOUBLE, 1.0);
958     gtk_binding_entry_add_signal (binding_set, GDK_KEY_2, 0,
959                                   "set_zoom", 1, G_TYPE_DOUBLE, 2.0);
960     gtk_binding_entry_add_signal (binding_set, GDK_KEY_3, 0,
961                                   "set_zoom", 1, G_TYPE_DOUBLE, 3.0);
962     gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_1, 0,
963                                   "set_zoom", 1, G_TYPE_DOUBLE, 1.0);
964     gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_2, 0,
965                                   "set_zoom", 1, G_TYPE_DOUBLE, 2.0);
966     gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_3, 0,
967                                   "set_zoom", 1, G_TYPE_DOUBLE, 3.0);
968 
969     /* Zoom in */
970     gtk_binding_entry_add_signal (binding_set, GDK_KEY_plus, 0, "zoom_in", 0);
971     gtk_binding_entry_add_signal (binding_set, GDK_KEY_equal, 0, "zoom_in", 0);
972     gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Add, 0, "zoom_in", 0);
973 
974     /* Zoom out */
975     gtk_binding_entry_add_signal (binding_set, GDK_KEY_minus, 0, "zoom_out", 0);
976     gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Subtract, 0,
977                                   "zoom_out", 0);
978 
979     /* Set fitting */
980     gtk_binding_entry_add_signal (binding_set, GDK_KEY_f, 0,
981                                   "set_fitting", 1, G_TYPE_ENUM, UNI_FITTING_FULL);
982     gtk_binding_entry_add_signal (binding_set, GDK_KEY_0, 0,
983                                   "set_fitting", 1, G_TYPE_ENUM, UNI_FITTING_FULL);
984     gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_0, 0,
985                                   "set_fitting", 1, G_TYPE_ENUM, UNI_FITTING_FULL);
986 
987     /* Unmodified scrolling */
988     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Right, 0,
989                                   "scroll", 2,
990                                   GTK_TYPE_SCROLL_TYPE,
991                                   GTK_SCROLL_STEP_RIGHT,
992                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE);
993     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Left, 0,
994                                   "scroll", 2,
995                                   GTK_TYPE_SCROLL_TYPE,
996                                   GTK_SCROLL_STEP_LEFT,
997                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE);
998     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, 0,
999                                   "scroll", 2,
1000                                   GTK_TYPE_SCROLL_TYPE,
1001                                   GTK_SCROLL_NONE,
1002                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_DOWN);
1003     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, 0,
1004                                   "scroll", 2,
1005                                   GTK_TYPE_SCROLL_TYPE,
1006                                   GTK_SCROLL_NONE,
1007                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_UP);
1008 
1009     /* Shifted scrolling */
1010     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Right, GDK_SHIFT_MASK,
1011                                   "scroll", 2,
1012                                   GTK_TYPE_SCROLL_TYPE,
1013                                   GTK_SCROLL_PAGE_RIGHT,
1014                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE);
1015     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Left, GDK_SHIFT_MASK,
1016                                   "scroll", 2,
1017                                   GTK_TYPE_SCROLL_TYPE,
1018                                   GTK_SCROLL_PAGE_LEFT,
1019                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE);
1020     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, GDK_SHIFT_MASK,
1021                                   "scroll", 2,
1022                                   GTK_TYPE_SCROLL_TYPE,
1023                                   GTK_SCROLL_NONE,
1024                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_UP);
1025     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, GDK_SHIFT_MASK,
1026                                   "scroll", 2,
1027                                   GTK_TYPE_SCROLL_TYPE,
1028                                   GTK_SCROLL_NONE,
1029                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_DOWN);
1030 
1031     /* Page Up & Down */
1032     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Up, 0,
1033                                   "scroll", 2,
1034                                   GTK_TYPE_SCROLL_TYPE,
1035                                   GTK_SCROLL_NONE,
1036                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_UP);
1037     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Down, 0,
1038                                   "scroll", 2,
1039                                   GTK_TYPE_SCROLL_TYPE,
1040                                   GTK_SCROLL_NONE,
1041                                   GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_DOWN);
1042 }
1043 
1044 /**
1045  * uni_image_view_new:
1046  * @returns: a new #UniImageView.
1047  *
1048  * Creates a new image view with default values.
1049  **/
1050 GtkWidget *
uni_image_view_new(void)1051 uni_image_view_new (void)
1052 {
1053     GtkWidget *view = g_object_new (UNI_TYPE_IMAGE_VIEW, NULL);
1054     return view;
1055 }
1056 
1057 /*************************************************************/
1058 /***** Read-only properties **********************************/
1059 /*************************************************************/
1060 /**
1061  * uni_image_view_get_viewport:
1062  * @view: a #UniImageView
1063  * @rect: a #GdkRectangle to fill in with the current viewport or
1064  *   %NULL.
1065  * @returns: %TRUE if a #GdkPixbuf is shown, %FALSE otherwise.
1066  *
1067  * Fills in the rectangle with the current viewport. If pixbuf is
1068  * %NULL, there is no viewport, @rect is left untouched and %FALSE is
1069  * returned.
1070  *
1071  * The current viewport is defined as the rectangle, in zoomspace
1072  * coordinates as the area of the loaded pixbuf the #UniImageView is
1073  * currently showing.
1074  **/
1075 gboolean
uni_image_view_get_viewport(UniImageView * view,GdkRectangle * rect)1076 uni_image_view_get_viewport (UniImageView * view, GdkRectangle * rect)
1077 {
1078     gboolean ret_val = (view->pixbuf != NULL);
1079     if (!rect || !ret_val)
1080         return ret_val;
1081 
1082     Size alloc = uni_image_view_get_allocated_size (view);
1083     Size zoomed = uni_image_view_get_zoomed_size (view);
1084     rect->x = view->offset_x;
1085     rect->y = view->offset_y;
1086     rect->width = MIN (alloc.width, zoomed.width);
1087     rect->height = MIN (alloc.height, zoomed.height);
1088     return TRUE;
1089 }
1090 
1091 /**
1092  * uni_image_view_get_draw_rect:
1093  * @view: a #UniImageView
1094  * @rect: a #GdkRectangle to fill in with the area of the widget in
1095  *   which the pixbuf is drawn.
1096  * @returns: %TRUE if the view is allocated and has a pixbuf, %FALSE
1097  *   otherwise.
1098  *
1099  * Get the rectangle in the widget where the pixbuf is painted.
1100  *
1101  * For example, if the widgets allocated size is 100, 100 and the
1102  * pixbufs size is 50, 50 and the zoom factor is 1.0, then the pixbuf
1103  * will be drawn centered on the widget. @rect will then be
1104  * (25,25)-[50,50].
1105  *
1106  * This method is useful when converting from widget to image or zoom
1107  * space coordinates.
1108  **/
1109 gboolean
uni_image_view_get_draw_rect(UniImageView * view,GdkRectangle * rect)1110 uni_image_view_get_draw_rect (UniImageView * view, GdkRectangle * rect)
1111 {
1112     if (!view->pixbuf)
1113         return FALSE;
1114     Size alloc = uni_image_view_get_allocated_size (view);
1115     Size zoomed = uni_image_view_get_zoomed_size (view);
1116 
1117     rect->x = (alloc.width - zoomed.width) / 2;
1118     rect->y = (alloc.height - zoomed.height) / 2;
1119     rect->x = MAX (rect->x, 0);
1120     rect->y = MAX (rect->y, 0);
1121     rect->width = MIN (zoomed.width, alloc.width);
1122     rect->height = MIN (zoomed.height, alloc.height);
1123     return TRUE;
1124 }
1125 
1126 /*************************************************************/
1127 /***** Write-only properties *********************************/
1128 /*************************************************************/
1129 /**
1130  * uni_image_view_set_offset:
1131  * @view: A #UniImageView.
1132  * @x: X-component of the offset in zoom space coordinates.
1133  * @y: Y-component of the offset in zoom space coordinates.
1134  * @invalidate: whether to invalidate the view or redraw immediately.
1135  *
1136  * Sets the offset of where in the image the #UniImageView should
1137  * begin displaying image data.
1138  *
1139  * The offset is clamped so that it will never cause the #UniImageView
1140  * to display pixels outside the pixbuf. Setting this attribute causes
1141  * the widget to repaint itself if it is realized.
1142  *
1143  * If @invalidate is %TRUE, the views entire area will be invalidated
1144  * instead of redrawn immediately. The view is then queued for redraw,
1145  * which means that additional operations can be performed on it
1146  * before it is redrawn.
1147  *
1148  * The difference can sometimes be important like when you are
1149  * overlaying data and get flicker or artifacts when setting the
1150  * offset. If that happens, setting @invalidate to %TRUE could fix the
1151  * problem. See the source code to #GtkImageToolSelector for an
1152  * example.
1153  *
1154  * Normally, @invalidate should always be %FALSE because it is much
1155  * faster to repaint immedately than invalidating.
1156  **/
1157 void
uni_image_view_set_offset(UniImageView * view,gdouble offset_x,gdouble offset_y,gboolean invalidate)1158 uni_image_view_set_offset (UniImageView * view,
1159                            gdouble offset_x,
1160                            gdouble offset_y, gboolean invalidate)
1161 {
1162     uni_image_view_scroll_to (view, offset_x, offset_y, TRUE, invalidate);
1163 }
1164 
1165 /*************************************************************/
1166 /***** Read-write properties *********************************/
1167 /*************************************************************/
1168 void
uni_image_view_set_fitting(UniImageView * view,UniFittingMode fitting)1169 uni_image_view_set_fitting (UniImageView * view, UniFittingMode fitting)
1170 {
1171     g_return_if_fail (UNI_IS_IMAGE_VIEW (view));
1172     view->fitting = fitting;
1173     gtk_widget_queue_resize (GTK_WIDGET (view));
1174 }
1175 
1176 /**
1177  * uni_image_view_get_pixbuf:
1178  * @view: A #UniImageView.
1179  * @returns: The pixbuf this view shows.
1180  *
1181  * Returns the pixbuf this view shows.
1182  **/
1183 GdkPixbuf *
uni_image_view_get_pixbuf(UniImageView * view)1184 uni_image_view_get_pixbuf (UniImageView * view)
1185 {
1186     g_return_val_if_fail (UNI_IS_IMAGE_VIEW (view), NULL);
1187     return view->pixbuf;
1188 }
1189 
1190 /**
1191  * uni_image_view_set_pixbuf:
1192  * @view: A #UniImageView.
1193  * @pixbuf: The pixbuf to display.
1194  * @reset_fit: Whether to reset fitting or not.
1195  *
1196  * Sets the @pixbuf to display, or %NULL to not display any pixbuf.
1197  * Normally, @reset_fit should be %TRUE which enables fitting. Which
1198  * means that, initially, the whole pixbuf will be shown.
1199  *
1200  * Sometimes, the fit mode should not be reset. For example, if
1201  * UniImageView is showing an animation, it would be bad to reset the
1202  * fit mode for each new frame. The parameter should then be %FALSE
1203  * which leaves the fit mode of the view untouched.
1204  *
1205  * This method should not be used if merely the contents of the pixbuf
1206  * has changed. See uni_image_view_damage_pixels() for that.
1207  *
1208  * If @reset_fit is %TRUE, the ::zoom-changed signal is emitted,
1209  * otherwise not. The ::pixbuf-changed signal is also emitted.
1210  *
1211  * The default pixbuf is %NULL.
1212  **/
1213 void
uni_image_view_set_pixbuf(UniImageView * view,GdkPixbuf * pixbuf,gboolean reset_fit)1214 uni_image_view_set_pixbuf (UniImageView * view,
1215                            GdkPixbuf * pixbuf, gboolean reset_fit)
1216 {
1217     if (view->pixbuf != pixbuf)
1218     {
1219         if (view->pixbuf)
1220             g_object_unref (view->pixbuf);
1221         view->pixbuf = pixbuf;
1222         if (view->pixbuf)
1223             g_object_ref (pixbuf);
1224     }
1225 
1226     if (reset_fit)
1227         uni_image_view_set_fitting (view, UNI_FITTING_NORMAL);
1228     else
1229     {
1230         /*
1231            If the size of the pixbuf changes, the offset might point to
1232            pixels outside it so we use uni_image_view_scroll_to() to
1233            make it valid again. And if the size is different, naturally
1234            we must also update the adjustments.
1235          */
1236         uni_image_view_scroll_to (view, view->offset_x, view->offset_y,
1237                                   FALSE, FALSE);
1238         uni_image_view_update_adjustments (view);
1239         gtk_widget_queue_draw (GTK_WIDGET (view));
1240     }
1241 
1242     g_signal_emit (G_OBJECT (view),
1243                    uni_image_view_signals[PIXBUF_CHANGED], 0);
1244     uni_dragger_pixbuf_changed (UNI_DRAGGER(view->tool), reset_fit, NULL);
1245 }
1246 
1247 /**
1248  * uni_image_view_set_zoom:
1249  * @view: a #UniImageView
1250  * @zoom: the new zoom factor
1251  *
1252  * Sets the zoom of the view.
1253  *
1254  * Fitting is always disabled after this method has run. The
1255  * ::zoom-changed signal is unconditionally emitted.
1256  **/
1257 void
uni_image_view_set_zoom(UniImageView * view,gdouble zoom)1258 uni_image_view_set_zoom (UniImageView * view, gdouble zoom)
1259 {
1260     g_return_if_fail (UNI_IS_IMAGE_VIEW (view));
1261     zoom = CLAMP (zoom, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
1262     uni_image_view_set_zoom_no_center (view, zoom, FALSE);
1263 }
1264 
1265 
1266 void
uni_image_view_set_zoom_mode(UniImageView * view,VnrPrefsZoom mode)1267 uni_image_view_set_zoom_mode (UniImageView * view, VnrPrefsZoom mode)
1268 {
1269     switch(mode)
1270     {
1271         case VNR_PREFS_ZOOM_NORMAL:
1272             uni_image_view_set_fitting(view, UNI_FITTING_NONE);
1273             //view->zoom = 1.0;
1274             uni_image_view_set_zoom (view, 1.0);
1275         break;
1276         case VNR_PREFS_ZOOM_FIT:
1277             uni_image_view_set_fitting(view, UNI_FITTING_FULL);
1278         break;
1279         case VNR_PREFS_ZOOM_SMART:
1280             uni_image_view_set_fitting(view, UNI_FITTING_NORMAL);
1281         break;
1282         default: break;
1283     }
1284 }
1285 
1286 /*************************************************************/
1287 /***** Actions ***********************************************/
1288 /*************************************************************/
1289 /**
1290  * uni_image_view_zoom_in:
1291  * @view: a #UniImageView
1292  *
1293  * Zoom in the view one step. Calling this method causes the widget to
1294  * immediately repaint itself.
1295  **/
1296 void
uni_image_view_zoom_in(UniImageView * view)1297 uni_image_view_zoom_in (UniImageView * view)
1298 {
1299     gdouble zoom;
1300     zoom = CLAMP (view->zoom * UNI_ZOOM_STEP, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
1301     uni_image_view_set_zoom (view, zoom);
1302 }
1303 
1304 /**
1305  * uni_image_view_zoom_out:
1306  * @view: a #UniImageView
1307  *
1308  * Zoom out the view one step. Calling this method causes the widget to
1309  * immediately repaint itself.
1310  **/
1311 void
uni_image_view_zoom_out(UniImageView * view)1312 uni_image_view_zoom_out (UniImageView * view)
1313 {
1314     gdouble zoom;
1315     zoom = CLAMP (view->zoom / UNI_ZOOM_STEP, UNI_ZOOM_MIN, UNI_ZOOM_MAX);
1316     uni_image_view_set_zoom (view, zoom);
1317 }
1318