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