expose/draw method moved to renderer-tiles
[geeqie.git] / src / pixbuf-renderer.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2012 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <math.h>
17
18 #include "main.h"
19 #include "pixbuf-renderer.h"
20 #include "renderer-tiles.h"
21 #include "renderer-clutter.h"
22
23 #include "intl.h"
24 #include "layout.h"
25
26 #include <gtk/gtk.h>
27
28 #define RENDERER_NEW(pr) renderer_clutter_new(pr)
29
30 /* comment this out if not using this from within Geeqie
31  * defining GQ_BUILD does these things:
32  *   - Sets the shift-click scroller pixbuf to a nice icon instead of a black box
33  */
34 #define GQ_BUILD 1
35
36 #ifdef GQ_BUILD
37 #include "main.h"
38 #include "pixbuf_util.h"
39 #include "exif.h"
40 #else
41 typedef enum {
42         EXIF_ORIENTATION_UNKNOWN        = 0,
43         EXIF_ORIENTATION_TOP_LEFT       = 1,
44         EXIF_ORIENTATION_TOP_RIGHT      = 2,
45         EXIF_ORIENTATION_BOTTOM_RIGHT   = 3,
46         EXIF_ORIENTATION_BOTTOM_LEFT    = 4,
47         EXIF_ORIENTATION_LEFT_TOP       = 5,
48         EXIF_ORIENTATION_RIGHT_TOP      = 6,
49         EXIF_ORIENTATION_RIGHT_BOTTOM   = 7,
50         EXIF_ORIENTATION_LEFT_BOTTOM    = 8
51 } ExifOrientationType;
52 #endif
53
54
55 /* default min and max zoom */
56 #define PR_ZOOM_MIN -32.0
57 #define PR_ZOOM_MAX 32.0
58
59 /* distance to drag mouse to disable image flip */
60 #define PR_DRAG_SCROLL_THRESHHOLD 4
61
62 /* increase pan rate when holding down shift */
63 #define PR_PAN_SHIFT_MULTIPLIER 6
64
65 /* scroller config */
66 #define PR_SCROLLER_UPDATES_PER_SEC 30
67 #define PR_SCROLLER_DEAD_ZONE 6
68
69 /* when scaling image to below this size, use nearest pixel for scaling
70  * (below about 4, the other scale types become slow generating their conversion tables)
71  */
72 #define PR_MIN_SCALE_SIZE 8
73
74 enum {
75         SIGNAL_ZOOM = 0,
76         SIGNAL_CLICKED,
77         SIGNAL_SCROLL_NOTIFY,
78         SIGNAL_RENDER_COMPLETE,
79         SIGNAL_DRAG,
80         SIGNAL_UPDATE_PIXEL,
81         SIGNAL_COUNT
82 };
83
84 enum {
85         PROP_0,
86         PROP_ZOOM_MIN,
87         PROP_ZOOM_MAX,
88         PROP_ZOOM_QUALITY,
89         PROP_ZOOM_2PASS,
90         PROP_ZOOM_EXPAND,
91         PROP_SCROLL_RESET,
92         PROP_DELAY_FLIP,
93         PROP_LOADING,
94         PROP_COMPLETE,
95         PROP_CACHE_SIZE_DISPLAY,
96         PROP_CACHE_SIZE_TILES,
97         PROP_WINDOW_FIT,
98         PROP_WINDOW_LIMIT,
99         PROP_WINDOW_LIMIT_VALUE,
100         PROP_AUTOFIT_LIMIT,
101         PROP_AUTOFIT_LIMIT_VALUE
102 };
103
104 typedef enum {
105         PR_ZOOM_NONE            = 0,
106         PR_ZOOM_FORCE           = 1 << 0,
107         PR_ZOOM_NEW             = 1 << 1,
108         PR_ZOOM_CENTER          = 1 << 2,
109         PR_ZOOM_INVALIDATE      = 1 << 3,
110         PR_ZOOM_LAZY            = 1 << 4  /* wait with redraw for pixbuf_renderer_area_changed */
111 } PrZoomFlags;
112
113 static guint signals[SIGNAL_COUNT] = { 0 };
114 static GtkEventBoxClass *parent_class = NULL;
115
116
117
118 static void pixbuf_renderer_class_init(PixbufRendererClass *class);
119 static void pixbuf_renderer_init(PixbufRenderer *pr);
120 static void pixbuf_renderer_finalize(GObject *object);
121 static void pixbuf_renderer_set_property(GObject *object, guint prop_id,
122                                          const GValue *value, GParamSpec *pspec);
123 static void pixbuf_renderer_get_property(GObject *object, guint prop_id,
124                                          GValue *value, GParamSpec *pspec);
125 static void pr_scroller_timer_set(PixbufRenderer *pr, gboolean start);
126
127
128 static void pr_source_tile_free_all(PixbufRenderer *pr);
129
130 static void pr_zoom_sync(PixbufRenderer *pr, gdouble zoom,
131                          PrZoomFlags flags, gint px, gint py);
132
133 static void pr_signals_connect(PixbufRenderer *pr);
134 static void pr_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data);
135 static void pr_stereo_temp_disable(PixbufRenderer *pr, gboolean disable);
136
137
138 /*
139  *-------------------------------------------------------------------
140  * Pixbuf Renderer object
141  *-------------------------------------------------------------------
142  */
143
144 GType pixbuf_renderer_get_type(void)
145 {
146         static GType pixbuf_renderer_type = 0;
147
148         if (!pixbuf_renderer_type)
149                 {
150                 static const GTypeInfo pixbuf_renderer_info =
151                         {
152                         sizeof(PixbufRendererClass), /* class_size */
153                         NULL,           /* base_init */
154                         NULL,           /* base_finalize */
155                         (GClassInitFunc)pixbuf_renderer_class_init,
156                         NULL,           /* class_finalize */
157                         NULL,           /* class_data */
158                         sizeof(PixbufRenderer), /* instance_size */
159                         0,              /* n_preallocs */
160                         (GInstanceInitFunc)pixbuf_renderer_init, /* instance_init */
161                         NULL,           /* value_table */
162                         };
163
164                 pixbuf_renderer_type = g_type_register_static(GTK_TYPE_EVENT_BOX, "PixbufRenderer",
165                                                               &pixbuf_renderer_info, 0);
166                 }
167
168         return pixbuf_renderer_type;
169 }
170
171 static void pixbuf_renderer_class_init(PixbufRendererClass *class)
172 {
173         GObjectClass *gobject_class = G_OBJECT_CLASS(class);
174
175         parent_class = g_type_class_peek_parent(class);
176
177         gobject_class->set_property = pixbuf_renderer_set_property;
178         gobject_class->get_property = pixbuf_renderer_get_property;
179
180         gobject_class->finalize = pixbuf_renderer_finalize;
181
182         g_object_class_install_property(gobject_class,
183                                         PROP_ZOOM_MIN,
184                                         g_param_spec_double("zoom_min",
185                                                             "Zoom minimum",
186                                                             NULL,
187                                                             -1000.0,
188                                                             1000.0,
189                                                             PR_ZOOM_MIN,
190                                                             G_PARAM_READABLE | G_PARAM_WRITABLE));
191
192         g_object_class_install_property(gobject_class,
193                                         PROP_ZOOM_MAX,
194                                         g_param_spec_double("zoom_max",
195                                                             "Zoom maximum",
196                                                             NULL,
197                                                             -1000.0,
198                                                             1000.0,
199                                                             PR_ZOOM_MIN,
200                                                             G_PARAM_READABLE | G_PARAM_WRITABLE));
201
202         g_object_class_install_property(gobject_class,
203                                         PROP_ZOOM_QUALITY,
204                                         g_param_spec_uint("zoom_quality",
205                                                           "Zoom quality",
206                                                           NULL,
207                                                           GDK_INTERP_NEAREST,
208                                                           GDK_INTERP_HYPER,
209                                                           GDK_INTERP_BILINEAR,
210                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
211
212         g_object_class_install_property(gobject_class,
213                                         PROP_ZOOM_2PASS,
214                                         g_param_spec_boolean("zoom_2pass",
215                                                              "2 pass zoom",
216                                                              NULL,
217                                                              TRUE,
218                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
219
220         g_object_class_install_property(gobject_class,
221                                         PROP_ZOOM_EXPAND,
222                                         g_param_spec_boolean("zoom_expand",
223                                                              "Expand image in autozoom.",
224                                                              NULL,
225                                                              FALSE,
226                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
227         g_object_class_install_property(gobject_class,
228                                         PROP_SCROLL_RESET,
229                                         g_param_spec_uint("scroll_reset",
230                                                           "New image scroll reset",
231                                                           NULL,
232                                                           PR_SCROLL_RESET_TOPLEFT,
233                                                           PR_SCROLL_RESET_NOCHANGE,
234                                                           PR_SCROLL_RESET_TOPLEFT,
235                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
236
237         g_object_class_install_property(gobject_class,
238                                         PROP_DELAY_FLIP,
239                                         g_param_spec_boolean("delay_flip",
240                                                              "Delay image update",
241                                                              NULL,
242                                                              FALSE,
243                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
244
245         g_object_class_install_property(gobject_class,
246                                         PROP_LOADING,
247                                         g_param_spec_boolean("loading",
248                                                              "Image actively loading",
249                                                              NULL,
250                                                              FALSE,
251                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
252
253         g_object_class_install_property(gobject_class,
254                                         PROP_COMPLETE,
255                                         g_param_spec_boolean("complete",
256                                                              "Image rendering complete",
257                                                              NULL,
258                                                              FALSE,
259                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
260
261         g_object_class_install_property(gobject_class,
262                                         PROP_CACHE_SIZE_DISPLAY,
263                                         g_param_spec_uint("cache_display",
264                                                           "Display cache size MB",
265                                                           NULL,
266                                                           0,
267                                                           128,
268                                                           PR_CACHE_SIZE_DEFAULT,
269                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
270
271         g_object_class_install_property(gobject_class,
272                                         PROP_CACHE_SIZE_TILES,
273                                         g_param_spec_uint("cache_tiles",
274                                                           "Tile cache count",
275                                                           "Number of tiles to retain in memory at any one time.",
276                                                           0,
277                                                           256,
278                                                           PR_CACHE_SIZE_DEFAULT,
279                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
280
281         g_object_class_install_property(gobject_class,
282                                         PROP_WINDOW_FIT,
283                                         g_param_spec_boolean("window_fit",
284                                                              "Fit window to image size",
285                                                              NULL,
286                                                              FALSE,
287                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
288
289         g_object_class_install_property(gobject_class,
290                                         PROP_WINDOW_LIMIT,
291                                         g_param_spec_boolean("window_limit",
292                                                              "Limit size of parent window",
293                                                              NULL,
294                                                              FALSE,
295                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
296
297         g_object_class_install_property(gobject_class,
298                                         PROP_WINDOW_LIMIT_VALUE,
299                                         g_param_spec_uint("window_limit_value",
300                                                           "Size limit of parent window",
301                                                           NULL,
302                                                           10,
303                                                           150,
304                                                           100,
305                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
306
307         g_object_class_install_property(gobject_class,
308                                         PROP_AUTOFIT_LIMIT,
309                                         g_param_spec_boolean("autofit_limit",
310                                                              "Limit size of image when autofitting",
311                                                              NULL,
312                                                              FALSE,
313                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
314
315         g_object_class_install_property(gobject_class,
316                                         PROP_AUTOFIT_LIMIT_VALUE,
317                                         g_param_spec_uint("autofit_limit_value",
318                                                           "Size limit of image when autofitting",
319                                                           NULL,
320                                                           10,
321                                                           150,
322                                                           100,
323                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
324
325
326         signals[SIGNAL_ZOOM] =
327                 g_signal_new("zoom",
328                              G_OBJECT_CLASS_TYPE(gobject_class),
329                              G_SIGNAL_RUN_LAST,
330                              G_STRUCT_OFFSET(PixbufRendererClass, zoom),
331                              NULL, NULL,
332                              g_cclosure_marshal_VOID__DOUBLE,
333                              G_TYPE_NONE, 1,
334                              G_TYPE_DOUBLE);
335
336         signals[SIGNAL_CLICKED] =
337                 g_signal_new("clicked",
338                              G_OBJECT_CLASS_TYPE(gobject_class),
339                              G_SIGNAL_RUN_LAST,
340                              G_STRUCT_OFFSET(PixbufRendererClass, clicked),
341                              NULL, NULL,
342                              g_cclosure_marshal_VOID__BOXED,
343                              G_TYPE_NONE, 1,
344                              GDK_TYPE_EVENT);
345
346         signals[SIGNAL_SCROLL_NOTIFY] =
347                 g_signal_new("scroll-notify",
348                              G_OBJECT_CLASS_TYPE(gobject_class),
349                              G_SIGNAL_RUN_LAST,
350                              G_STRUCT_OFFSET(PixbufRendererClass, scroll_notify),
351                              NULL, NULL,
352                              g_cclosure_marshal_VOID__VOID,
353                              G_TYPE_NONE, 0);
354
355         signals[SIGNAL_RENDER_COMPLETE] =
356                 g_signal_new("render-complete",
357                              G_OBJECT_CLASS_TYPE(gobject_class),
358                              G_SIGNAL_RUN_LAST,
359                              G_STRUCT_OFFSET(PixbufRendererClass, render_complete),
360                              NULL, NULL,
361                              g_cclosure_marshal_VOID__VOID,
362                              G_TYPE_NONE, 0);
363
364         signals[SIGNAL_DRAG] =
365                 g_signal_new("drag",
366                              G_OBJECT_CLASS_TYPE(gobject_class),
367                              G_SIGNAL_RUN_LAST,
368                              G_STRUCT_OFFSET(PixbufRendererClass, drag),
369                              NULL, NULL,
370                              g_cclosure_marshal_VOID__BOXED,
371                              G_TYPE_NONE, 1,
372                              GDK_TYPE_EVENT);
373                              
374         signals[SIGNAL_UPDATE_PIXEL] =
375                 g_signal_new("update-pixel",
376                              G_OBJECT_CLASS_TYPE(gobject_class),
377                              G_SIGNAL_RUN_LAST,
378                              G_STRUCT_OFFSET(PixbufRendererClass, update_pixel),
379                              NULL, NULL,
380                              g_cclosure_marshal_VOID__VOID,
381                              G_TYPE_NONE, 0);
382 }
383
384 static void pixbuf_renderer_init(PixbufRenderer *pr)
385 {
386         GtkWidget *box;
387
388         box = GTK_WIDGET(pr);
389
390         pr->zoom_min = PR_ZOOM_MIN;
391         pr->zoom_max = PR_ZOOM_MAX;
392         pr->zoom_quality = GDK_INTERP_BILINEAR;
393         pr->zoom_2pass = FALSE;
394
395         pr->zoom = 1.0;
396         pr->scale = 1.0;
397         pr->aspect_ratio = 1.0;
398
399         pr->scroll_reset = PR_SCROLL_RESET_TOPLEFT;
400
401         pr->scroller_id = 0;
402         pr->scroller_overlay = -1;
403         
404         pr->x_mouse = -1;
405         pr->y_mouse = -1;
406
407         pr->source_tiles_enabled = FALSE;
408         pr->source_tiles = NULL;
409
410         pr->orientation = 1;
411
412         pr->norm_center_x = 0.5;
413         pr->norm_center_y = 0.5;
414         
415         pr->stereo_mode = PR_STEREO_NONE;
416         
417         pr->renderer = RENDERER_NEW(pr);
418         
419         pr->renderer2 = NULL;
420
421         gtk_widget_set_double_buffered(box, FALSE);
422         g_signal_connect_after(G_OBJECT(box), "size_allocate",
423                                G_CALLBACK(pr_size_cb), pr);
424
425         pr_signals_connect(pr);
426 }
427
428 static void pixbuf_renderer_finalize(GObject *object)
429 {
430         PixbufRenderer *pr;
431
432         pr = PIXBUF_RENDERER(object);
433
434         pr->renderer->free(pr->renderer);
435         if (pr->renderer2) pr->renderer2->free(pr->renderer2);
436
437
438         if (pr->pixbuf) g_object_unref(pr->pixbuf);
439
440         pr_scroller_timer_set(pr, FALSE);
441
442         pr_source_tile_free_all(pr);
443 }
444
445 PixbufRenderer *pixbuf_renderer_new(void)
446 {
447         return g_object_new(TYPE_PIXBUF_RENDERER, NULL);
448 }
449
450 static void pixbuf_renderer_set_property(GObject *object, guint prop_id,
451                                          const GValue *value, GParamSpec *pspec)
452 {
453         PixbufRenderer *pr;
454
455         pr = PIXBUF_RENDERER(object);
456
457         switch (prop_id)
458                 {
459                 case PROP_ZOOM_MIN:
460                         pr->zoom_min = g_value_get_double(value);
461                         break;
462                 case PROP_ZOOM_MAX:
463                         pr->zoom_max = g_value_get_double(value);
464                         break;
465                 case PROP_ZOOM_QUALITY:
466                         pr->zoom_quality = g_value_get_uint(value);
467                         break;
468                 case PROP_ZOOM_2PASS:
469                         pr->zoom_2pass = g_value_get_boolean(value);
470                         break;
471                 case PROP_ZOOM_EXPAND:
472                         pr->zoom_expand = g_value_get_boolean(value);
473                         break;
474                 case PROP_SCROLL_RESET:
475                         pr->scroll_reset = g_value_get_uint(value);
476                         break;
477                 case PROP_DELAY_FLIP:
478                         pr->delay_flip = g_value_get_boolean(value);
479                         break;
480                 case PROP_LOADING:
481                         pr->loading = g_value_get_boolean(value);
482                         break;
483                 case PROP_COMPLETE:
484                         pr->complete = g_value_get_boolean(value);
485                         break;
486                 case PROP_CACHE_SIZE_DISPLAY:
487 //                      pr->tile_cache_max = g_value_get_uint(value);
488                         break;
489                 case PROP_CACHE_SIZE_TILES:
490                         pr->source_tiles_cache_size = g_value_get_uint(value);
491                         break;
492                 case PROP_WINDOW_FIT:
493                         pr->window_fit = g_value_get_boolean(value);
494                         break;
495                 case PROP_WINDOW_LIMIT:
496                         pr->window_limit = g_value_get_boolean(value);
497                         break;
498                 case PROP_WINDOW_LIMIT_VALUE:
499                         pr->window_limit_size = g_value_get_uint(value);
500                         break;
501                 case PROP_AUTOFIT_LIMIT:
502                         pr->autofit_limit = g_value_get_boolean(value);
503                         break;
504                 case PROP_AUTOFIT_LIMIT_VALUE:
505                         pr->autofit_limit_size = g_value_get_uint(value);
506                         break;
507                 default:
508                         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
509                         break;
510                 }
511 }
512
513 static void pixbuf_renderer_get_property(GObject *object, guint prop_id,
514                                          GValue *value, GParamSpec *pspec)
515 {
516         PixbufRenderer *pr;
517
518         pr = PIXBUF_RENDERER(object);
519
520         switch (prop_id)
521                 {
522                 case PROP_ZOOM_MIN:
523                         g_value_set_double(value, pr->zoom_min);
524                         break;
525                 case PROP_ZOOM_MAX:
526                         g_value_set_double(value, pr->zoom_max);
527                         break;
528                 case PROP_ZOOM_QUALITY:
529                         g_value_set_uint(value, pr->zoom_quality);
530                         break;
531                 case PROP_ZOOM_2PASS:
532                         g_value_set_boolean(value, pr->zoom_2pass);
533                         break;
534                 case PROP_ZOOM_EXPAND:
535                         g_value_set_boolean(value, pr->zoom_expand);
536                         break;
537                 case PROP_SCROLL_RESET:
538                         g_value_set_uint(value, pr->scroll_reset);
539                         break;
540                 case PROP_DELAY_FLIP:
541                         g_value_set_boolean(value, pr->delay_flip);
542                         break;
543                 case PROP_LOADING:
544                         g_value_set_boolean(value, pr->loading);
545                         break;
546                 case PROP_COMPLETE:
547                         g_value_set_boolean(value, pr->complete);
548                         break;
549                 case PROP_CACHE_SIZE_DISPLAY:
550 //                      g_value_set_uint(value, pr->tile_cache_max);
551                         break;
552                 case PROP_CACHE_SIZE_TILES:
553                         g_value_set_uint(value, pr->source_tiles_cache_size);
554                         break;
555                 case PROP_WINDOW_FIT:
556                         g_value_set_boolean(value, pr->window_fit);
557                         break;
558                 case PROP_WINDOW_LIMIT:
559                         g_value_set_boolean(value, pr->window_limit);
560                         break;
561                 case PROP_WINDOW_LIMIT_VALUE:
562                         g_value_set_uint(value, pr->window_limit_size);
563                         break;
564                 case PROP_AUTOFIT_LIMIT:
565                         g_value_set_boolean(value, pr->autofit_limit);
566                         break;
567                 case PROP_AUTOFIT_LIMIT_VALUE:
568                         g_value_set_uint(value, pr->autofit_limit_size);
569                         break;
570                 default:
571                         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
572                         break;
573                 }
574 }
575
576
577 /*
578  *-------------------------------------------------------------------
579  * misc utilities
580  *-------------------------------------------------------------------
581  */
582
583 static void widget_set_cursor(GtkWidget *widget, gint icon)
584 {
585         GdkCursor *cursor;
586
587         if (!gtk_widget_get_window(widget)) return;
588
589         if (icon == -1)
590                 {
591                 cursor = NULL;
592                 }
593         else
594                 {
595                 cursor = gdk_cursor_new(icon);
596                 }
597
598         gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
599
600         if (cursor) gdk_cursor_unref(cursor);
601 }
602
603 gboolean pr_clip_region(gint x, gint y, gint w, gint h,
604                                gint clip_x, gint clip_y, gint clip_w, gint clip_h,
605                                gint *rx, gint *ry, gint *rw, gint *rh)
606 {
607         if (clip_x + clip_w <= x ||
608             clip_x >= x + w ||
609             clip_y + clip_h <= y ||
610             clip_y >= y + h)
611                 {
612                 return FALSE;
613                 }
614
615         *rx = MAX(x, clip_x);
616         *rw = MIN((x + w), (clip_x + clip_w)) - *rx;
617
618         *ry = MAX(y, clip_y);
619         *rh = MIN((y + h), (clip_y + clip_h)) - *ry;
620
621         return TRUE;
622 }
623
624 static gboolean pr_parent_window_sizable(PixbufRenderer *pr)
625 {
626         GdkWindowState state;
627
628         if (!pr->parent_window) return FALSE;
629         if (!pr->window_fit) return FALSE;
630         if (!gtk_widget_get_window(GTK_WIDGET(pr))) return FALSE;
631
632         if (!gtk_widget_get_window(pr->parent_window)) return FALSE;
633         state = gdk_window_get_state(gtk_widget_get_window(pr->parent_window));
634         if (state & GDK_WINDOW_STATE_MAXIMIZED) return FALSE;
635
636         return TRUE;
637 }
638
639 static gboolean pr_parent_window_resize(PixbufRenderer *pr, gint w, gint h)
640 {
641         GtkWidget *widget;
642         GtkWidget *parent;
643         gint ww, wh;
644         GtkAllocation widget_allocation;
645         GtkAllocation parent_allocation;
646
647         if (!pr_parent_window_sizable(pr)) return FALSE;
648
649         if (pr->window_limit)
650                 {
651                 gint sw = gdk_screen_width() * pr->window_limit_size / 100;
652                 gint sh = gdk_screen_height() * pr->window_limit_size / 100;
653
654                 if (w > sw) w = sw;
655                 if (h > sh) h = sh;
656                 }
657
658         widget = GTK_WIDGET(pr);
659         parent = GTK_WIDGET(pr->parent_window);
660
661         gtk_widget_get_allocation(widget, &widget_allocation);
662         gtk_widget_get_allocation(parent, &parent_allocation);
663
664         w += (parent_allocation.width - widget_allocation.width);
665         h += (parent_allocation.height - widget_allocation.height);
666
667         ww = gdk_window_get_width(gtk_widget_get_window(parent));
668         wh = gdk_window_get_height(gtk_widget_get_window(parent));
669         if (w == ww && h == wh) return FALSE;
670
671         gdk_window_resize(gtk_widget_get_window(parent), w, h);
672
673         return TRUE;
674 }
675
676 void pixbuf_renderer_set_parent(PixbufRenderer *pr, GtkWindow *window)
677 {
678         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
679         g_return_if_fail(window == NULL || GTK_IS_WINDOW(window));
680
681         pr->parent_window = GTK_WIDGET(window);
682 }
683
684 GtkWindow *pixbuf_renderer_get_parent(PixbufRenderer *pr)
685 {
686         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), NULL);
687
688         return GTK_WINDOW(pr->parent_window);
689 }
690
691
692 /*
693  *-------------------------------------------------------------------
694  * overlays
695  *-------------------------------------------------------------------
696  */
697
698
699 gint pixbuf_renderer_overlay_add(PixbufRenderer *pr, GdkPixbuf *pixbuf, gint x, gint y,
700                                  OverlayRendererFlags flags)
701 {
702         /* let's assume both renderers returns the same value */
703         if (pr->renderer2) pr->renderer2->overlay_add(pr->renderer2, pixbuf, x, y, flags);
704         return pr->renderer->overlay_add(pr->renderer, pixbuf, x, y, flags);
705 }
706
707 void pixbuf_renderer_overlay_set(PixbufRenderer *pr, gint id, GdkPixbuf *pixbuf, gint x, gint y)
708 {
709         pr->renderer->overlay_set(pr->renderer, id, pixbuf, x, y);
710         if (pr->renderer2) pr->renderer2->overlay_set(pr->renderer2, id, pixbuf, x, y);
711 }
712
713 gboolean pixbuf_renderer_overlay_get(PixbufRenderer *pr, gint id, GdkPixbuf **pixbuf, gint *x, gint *y)
714 {
715         if (pr->renderer2) pr->renderer2->overlay_get(pr->renderer2, id, pixbuf, x, y);
716         return pr->renderer->overlay_get(pr->renderer, id, pixbuf, x, y);
717 }
718
719 void pixbuf_renderer_overlay_remove(PixbufRenderer *pr, gint id)
720 {
721         pr->renderer->overlay_set(pr->renderer, id, NULL, 0, 0);
722         if (pr->renderer2) pr->renderer2->overlay_set(pr->renderer2, id, NULL, 0, 0);
723 }
724
725 /*
726  *-------------------------------------------------------------------
727  * scroller overlay
728  *-------------------------------------------------------------------
729  */
730
731
732 static gboolean pr_scroller_update_cb(gpointer data)
733 {
734         PixbufRenderer *pr = data;
735         gint x, y;
736         gint xinc, yinc;
737
738         /* this was a simple scroll by difference between scroller and mouse position,
739          * but all this math results in a smoother result and accounts for a dead zone.
740          */
741
742         if (abs(pr->scroller_xpos - pr->scroller_x) < PR_SCROLLER_DEAD_ZONE)
743                 {
744                 x = 0;
745                 }
746         else
747                 {
748                 gint shift = PR_SCROLLER_DEAD_ZONE / 2 * PR_SCROLLER_UPDATES_PER_SEC;
749                 x = (pr->scroller_xpos - pr->scroller_x) / 2 * PR_SCROLLER_UPDATES_PER_SEC;
750                 x += (x > 0) ? -shift : shift;
751                 }
752
753         if (abs(pr->scroller_ypos - pr->scroller_y) < PR_SCROLLER_DEAD_ZONE)
754                 {
755                 y = 0;
756                 }
757         else
758                 {
759                 gint shift = PR_SCROLLER_DEAD_ZONE / 2 * PR_SCROLLER_UPDATES_PER_SEC;
760                 y = (pr->scroller_ypos - pr->scroller_y) / 2 * PR_SCROLLER_UPDATES_PER_SEC;
761                 y += (y > 0) ? -shift : shift;
762                 }
763
764         if (abs(x) < PR_SCROLLER_DEAD_ZONE * PR_SCROLLER_UPDATES_PER_SEC)
765                 {
766                 xinc = x;
767                 }
768         else
769                 {
770                 xinc = pr->scroller_xinc;
771
772                 if (x >= 0)
773                         {
774                         if (xinc < 0) xinc = 0;
775                         if (x < xinc) xinc = x;
776                         if (x > xinc) xinc = MIN(xinc + x / PR_SCROLLER_UPDATES_PER_SEC, x);
777                         }
778                 else
779                         {
780                         if (xinc > 0) xinc = 0;
781                         if (x > xinc) xinc = x;
782                         if (x < xinc) xinc = MAX(xinc + x / PR_SCROLLER_UPDATES_PER_SEC, x);
783                         }
784                 }
785
786         if (abs(y) < PR_SCROLLER_DEAD_ZONE * PR_SCROLLER_UPDATES_PER_SEC)
787                 {
788                 yinc = y;
789                 }
790         else
791                 {
792                 yinc = pr->scroller_yinc;
793
794                 if (y >= 0)
795                         {
796                         if (yinc < 0) yinc = 0;
797                         if (y < yinc) yinc = y;
798                         if (y > yinc) yinc = MIN(yinc + y / PR_SCROLLER_UPDATES_PER_SEC, y);
799                         }
800                 else
801                         {
802                         if (yinc > 0) yinc = 0;
803                         if (y > yinc) yinc = y;
804                         if (y < yinc) yinc = MAX(yinc + y / PR_SCROLLER_UPDATES_PER_SEC, y);
805                         }
806                 }
807
808         pr->scroller_xinc = xinc;
809         pr->scroller_yinc = yinc;
810
811         xinc = xinc / PR_SCROLLER_UPDATES_PER_SEC;
812         yinc = yinc / PR_SCROLLER_UPDATES_PER_SEC;
813
814         pixbuf_renderer_scroll(pr, xinc, yinc);
815
816         return TRUE;
817 }
818
819 static void pr_scroller_timer_set(PixbufRenderer *pr, gboolean start)
820 {
821         if (pr->scroller_id)
822                 {
823                 g_source_remove(pr->scroller_id);
824                 pr->scroller_id = 0;
825                 }
826
827         if (start)
828                 {
829                 pr->scroller_id = g_timeout_add(1000 / PR_SCROLLER_UPDATES_PER_SEC,
830                                                 pr_scroller_update_cb, pr);
831                 }
832 }
833
834 static void pr_scroller_start(PixbufRenderer *pr, gint x, gint y)
835 {
836         if (pr->scroller_overlay == -1)
837                 {
838                 GdkPixbuf *pixbuf;
839                 gint w, h;
840
841 #ifdef GQ_BUILD
842                 pixbuf = pixbuf_inline(PIXBUF_INLINE_SCROLLER);
843 #else
844                 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 32, 32);
845                 gdk_pixbuf_fill(pixbuf, 0x000000ff);
846 #endif
847                 w = gdk_pixbuf_get_width(pixbuf);
848                 h = gdk_pixbuf_get_height(pixbuf);
849
850                 pr->scroller_overlay = pixbuf_renderer_overlay_add(pr, pixbuf, x - w / 2, y - h / 2, OVL_NORMAL);
851                 g_object_unref(pixbuf);
852                 }
853
854         pr->scroller_x = x;
855         pr->scroller_y = y;
856         pr->scroller_xpos = x;
857         pr->scroller_ypos = y;
858
859         pr_scroller_timer_set(pr, TRUE);
860 }
861
862 static void pr_scroller_stop(PixbufRenderer *pr)
863 {
864         if (!pr->scroller_id) return;
865
866         pixbuf_renderer_overlay_remove(pr, pr->scroller_overlay);
867         pr->scroller_overlay = -1;
868
869         pr_scroller_timer_set(pr, FALSE);
870 }
871
872 /*
873  *-------------------------------------------------------------------
874  * borders
875  *-------------------------------------------------------------------
876  */
877
878 void pixbuf_renderer_set_color(PixbufRenderer *pr, GdkColor *color)
879 {
880         GtkStyle *style;
881         GtkWidget *widget;
882
883         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
884
885         widget = GTK_WIDGET(pr);
886
887         if (color) {
888                 GdkColor *slot;
889
890                 style = gtk_style_copy(gtk_widget_get_style(widget));
891                 slot = &style->bg[GTK_STATE_NORMAL];
892
893                 slot->red = color->red;
894                 slot->green = color->green;
895                 slot->blue = color->blue;
896                 }
897         else {
898                 style = gtk_style_copy(gtk_widget_get_default_style());
899         }
900
901         gtk_widget_set_style(widget, style);
902
903         pr->renderer->update_sizes(pr->renderer);
904         if (pr->renderer2) pr->renderer2->update_sizes(pr->renderer2);
905 }
906
907 /*
908  *-------------------------------------------------------------------
909  * source tiles
910  *-------------------------------------------------------------------
911  */
912
913 static void pr_source_tile_free(SourceTile *st)
914 {
915         if (!st) return;
916
917         if (st->pixbuf) g_object_unref(st->pixbuf);
918         g_free(st);
919 }
920
921 static void pr_source_tile_free_all(PixbufRenderer *pr)
922 {
923         GList *work;
924
925         work = pr->source_tiles;
926         while (work)
927                 {
928                 SourceTile *st;
929
930                 st = work->data;
931                 work = work->next;
932
933                 pr_source_tile_free(st);
934                 }
935
936         g_list_free(pr->source_tiles);
937         pr->source_tiles = NULL;
938 }
939
940 static void pr_source_tile_unset(PixbufRenderer *pr)
941 {
942         pr_source_tile_free_all(pr);
943         pr->source_tiles_enabled = FALSE;
944 }
945
946 static gboolean pr_source_tile_visible(PixbufRenderer *pr, SourceTile *st)
947 {
948         gint x1, y1, x2, y2;
949
950         if (!st) return FALSE;
951
952 //      x1 = ROUND_DOWN(pr->x_scroll, pr->tile_width);
953 //      y1 = ROUND_DOWN(pr->y_scroll, pr->tile_height);
954 //      x2 = ROUND_UP(pr->x_scroll + pr->vis_width, pr->tile_width);
955 //      y2 = ROUND_UP(pr->y_scroll + pr->vis_height, pr->tile_height);
956         x1 = pr->x_scroll;
957         y1 = pr->y_scroll;
958         x2 = pr->x_scroll + pr->vis_width;
959         y2 = pr->y_scroll + pr->vis_height;
960
961         return !((gdouble)st->x * pr->scale > (gdouble)x2 ||
962                  (gdouble)(st->x + pr->source_tile_width) * pr->scale < (gdouble)x1 ||
963                  (gdouble)st->y * pr->scale > (gdouble)y2 ||
964                  (gdouble)(st->y + pr->source_tile_height) * pr->scale < (gdouble)y1);
965 }
966
967 static SourceTile *pr_source_tile_new(PixbufRenderer *pr, gint x, gint y)
968 {
969         SourceTile *st = NULL;
970         gint count;
971
972         g_return_val_if_fail(pr->source_tile_width >= 1 && pr->source_tile_height >= 1, NULL);
973
974         if (pr->source_tiles_cache_size < 4) pr->source_tiles_cache_size = 4;
975
976         count = g_list_length(pr->source_tiles);
977         if (count >= pr->source_tiles_cache_size)
978                 {
979                 GList *work;
980
981                 work = g_list_last(pr->source_tiles);
982                 while (work && count >= pr->source_tiles_cache_size)
983                         {
984                         SourceTile *needle;
985
986                         needle = work->data;
987                         work = work->prev;
988
989                         if (!pr_source_tile_visible(pr, needle))
990                                 {
991                                 pr->source_tiles = g_list_remove(pr->source_tiles, needle);
992
993                                 if (pr->func_tile_dispose)
994                                         {
995                                         pr->func_tile_dispose(pr, needle->x, needle->y,
996                                                               pr->source_tile_width, pr->source_tile_height,
997                                                               needle->pixbuf, pr->func_tile_data);
998                                         }
999
1000                                 if (!st)
1001                                         {
1002                                         st = needle;
1003                                         }
1004                                 else
1005                                         {
1006                                         pr_source_tile_free(needle);
1007                                         }
1008
1009                                 count--;
1010                                 }
1011                         }
1012                 }
1013
1014         if (!st)
1015                 {
1016                 st = g_new0(SourceTile, 1);
1017                 st->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
1018                                             pr->source_tile_width, pr->source_tile_height);
1019                 }
1020
1021         st->x = ROUND_DOWN(x, pr->source_tile_width);
1022         st->y = ROUND_DOWN(y, pr->source_tile_height);
1023         st->blank = TRUE;
1024
1025         pr->source_tiles = g_list_prepend(pr->source_tiles, st);
1026
1027         return st;
1028 }
1029
1030 static SourceTile *pr_source_tile_request(PixbufRenderer *pr, gint x, gint y)
1031 {
1032         SourceTile *st;
1033
1034         st = pr_source_tile_new(pr, x, y);
1035         if (!st) return NULL;
1036
1037         if (pr->func_tile_request &&
1038             pr->func_tile_request(pr, st->x, st->y,
1039                                    pr->source_tile_width, pr->source_tile_height, st->pixbuf, pr->func_tile_data))
1040                 {
1041                 st->blank = FALSE;
1042                 }
1043
1044         pr->renderer->invalidate_region(pr->renderer, st->x * pr->scale, st->y * pr->scale,
1045                                   pr->source_tile_width * pr->scale, pr->source_tile_height * pr->scale);
1046         if (pr->renderer2) pr->renderer2->invalidate_region(pr->renderer2, st->x * pr->scale, st->y * pr->scale,
1047                                   pr->source_tile_width * pr->scale, pr->source_tile_height * pr->scale);
1048         return st;
1049 }
1050
1051 static SourceTile *pr_source_tile_find(PixbufRenderer *pr, gint x, gint y)
1052 {
1053         GList *work;
1054
1055         work = pr->source_tiles;
1056         while (work)
1057                 {
1058                 SourceTile *st = work->data;
1059
1060                 if (x >= st->x && x < st->x + pr->source_tile_width &&
1061                     y >= st->y && y < st->y + pr->source_tile_height)
1062                         {
1063                         if (work != pr->source_tiles)
1064                                 {
1065                                 pr->source_tiles = g_list_remove_link(pr->source_tiles, work);
1066                                 pr->source_tiles = g_list_concat(work, pr->source_tiles);
1067                                 }
1068                         return st;
1069                         }
1070
1071                 work = work->next;
1072                 }
1073
1074         return NULL;
1075 }
1076
1077 GList *pr_source_tile_compute_region(PixbufRenderer *pr, gint x, gint y, gint w, gint h, gboolean request)
1078 {
1079         gint x1, y1;
1080         GList *list = NULL;
1081         gint sx, sy;
1082
1083         if (x < 0) x = 0;
1084         if (y < 0) y = 0;
1085         if (w > pr->image_width) w = pr->image_width;
1086         if (h > pr->image_height) h = pr->image_height;
1087
1088         sx = ROUND_DOWN(x, pr->source_tile_width);
1089         sy = ROUND_DOWN(y, pr->source_tile_height);
1090
1091         for (x1 = sx; x1 < x + w; x1+= pr->source_tile_width)
1092                 {
1093                 for (y1 = sy; y1 < y + h; y1 += pr->source_tile_height)
1094                         {
1095                         SourceTile *st;
1096
1097                         st = pr_source_tile_find(pr, x1, y1);
1098                         if (!st && request) st = pr_source_tile_request(pr, x1, y1);
1099
1100                         if (st) list = g_list_prepend(list, st);
1101                         }
1102                 }
1103
1104         return g_list_reverse(list);
1105 }
1106
1107 static void pr_source_tile_changed(PixbufRenderer *pr, gint x, gint y, gint width, gint height)
1108 {
1109         GList *work;
1110
1111         if (width < 1 || height < 1) return;
1112
1113         work = pr->source_tiles;
1114         while (work)
1115                 {
1116                 SourceTile *st;
1117                 gint rx, ry, rw, rh;
1118
1119                 st = work->data;
1120                 work = work->next;
1121
1122                 if (pr_clip_region(st->x, st->y, pr->source_tile_width, pr->source_tile_height,
1123                                    x, y, width, height,
1124                                    &rx, &ry, &rw, &rh))
1125                         {
1126                         GdkPixbuf *pixbuf;
1127
1128                         pixbuf = gdk_pixbuf_new_subpixbuf(st->pixbuf, rx - st->x, ry - st->y, rw, rh);
1129                         if (pr->func_tile_request &&
1130                             pr->func_tile_request(pr, rx, ry, rw, rh, pixbuf, pr->func_tile_data))
1131                                 {
1132                                         pr->renderer->invalidate_region(pr->renderer, rx * pr->scale, ry * pr->scale,
1133                                                               rw * pr->scale, rh * pr->scale);
1134                                         if (pr->renderer2) pr->renderer2->invalidate_region(pr->renderer2, rx * pr->scale, ry * pr->scale,
1135                                                                 rw * pr->scale, rh * pr->scale);
1136                                 }
1137                         g_object_unref(pixbuf);
1138                         }
1139                 }
1140 }
1141
1142 void pixbuf_renderer_set_tiles(PixbufRenderer *pr, gint width, gint height,
1143                                gint tile_width, gint tile_height, gint cache_size,
1144                                PixbufRendererTileRequestFunc func_request,
1145                                PixbufRendererTileDisposeFunc func_dispose,
1146                                gpointer user_data,
1147                                gdouble zoom)
1148 {
1149         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1150         g_return_if_fail(tile_width >= 32 && tile_width >= 32);
1151         g_return_if_fail(width >= 32 && height > 32);
1152         g_return_if_fail(func_request != NULL);
1153
1154         if (pr->pixbuf) g_object_unref(pr->pixbuf);
1155         pr->pixbuf = NULL;
1156
1157         pr_source_tile_unset(pr);
1158
1159         if (cache_size < 4) cache_size = 4;
1160
1161         pr->source_tiles_enabled = TRUE;
1162         pr->source_tiles_cache_size = cache_size;
1163         pr->source_tile_width = tile_width;
1164         pr->source_tile_height = tile_height;
1165
1166         pr->image_width = width;
1167         pr->image_height = height;
1168
1169         pr->func_tile_request = func_request;
1170         pr->func_tile_dispose = func_dispose;
1171         pr->func_tile_data = user_data;
1172
1173         pr_zoom_sync(pr, zoom, PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0);
1174 }
1175
1176 void pixbuf_renderer_set_tiles_size(PixbufRenderer *pr, gint width, gint height)
1177 {
1178         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1179         g_return_if_fail(width >= 32 && height > 32);
1180
1181         if (!pr->source_tiles_enabled) return;
1182         if (pr->image_width == width && pr->image_height == height) return;
1183
1184         pr->image_width = width;
1185         pr->image_height = height;
1186
1187         pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE, 0, 0);
1188 }
1189
1190 gint pixbuf_renderer_get_tiles(PixbufRenderer *pr)
1191 {
1192         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
1193
1194         return pr->source_tiles_enabled;
1195 }
1196
1197 static void pr_zoom_adjust_real(PixbufRenderer *pr, gdouble increment,
1198                                 PrZoomFlags flags, gint x, gint y)
1199 {
1200         gdouble zoom = pr->zoom;
1201
1202         if (increment == 0.0) return;
1203
1204         if (zoom == 0.0)
1205                 {
1206                 if (pr->scale < 1.0)
1207                         {
1208                         zoom = 0.0 - 1.0 / pr->scale;
1209                         }
1210                 else
1211                         {
1212                         zoom = pr->scale;
1213                         }
1214                 }
1215
1216         if (increment < 0.0)
1217                 {
1218                 if (zoom >= 1.0 && zoom + increment < 1.0)
1219                         {
1220                         zoom = zoom + increment - 2.0;
1221                         }
1222                 else
1223                         {
1224                         zoom = zoom + increment;
1225                         }
1226                 }
1227         else
1228                 {
1229                 if (zoom <= -1.0 && zoom + increment > -1.0)
1230                         {
1231                         zoom = zoom + increment + 2.0;
1232                         }
1233                 else
1234                         {
1235                         zoom = zoom + increment;
1236                         }
1237                 }
1238
1239         pr_zoom_sync(pr, zoom, flags, x, y);
1240 }
1241
1242
1243 /*
1244  *-------------------------------------------------------------------
1245  * signal emission
1246  *-------------------------------------------------------------------
1247  */
1248
1249 static void pr_update_signal(PixbufRenderer *pr)
1250 {
1251 #if 0
1252         log_printf("FIXME: send updated signal\n");
1253 #endif
1254         DEBUG_1("%s pixbuf renderer updated - started drawing %p, img: %dx%d", get_exec_time(), pr, pr->image_width, pr->image_height);
1255         pr->debug_updated = TRUE;
1256 }
1257
1258 static void pr_zoom_signal(PixbufRenderer *pr)
1259 {
1260         g_signal_emit(pr, signals[SIGNAL_ZOOM], 0, pr->zoom);
1261 }
1262
1263 static void pr_clicked_signal(PixbufRenderer *pr, GdkEventButton *bevent)
1264 {
1265         g_signal_emit(pr, signals[SIGNAL_CLICKED], 0, bevent);
1266 }
1267
1268 static void pr_scroll_notify_signal(PixbufRenderer *pr)
1269 {
1270         g_signal_emit(pr, signals[SIGNAL_SCROLL_NOTIFY], 0);
1271 }
1272
1273 void pr_render_complete_signal(PixbufRenderer *pr)
1274 {
1275         if (!pr->complete)
1276                 {
1277                 g_signal_emit(pr, signals[SIGNAL_RENDER_COMPLETE], 0);
1278                 g_object_set(G_OBJECT(pr), "complete", TRUE, NULL);
1279                 }
1280         if (pr->debug_updated)
1281                 {
1282                 DEBUG_1("%s pixbuf renderer done %p", get_exec_time(), pr);
1283                 pr->debug_updated = FALSE;
1284                 }
1285 }
1286
1287 static void pr_drag_signal(PixbufRenderer *pr, GdkEventButton *bevent)
1288 {
1289         g_signal_emit(pr, signals[SIGNAL_DRAG], 0, bevent);
1290 }
1291
1292 static void pr_update_pixel_signal(PixbufRenderer *pr)
1293 {
1294         g_signal_emit(pr, signals[SIGNAL_UPDATE_PIXEL], 0);
1295 }
1296
1297 /*
1298  *-------------------------------------------------------------------
1299  * sync and clamp
1300  *-------------------------------------------------------------------
1301  */
1302
1303
1304 void pr_tile_coords_map_orientation(gint orientation,
1305                                      gdouble tile_x, gdouble tile_y, /* coordinates of the tile */
1306                                      gdouble image_w, gdouble image_h,
1307                                      gdouble tile_w, gdouble tile_h,
1308                                      gdouble *res_x, gdouble *res_y)
1309 {
1310         *res_x = tile_x;
1311         *res_y = tile_y;
1312         switch (orientation)
1313                 {
1314                 case EXIF_ORIENTATION_TOP_LEFT:
1315                         /* normal -- nothing to do */
1316                         break;
1317                 case EXIF_ORIENTATION_TOP_RIGHT:
1318                         /* mirrored */
1319                         *res_x = image_w - tile_x - tile_w;
1320                         break;
1321                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
1322                         /* upside down */
1323                         *res_x = image_w - tile_x - tile_w;
1324                         *res_y = image_h - tile_y - tile_h;
1325                         break;
1326                 case EXIF_ORIENTATION_BOTTOM_LEFT:
1327                         /* flipped */
1328                         *res_y = image_h - tile_y - tile_h;
1329                         break;
1330                 case EXIF_ORIENTATION_LEFT_TOP:
1331                         *res_x = tile_y;
1332                         *res_y = tile_x;
1333                         break;
1334                 case EXIF_ORIENTATION_RIGHT_TOP:
1335                         /* rotated -90 (270) */
1336                         *res_x = tile_y;
1337                         *res_y = image_w - tile_x - tile_w;
1338                         break;
1339                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
1340                         *res_x = image_h - tile_y - tile_h;
1341                         *res_y = image_w - tile_x - tile_w;
1342                         break;
1343                 case EXIF_ORIENTATION_LEFT_BOTTOM:
1344                         /* rotated 90 */
1345                         *res_x = image_h - tile_y - tile_h;
1346                         *res_y = tile_x;
1347                         break;
1348                 default:
1349                         /* The other values are out of range */
1350                         break;
1351                 }
1352 //      log_printf("tile coord y:%f, ih:%d, th:%f ry:%f\n", tile_y, image_h, tile_h, *res_x);
1353 }
1354
1355 void pr_tile_region_map_orientation(gint orientation,
1356                                      gint area_x, gint area_y, /* coordinates of the area inside tile */
1357                                      gint tile_w, gint tile_h,
1358                                      gint area_w, gint area_h,
1359                                      gint *res_x, gint *res_y,
1360                                      gint *res_w, gint *res_h)
1361 {
1362         *res_x = area_x;
1363         *res_y = area_y;
1364         *res_w = area_w;
1365         *res_h = area_h;
1366
1367         switch (orientation)
1368                 {
1369                 case EXIF_ORIENTATION_TOP_LEFT:
1370                         /* normal -- nothing to do */
1371                         break;
1372                 case EXIF_ORIENTATION_TOP_RIGHT:
1373                         /* mirrored */
1374                         *res_x = tile_w - area_x - area_w;
1375                         break;
1376                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
1377                         /* upside down */
1378                         *res_x = tile_w - area_x - area_w;
1379                         *res_y = tile_h - area_y - area_h;
1380                         break;
1381                 case EXIF_ORIENTATION_BOTTOM_LEFT:
1382                         /* flipped */
1383                         *res_y = tile_h - area_y - area_h;
1384                         break;
1385                 case EXIF_ORIENTATION_LEFT_TOP:
1386                         *res_x = area_y;
1387                         *res_y = area_x;
1388                         *res_w = area_h;
1389                         *res_h = area_w;
1390                         break;
1391                 case EXIF_ORIENTATION_RIGHT_TOP:
1392                         /* rotated -90 (270) */
1393                         *res_x = area_y;
1394                         *res_y = tile_w - area_x - area_w;
1395                         *res_w = area_h;
1396                         *res_h = area_w;
1397                         break;
1398                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
1399                         *res_x = tile_h - area_y - area_h;
1400                         *res_y = tile_w - area_x - area_w;
1401                         *res_w = area_h;
1402                         *res_h = area_w;
1403                         break;
1404                 case EXIF_ORIENTATION_LEFT_BOTTOM:
1405                         /* rotated 90 */
1406                         *res_x = tile_h - area_y - area_h;
1407                         *res_y = area_x;
1408                         *res_w = area_h;
1409                         *res_h = area_w;
1410                         break;
1411                 default:
1412                         /* The other values are out of range */
1413                         break;
1414                 }
1415 //      log_printf("inside y:%d, th:%d, ah:%d ry:%d\n", area_y, tile_h, area_h, *res_x);
1416 }
1417
1418 void pr_coords_map_orientation_reverse(gint orientation,
1419                                      gint area_x, gint area_y,
1420                                      gint tile_w, gint tile_h,
1421                                      gint area_w, gint area_h,
1422                                      gint *res_x, gint *res_y,
1423                                      gint *res_w, gint *res_h)
1424 {
1425         *res_x = area_x;
1426         *res_y = area_y;
1427         *res_w = area_w;
1428         *res_h = area_h;
1429
1430         switch (orientation)
1431                 {
1432                 case EXIF_ORIENTATION_TOP_LEFT:
1433                         /* normal -- nothing to do */
1434                         break;
1435                 case EXIF_ORIENTATION_TOP_RIGHT:
1436                         /* mirrored */
1437                         *res_x = tile_w - area_x - area_w;
1438                         break;
1439                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
1440                         /* upside down */
1441                         *res_x = tile_w - area_x - area_w;
1442                         *res_y = tile_h - area_y - area_h;
1443                         break;
1444                 case EXIF_ORIENTATION_BOTTOM_LEFT:
1445                         /* flipped */
1446                         *res_y = tile_h - area_y - area_h;
1447                         break;
1448                 case EXIF_ORIENTATION_LEFT_TOP:
1449                         *res_x = area_y;
1450                         *res_y = area_x;
1451                         *res_w = area_h;
1452                         *res_h = area_w;
1453                         break;
1454                 case EXIF_ORIENTATION_RIGHT_TOP:
1455                         /* rotated -90 (270) */
1456                         *res_x = tile_w - area_y - area_h;
1457                         *res_y = area_x;
1458                         *res_w = area_h;
1459                         *res_h = area_w;
1460                         break;
1461                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
1462                         *res_x = tile_w - area_y - area_h;
1463                         *res_y = tile_h - area_x - area_w;
1464                         *res_w = area_h;
1465                         *res_h = area_w;
1466                         break;
1467                 case EXIF_ORIENTATION_LEFT_BOTTOM:
1468                         /* rotated 90 */
1469                         *res_x = area_y;
1470                         *res_y = tile_h - area_x - area_w;
1471                         *res_w = area_h;
1472                         *res_h = area_w;
1473                         break;
1474                 default:
1475                         /* The other values are out of range */
1476                         break;
1477                 }
1478 }
1479
1480
1481
1482 static void pixbuf_renderer_sync_scroll_center(PixbufRenderer *pr)
1483 {
1484         gint src_x, src_y;
1485         if (!pr->width || !pr->height) return;
1486
1487         /* 
1488          * Update norm_center only if the image is bigger than the window.
1489          * With this condition the stored center survives also a temporary display
1490          * of the "broken image" icon.
1491         */
1492
1493         if (pr->width > pr->viewport_width)
1494                 {
1495                 src_x = pr->x_scroll + pr->vis_width / 2;
1496                 pr->norm_center_x = (gdouble)src_x / pr->width;
1497                 }
1498         
1499         if (pr->height > pr->viewport_height)
1500                 {
1501                 src_y = pr->y_scroll + pr->vis_height / 2;
1502                 pr->norm_center_y = (gdouble)src_y / pr->height;
1503                 }
1504 }
1505
1506
1507 static gboolean pr_scroll_clamp(PixbufRenderer *pr)
1508 {
1509         gint old_xs;
1510         gint old_ys;
1511
1512         if (pr->zoom == 0.0)
1513                 {
1514                 pr->x_scroll = 0;
1515                 pr->y_scroll = 0;
1516
1517                 return FALSE;
1518                 }
1519
1520         old_xs = pr->x_scroll;
1521         old_ys = pr->y_scroll;
1522
1523         if (pr->x_offset > 0)
1524                 {
1525                 pr->x_scroll = 0;
1526                 }
1527         else
1528                 {
1529                 pr->x_scroll = CLAMP(pr->x_scroll, 0, pr->width - pr->vis_width);
1530                 }
1531
1532         if (pr->y_offset > 0)
1533                 {
1534                 pr->y_scroll = 0;
1535                 }
1536         else
1537                 {
1538                 pr->y_scroll = CLAMP(pr->y_scroll, 0, pr->height - pr->vis_height);
1539                 }
1540
1541         pixbuf_renderer_sync_scroll_center(pr);
1542
1543         return (old_xs != pr->x_scroll || old_ys != pr->y_scroll);
1544 }
1545
1546 static gboolean pr_size_clamp(PixbufRenderer *pr)
1547 {
1548         gint old_vw, old_vh;
1549
1550         old_vw = pr->vis_width;
1551         old_vh = pr->vis_height;
1552
1553         if (pr->width < pr->viewport_width)
1554                 {
1555                 pr->vis_width = pr->width;
1556                 pr->x_offset = (pr->viewport_width - pr->width) / 2;
1557                 }
1558         else
1559                 {
1560                 pr->vis_width = pr->viewport_width;
1561                 pr->x_offset = 0;
1562                 }
1563
1564         if (pr->height < pr->viewport_height)
1565                 {
1566                 pr->vis_height = pr->height;
1567                 pr->y_offset = (pr->viewport_height - pr->height) / 2;
1568                 }
1569         else
1570                 {
1571                 pr->vis_height = pr->viewport_height;
1572                 pr->y_offset = 0;
1573                 }
1574
1575         pixbuf_renderer_sync_scroll_center(pr);
1576
1577         return (old_vw != pr->vis_width || old_vh != pr->vis_height);
1578 }
1579
1580 static gboolean pr_zoom_clamp(PixbufRenderer *pr, gdouble zoom,
1581                               PrZoomFlags flags, gboolean *redrawn)
1582 {
1583         gint w, h;
1584         gdouble scale;
1585         gboolean invalid;
1586         gboolean force = !!(flags & PR_ZOOM_FORCE);
1587         gboolean new = !!(flags & PR_ZOOM_NEW);
1588         gboolean invalidate = !!(flags & PR_ZOOM_INVALIDATE);
1589         gboolean lazy = !!(flags & PR_ZOOM_LAZY);
1590
1591         zoom = CLAMP(zoom, pr->zoom_min, pr->zoom_max);
1592
1593         if (pr->zoom == zoom && !force) return FALSE;
1594
1595         w = pr->image_width;
1596         h = pr->image_height;
1597
1598         if (zoom == 0.0 && !pr->pixbuf)
1599                 {
1600                 scale = 1.0;
1601                 }
1602         else if (zoom == 0.0)
1603                 {
1604                 gint max_w;
1605                 gint max_h;
1606                 gboolean sizeable;
1607
1608                 sizeable = (new && pr_parent_window_sizable(pr));
1609
1610                 if (sizeable)
1611                         {
1612                         max_w = gdk_screen_width();
1613                         max_h = gdk_screen_height();
1614
1615                         if (pr->window_limit)
1616                                 {
1617                                 max_w = max_w * pr->window_limit_size / 100;
1618                                 max_h = max_h * pr->window_limit_size / 100;
1619                                 }
1620                         }
1621                 else
1622                         {
1623                         max_w = pr->viewport_width;
1624                         max_h = pr->viewport_height;
1625                         }
1626
1627                 if ((pr->zoom_expand && !sizeable) || w > max_w || h > max_h)
1628                         {
1629                         if ((gdouble)max_w / w > (gdouble)max_h / h / pr->aspect_ratio)
1630                                 {
1631                                 scale = (gdouble)max_h / h / pr->aspect_ratio;
1632                                 h = max_h;
1633                                 w = w * scale + 0.5;
1634                                 if (w > max_w) w = max_w;
1635                                 }
1636                         else
1637                                 {
1638                                 scale = (gdouble)max_w / w;
1639                                 w = max_w;
1640                                 h = h * scale * pr->aspect_ratio + 0.5;
1641                                 if (h > max_h) h = max_h;
1642                                 }
1643
1644                         if (pr->autofit_limit)
1645                                 {
1646                                 gdouble factor = (gdouble)pr->autofit_limit_size / 100;
1647                                 w = w * factor + 0.5;
1648                                 h = h * factor + 0.5;
1649                                 scale = scale * factor;
1650                                 }
1651
1652                         if (w < 1) w = 1;
1653                         if (h < 1) h = 1;
1654                         }
1655                 else
1656                         {
1657                         scale = 1.0;
1658                         }
1659                 }
1660         else if (zoom > 0.0) /* zoom orig, in */
1661                 {
1662                 scale = zoom;
1663                 w = w * scale;
1664                 h = h * scale * pr->aspect_ratio;
1665                 }
1666         else /* zoom out */
1667                 {
1668                 scale = 1.0 / (0.0 - zoom);
1669                 w = w * scale;
1670                 h = h * scale * pr->aspect_ratio;
1671                 }
1672
1673         invalid = (pr->width != w || pr->height != h);
1674
1675         pr->zoom = zoom;
1676         pr->width = w;
1677         pr->height = h;
1678         pr->scale = scale;
1679
1680         if (invalidate || invalid)
1681                 {
1682                 pr->renderer->update_zoom(pr->renderer, lazy);
1683                 if (pr->renderer2) pr->renderer2->update_zoom(pr->renderer2, lazy);
1684 //              if (!lazy) pr_redraw(pr, TRUE);
1685                 }
1686         if (redrawn) *redrawn = (invalidate || invalid);
1687
1688         pixbuf_renderer_sync_scroll_center(pr);
1689
1690         return TRUE;
1691 }
1692
1693 static void pr_zoom_sync(PixbufRenderer *pr, gdouble zoom,
1694                          PrZoomFlags flags, gint px, gint py)
1695 {
1696         gdouble old_scale;
1697         gint old_cx, old_cy;
1698         gboolean clamped;
1699         gboolean sized;
1700         gboolean redrawn = FALSE;
1701         gboolean center_point = !!(flags & PR_ZOOM_CENTER);
1702         gboolean force = !!(flags & PR_ZOOM_FORCE);
1703         gboolean new = !!(flags & PR_ZOOM_NEW);
1704         gboolean lazy = !!(flags & PR_ZOOM_LAZY);
1705         PrZoomFlags clamp_flags = flags;
1706         gdouble old_center_x = pr->norm_center_x;
1707         gdouble old_center_y = pr->norm_center_y;
1708         
1709         old_scale = pr->scale;
1710         if (center_point)
1711                 {
1712                 px = CLAMP(px, 0, pr->width);
1713                 py = CLAMP(py, 0, pr->height);
1714                 old_cx = pr->x_scroll + (px - pr->x_offset);
1715                 old_cy = pr->y_scroll + (py - pr->y_offset);
1716                 }
1717         else
1718                 {
1719                 px = py = 0;
1720                 old_cx = pr->x_scroll + pr->vis_width / 2;
1721                 old_cy = pr->y_scroll + pr->vis_height / 2;
1722                 }
1723
1724         if (force) clamp_flags |= PR_ZOOM_INVALIDATE;
1725         if (lazy) clamp_flags |= PR_ZOOM_LAZY;
1726         if (!pr_zoom_clamp(pr, zoom, clamp_flags, &redrawn)) return;
1727
1728         clamped = pr_size_clamp(pr);
1729         sized = pr_parent_window_resize(pr, pr->width, pr->height);
1730
1731         if (force && new)
1732                 {
1733                 switch (pr->scroll_reset)
1734                         {
1735                         case PR_SCROLL_RESET_NOCHANGE:
1736                                 /* maintain old scroll position */
1737                                 pr->x_scroll = ((gdouble)pr->image_width * old_center_x * pr->scale) - pr->vis_width / 2;
1738                                 pr->y_scroll = ((gdouble)pr->image_height * old_center_y * pr->scale * pr->aspect_ratio) - pr->vis_height / 2;
1739                                 break;
1740                         case PR_SCROLL_RESET_CENTER:
1741                                 /* center new image */
1742                                 pr->x_scroll = ((gdouble)pr->image_width / 2.0 * pr->scale) - pr->vis_width / 2;
1743                                 pr->y_scroll = ((gdouble)pr->image_height / 2.0 * pr->scale * pr->aspect_ratio) - pr->vis_height / 2;
1744                                 break;
1745                         case PR_SCROLL_RESET_TOPLEFT:
1746                         default:
1747                                 /* reset to upper left */
1748                                 pr->x_scroll = 0;
1749                                 pr->y_scroll = 0;
1750                                 break;
1751                         }
1752                 }
1753         else
1754                 {
1755                 /* user zoom does not force, so keep visible center point */
1756                 if (center_point)
1757                         {
1758                         pr->x_scroll = old_cx / old_scale * pr->scale - (px - pr->x_offset);
1759                         pr->y_scroll = old_cy / old_scale * pr->scale * pr->aspect_ratio - (py - pr->y_offset);
1760                         }
1761                 else
1762                         {
1763                         pr->x_scroll = old_cx / old_scale * pr->scale - (pr->vis_width / 2);
1764                         pr->y_scroll = old_cy / old_scale * pr->scale * pr->aspect_ratio - (pr->vis_height / 2);
1765                         }
1766                 }
1767
1768         pr_scroll_clamp(pr);
1769
1770 #if 0   
1771         if (lazy)
1772                 {
1773                 pr->renderer->queue_clear(pr->renderer);
1774                 if (pr->renderer2) pr->renderer2->queue_clear(pr->renderer2);
1775                 }
1776         else
1777                 {
1778                 pr_redraw(pr, redrawn);
1779                 }
1780 #endif
1781         pr->renderer->update_zoom(pr->renderer, lazy);
1782         if (pr->renderer2) pr->renderer2->update_zoom(pr->renderer2, lazy);
1783
1784         pr_scroll_notify_signal(pr);
1785         pr_zoom_signal(pr);
1786         pr_update_signal(pr);
1787 }
1788
1789 static void pr_size_sync(PixbufRenderer *pr, gint new_width, gint new_height)
1790 {
1791         gboolean zoom_changed = FALSE;
1792
1793         gint new_viewport_width = new_width;
1794         gint new_viewport_height = new_height;
1795
1796         if (!pr->stereo_temp_disable)
1797                 {
1798                 if (pr->stereo_mode & PR_STEREO_HORIZ)
1799                         {
1800                         new_viewport_width = new_width / 2;
1801                         }
1802                 else if (pr->stereo_mode & PR_STEREO_VERT)
1803                         {
1804                         new_viewport_height = new_height / 2;
1805                         }
1806                 else if (pr->stereo_mode & PR_STEREO_FIXED)
1807                         {
1808                         new_viewport_width = pr->stereo_fixed_width;
1809                         new_viewport_height = pr->stereo_fixed_height;
1810                         }
1811                 }
1812                 
1813         if (pr->window_width == new_width && pr->window_height == new_height &&
1814             pr->viewport_width == new_viewport_width && pr->viewport_height == new_viewport_height) return;
1815
1816         pr->window_width = new_width;
1817         pr->window_height = new_height;
1818         pr->viewport_width = new_viewport_width;
1819         pr->viewport_height = new_viewport_height;
1820
1821         if (pr->zoom == 0.0)
1822                 {
1823                 gdouble old_scale = pr->scale;
1824                 pr_zoom_clamp(pr, 0.0, PR_ZOOM_FORCE, NULL);
1825                 zoom_changed = (old_scale != pr->scale);
1826                 }
1827
1828         pr_size_clamp(pr);
1829         pr_scroll_clamp(pr);
1830
1831         pr->renderer->update_sizes(pr->renderer);
1832         if (pr->renderer2) pr->renderer2->update_sizes(pr->renderer2);
1833
1834         /* ensure scroller remains visible */
1835         if (pr->scroller_overlay != -1)
1836                 {
1837                 gboolean update = FALSE;
1838
1839                 if (pr->scroller_x > new_width)
1840                         {
1841                         pr->scroller_x = new_width;
1842                         pr->scroller_xpos = new_width;
1843                         update = TRUE;
1844                         }
1845                 if (pr->scroller_y > new_height)
1846                         {
1847                         pr->scroller_y = new_height;
1848                         pr->scroller_ypos = new_height;
1849                         update = TRUE;
1850                         }
1851
1852                 if (update)
1853                         {
1854                         GdkPixbuf *pixbuf;
1855
1856                         if (pixbuf_renderer_overlay_get(pr, pr->scroller_overlay, &pixbuf, NULL, NULL))
1857                                 {
1858                                 gint w, h;
1859
1860                                 w = gdk_pixbuf_get_width(pixbuf);
1861                                 h = gdk_pixbuf_get_height(pixbuf);
1862                                 pixbuf_renderer_overlay_set(pr, pr->scroller_overlay, pixbuf,
1863                                                             pr->scroller_x - w / 2, pr->scroller_y - h / 2);
1864                                 }
1865                         }
1866                 }
1867
1868         pr_scroll_notify_signal(pr);
1869         if (zoom_changed) pr_zoom_signal(pr);
1870         pr_update_signal(pr);
1871 }
1872
1873 static void pr_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data)
1874 {
1875         PixbufRenderer *pr = data;
1876
1877         pr_size_sync(pr, allocation->width, allocation->height);
1878 }
1879
1880 /*
1881  *-------------------------------------------------------------------
1882  * scrolling
1883  *-------------------------------------------------------------------
1884  */
1885
1886 void pixbuf_renderer_scroll(PixbufRenderer *pr, gint x, gint y)
1887 {
1888         gint old_x, old_y;
1889         gint x_off, y_off;
1890
1891         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1892
1893         if (!pr->pixbuf && !pr->source_tiles_enabled) return;
1894
1895         old_x = pr->x_scroll;
1896         old_y = pr->y_scroll;
1897
1898         pr->x_scroll += x;
1899         pr->y_scroll += y;
1900
1901         pr_scroll_clamp(pr);
1902         
1903         pixbuf_renderer_sync_scroll_center(pr);
1904         
1905         if (pr->x_scroll == old_x && pr->y_scroll == old_y) return;
1906
1907         pr_scroll_notify_signal(pr);
1908
1909         x_off = pr->x_scroll - old_x;
1910         y_off = pr->y_scroll - old_y;
1911         
1912         pr->renderer->scroll(pr->renderer, x_off, y_off);
1913         if (pr->renderer2) pr->renderer2->scroll(pr->renderer2, x_off, y_off);
1914 }
1915
1916 void pixbuf_renderer_scroll_to_point(PixbufRenderer *pr, gint x, gint y,
1917                                      gdouble x_align, gdouble y_align)
1918 {
1919         gint px, py;
1920         gint ax, ay;
1921
1922         x_align = CLAMP(x_align, 0.0, 1.0);
1923         y_align = CLAMP(y_align, 0.0, 1.0);
1924
1925         ax = (gdouble)pr->vis_width * x_align;
1926         ay = (gdouble)pr->vis_height * y_align;
1927
1928         px = (gdouble)x * pr->scale - (pr->x_scroll + ax);
1929         py = (gdouble)y * pr->scale * pr->aspect_ratio - (pr->y_scroll + ay);
1930
1931         pixbuf_renderer_scroll(pr, px, py);
1932 }
1933
1934 /* get or set coordinates of viewport center in the image, in range 0.0 - 1.0 */
1935
1936 void pixbuf_renderer_get_scroll_center(PixbufRenderer *pr, gdouble *x, gdouble *y)
1937 {
1938         *x = pr->norm_center_x;
1939         *y = pr->norm_center_y;
1940 }
1941
1942 void pixbuf_renderer_set_scroll_center(PixbufRenderer *pr, gdouble x, gdouble y)
1943 {
1944         gdouble dst_x, dst_y;
1945
1946         dst_x = x * pr->width  - pr->vis_width  / 2 - pr->x_scroll + CLAMP(pr->subpixel_x_scroll, -1.0, 1.0);
1947         dst_y = y * pr->height - pr->vis_height / 2 - pr->y_scroll + CLAMP(pr->subpixel_y_scroll, -1.0, 1.0);
1948
1949         pr->subpixel_x_scroll = dst_x - (gint)dst_x;
1950         pr->subpixel_y_scroll = dst_y - (gint)dst_y;
1951
1952         pixbuf_renderer_scroll(pr, (gint)dst_x, (gint)dst_y);
1953 }
1954
1955 /*
1956  *-------------------------------------------------------------------
1957  * mouse
1958  *-------------------------------------------------------------------
1959  */
1960
1961 static gboolean pr_mouse_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1962 {
1963         PixbufRenderer *pr;
1964         gint accel;
1965
1966         /* This is a hack, but work far the best, at least for single pointer systems.
1967          * See http://bugzilla.gnome.org/show_bug.cgi?id=587714 for more. */
1968         gint x, y;
1969         gdk_window_get_pointer (bevent->window, &x, &y, NULL);
1970         bevent->x = x;
1971         bevent->y = y;
1972
1973         pr = PIXBUF_RENDERER(widget);
1974
1975         if (pr->scroller_id)
1976                 {
1977                 pr->scroller_xpos = bevent->x;
1978                 pr->scroller_ypos = bevent->y;
1979                 }
1980         
1981         pr->x_mouse = bevent->x;
1982         pr->y_mouse = bevent->y;
1983         pr_update_pixel_signal(pr);
1984         
1985         if (!pr->in_drag || !gdk_pointer_is_grabbed()) return FALSE;
1986
1987         if (pr->drag_moved < PR_DRAG_SCROLL_THRESHHOLD)
1988                 {
1989                 pr->drag_moved++;
1990                 }
1991         else
1992                 {
1993                 widget_set_cursor(widget, GDK_FLEUR);
1994                 }
1995
1996         if (bevent->state & GDK_CONTROL_MASK)
1997                 {
1998                 accel = PR_PAN_SHIFT_MULTIPLIER;
1999                 }
2000         else
2001                 {
2002                 accel = 1;
2003                 }
2004
2005         /* do the scroll */
2006         pixbuf_renderer_scroll(pr, (pr->drag_last_x - bevent->x) * accel,
2007                                (pr->drag_last_y - bevent->y) * accel);
2008
2009         pr_drag_signal(pr, bevent);
2010
2011         pr->drag_last_x = bevent->x;
2012         pr->drag_last_y = bevent->y;
2013
2014         /* This is recommended by the GTK+ documentation, but does not work properly.
2015          * Use deprecated way until GTK+ gets a solution for correct motion hint handling:
2016          * http://bugzilla.gnome.org/show_bug.cgi?id=587714
2017          */
2018         /* gdk_event_request_motions (bevent); */
2019         return FALSE;
2020 }
2021
2022 static gboolean pr_leave_notify_cb(GtkWidget *widget, GdkEventCrossing *cevent, gpointer data)
2023 {
2024         PixbufRenderer *pr;
2025
2026         pr = PIXBUF_RENDERER(widget);
2027         pr->x_mouse = -1;
2028         pr->y_mouse = -1;
2029
2030         pr_update_pixel_signal(pr);
2031         return FALSE;
2032 }
2033
2034 static gboolean pr_mouse_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
2035 {
2036         PixbufRenderer *pr;
2037         GtkWidget *parent;
2038
2039         pr = PIXBUF_RENDERER(widget);
2040
2041         if (pr->scroller_id) return TRUE;
2042
2043         switch (bevent->button)
2044                 {
2045                 case MOUSE_BUTTON_LEFT:
2046                         pr->in_drag = TRUE;
2047                         pr->drag_last_x = bevent->x;
2048                         pr->drag_last_y = bevent->y;
2049                         pr->drag_moved = 0;
2050                         gdk_pointer_grab(gtk_widget_get_window(widget), FALSE,
2051                                          GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_RELEASE_MASK,
2052                                          NULL, NULL, bevent->time);
2053                         gtk_grab_add(widget);
2054                         break;
2055                 case MOUSE_BUTTON_MIDDLE:
2056                         pr->drag_moved = 0;
2057                         break;
2058                 case MOUSE_BUTTON_RIGHT:
2059                         pr_clicked_signal(pr, bevent);
2060                         break;
2061                 default:
2062                         break;
2063                 }
2064
2065         parent = gtk_widget_get_parent(widget);
2066 #if GTK_CHECK_VERSION(2,20,0)
2067         if (widget && gtk_widget_get_can_focus(parent))
2068 #else
2069         if (widget && GTK_WIDGET_CAN_FOCUS(parent))
2070 #endif
2071                 {
2072                 gtk_widget_grab_focus(parent);
2073                 }
2074
2075         return FALSE;
2076 }
2077
2078 static gboolean pr_mouse_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
2079 {
2080         PixbufRenderer *pr;
2081
2082         pr = PIXBUF_RENDERER(widget);
2083
2084         if (pr->scroller_id)
2085                 {
2086                 pr_scroller_stop(pr);
2087                 return TRUE;
2088                 }
2089
2090 #if GTK_CHECK_VERSION(2,20,0)
2091         if (gdk_pointer_is_grabbed() && gtk_widget_has_grab(GTK_WIDGET(pr)))
2092 #else
2093         if (gdk_pointer_is_grabbed() && GTK_WIDGET_HAS_GRAB(pr))
2094 #endif
2095                 {
2096                 gtk_grab_remove(widget);
2097                 gdk_pointer_ungrab(bevent->time);
2098                 widget_set_cursor(widget, -1);
2099                 }
2100
2101         if (pr->drag_moved < PR_DRAG_SCROLL_THRESHHOLD)
2102                 {
2103                 if (bevent->button == MOUSE_BUTTON_LEFT && (bevent->state & GDK_CONTROL_MASK))
2104                         {
2105                         pr_scroller_start(pr, bevent->x, bevent->y);
2106                         }
2107                 else if (bevent->button == MOUSE_BUTTON_LEFT || bevent->button == MOUSE_BUTTON_MIDDLE)
2108                         {
2109                         pr_clicked_signal(pr, bevent);
2110                         }
2111                 }
2112
2113         pr->in_drag = FALSE;
2114
2115         return FALSE;
2116 }
2117
2118 static gboolean pr_mouse_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
2119 {
2120         PixbufRenderer *pr;
2121
2122         pr = PIXBUF_RENDERER(widget);
2123
2124         if (pr->scroller_id)
2125                 {
2126                 pr->scroller_xpos = pr->scroller_x;
2127                 pr->scroller_ypos = pr->scroller_y;
2128                 pr->scroller_xinc = 0;
2129                 pr->scroller_yinc = 0;
2130                 }
2131
2132         return FALSE;
2133 }
2134
2135 static void pr_mouse_drag_cb(GtkWidget *widget, GdkDragContext *context, gpointer data)
2136 {
2137         PixbufRenderer *pr;
2138
2139         pr = PIXBUF_RENDERER(widget);
2140
2141         pr->drag_moved = PR_DRAG_SCROLL_THRESHHOLD;
2142 }
2143
2144 static void pr_signals_connect(PixbufRenderer *pr)
2145 {
2146         g_signal_connect(G_OBJECT(pr), "motion_notify_event",
2147                          G_CALLBACK(pr_mouse_motion_cb), pr);
2148         g_signal_connect(G_OBJECT(pr), "button_press_event",
2149                          G_CALLBACK(pr_mouse_press_cb), pr);
2150         g_signal_connect(G_OBJECT(pr), "button_release_event",
2151                          G_CALLBACK(pr_mouse_release_cb), pr);
2152         g_signal_connect(G_OBJECT(pr), "leave_notify_event",
2153                          G_CALLBACK(pr_mouse_leave_cb), pr);
2154         g_signal_connect(G_OBJECT(pr), "leave_notify_event",
2155                          G_CALLBACK(pr_leave_notify_cb), pr);
2156
2157         gtk_widget_set_events(GTK_WIDGET(pr), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
2158                                               GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
2159                                               GDK_LEAVE_NOTIFY_MASK);
2160
2161         g_signal_connect(G_OBJECT(pr), "drag_begin",
2162                          G_CALLBACK(pr_mouse_drag_cb), pr);
2163
2164 }
2165
2166 /*
2167  *-------------------------------------------------------------------
2168  * stereo support
2169  *-------------------------------------------------------------------
2170  */
2171
2172 #define COLOR_BYTES 3   /* rgb */
2173 static void pr_create_anaglyph_RC(GdkPixbuf *pixbuf, GdkPixbuf *right, gint x, gint y, gint w, gint h)
2174 {
2175         gint srs, drs;
2176         guchar *s_pix, *d_pix;
2177         guchar *sp, *dp;
2178         guchar *spi, *dpi;
2179         gint i, j;
2180
2181         srs = gdk_pixbuf_get_rowstride(right);
2182         s_pix = gdk_pixbuf_get_pixels(right);
2183         spi = s_pix + (x * COLOR_BYTES);
2184
2185         drs = gdk_pixbuf_get_rowstride(pixbuf);
2186         d_pix = gdk_pixbuf_get_pixels(pixbuf);
2187         dpi =  d_pix + x * COLOR_BYTES;
2188
2189         for (i = y; i < y + h; i++)
2190                 {
2191                 sp = spi + (i * srs);
2192                 dp = dpi + (i * drs);
2193                 for (j = 0; j < w; j++)
2194                         {
2195                         *dp = *sp; /* copy red channel */
2196                         sp += COLOR_BYTES;
2197                         dp += COLOR_BYTES;
2198                         }
2199                 }
2200 }
2201
2202 static void pr_create_anaglyph_gray(GdkPixbuf *pixbuf, GdkPixbuf *right, gint x, gint y, gint w, gint h)
2203 {
2204         gint srs, drs;
2205         guchar *s_pix, *d_pix;
2206         guchar *sp, *dp;
2207         guchar *spi, *dpi;
2208         gint i, j;
2209         const double gc[3] = {0.299, 0.587, 0.114};
2210
2211         srs = gdk_pixbuf_get_rowstride(right);
2212         s_pix = gdk_pixbuf_get_pixels(right);
2213         spi = s_pix + (x * COLOR_BYTES);
2214
2215         drs = gdk_pixbuf_get_rowstride(pixbuf);
2216         d_pix = gdk_pixbuf_get_pixels(pixbuf);
2217         dpi =  d_pix + x * COLOR_BYTES;
2218
2219         for (i = y; i < y + h; i++)
2220                 {
2221                 sp = spi + (i * srs);
2222                 dp = dpi + (i * drs);
2223                 for (j = 0; j < w; j++)
2224                         {
2225                         guchar g1 = dp[0] * gc[0] + dp[1] * gc[1] + dp[2] * gc[2];
2226                         guchar g2 = sp[0] * gc[0] + sp[1] * gc[1] + sp[2] * gc[2];
2227                         dp[0] = g2; /* red channel from sp */
2228                         dp[1] = g1; /* green and blue from dp */
2229                         dp[2] = g1;
2230                         sp += COLOR_BYTES;
2231                         dp += COLOR_BYTES;
2232                         }
2233                 }
2234 }
2235
2236 const double pr_dubois_matrix[3][6] = {
2237         { 0.456,  0.500,  0.176, -0.043, -0.088, -0.002},
2238         {-0.040, -0.038, -0.016,  0.378,  0.734, -0.018},
2239         {-0.015, -0.021, -0.005, -0.072, -0.113,  1.226}
2240         }; 
2241
2242 static void pr_create_anaglyph_dubois(GdkPixbuf *pixbuf, GdkPixbuf *right, gint x, gint y, gint w, gint h)
2243 {
2244         gint srs, drs;
2245         guchar *s_pix, *d_pix;
2246         guchar *sp, *dp;
2247         guchar *spi, *dpi;
2248         gint i, j, k;
2249
2250         srs = gdk_pixbuf_get_rowstride(right);
2251         s_pix = gdk_pixbuf_get_pixels(right);
2252         spi = s_pix + (x * COLOR_BYTES);
2253
2254         drs = gdk_pixbuf_get_rowstride(pixbuf);
2255         d_pix = gdk_pixbuf_get_pixels(pixbuf);
2256         dpi =  d_pix + x * COLOR_BYTES;
2257
2258         for (i = y; i < y + h; i++)
2259                 {
2260                 sp = spi + (i * srs);
2261                 dp = dpi + (i * drs);
2262                 for (j = 0; j < w; j++)
2263                         {
2264                         double res[3];
2265                         for (k = 0; k < 3; k++) 
2266                                 {
2267                                 const double *m = pr_dubois_matrix[k];
2268                                 res[k] = sp[0] * m[0] + sp[1] * m[1] + sp[2] * m[2] + dp[0] * m[3] + dp[1] * m[4] + dp[2] * m[5];
2269                                 if (res[k] < 0.0) res[k] = 0;
2270                                 if (res[k] > 255.0) res[k] = 255.0;
2271                                 }
2272                         dp[0] = res[0];
2273                         dp[1] = res[1];
2274                         dp[2] = res[2];
2275                         sp += COLOR_BYTES;
2276                         dp += COLOR_BYTES;
2277                         }
2278                 }
2279 }
2280  
2281 void pr_create_anaglyph(guint mode, GdkPixbuf *pixbuf, GdkPixbuf *right, gint x, gint y, gint w, gint h)
2282 {
2283         if (mode & PR_STEREO_ANAGLYPH_RC)
2284                 pr_create_anaglyph_RC(pixbuf, right, x, y, w, h);
2285         else if (mode & PR_STEREO_ANAGLYPH_GRAY)
2286                 pr_create_anaglyph_gray(pixbuf, right, x, y, w, h);
2287         else if (mode & PR_STEREO_ANAGLYPH_DB)
2288                 pr_create_anaglyph_dubois(pixbuf, right, x, y, w, h);
2289 }
2290
2291 /*
2292  *-------------------------------------------------------------------
2293  * public
2294  *-------------------------------------------------------------------
2295  */
2296 static void pr_pixbuf_size_sync(PixbufRenderer *pr)
2297 {
2298         pr->stereo_pixbuf_offset_left = 0;
2299         pr->stereo_pixbuf_offset_right = 0;
2300         if (!pr->pixbuf) return;
2301         switch (pr->orientation)
2302                 {
2303                 case EXIF_ORIENTATION_LEFT_TOP:
2304                 case EXIF_ORIENTATION_RIGHT_TOP:
2305                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
2306                 case EXIF_ORIENTATION_LEFT_BOTTOM:
2307                         pr->image_width = gdk_pixbuf_get_height(pr->pixbuf);
2308                         pr->image_height = gdk_pixbuf_get_width(pr->pixbuf);
2309                         if (pr->stereo_data == STEREO_PIXBUF_SBS) 
2310                                 {
2311                                 pr->image_height /= 2;
2312                                 pr->stereo_pixbuf_offset_right = pr->image_height;
2313                                 }
2314                         else if (pr->stereo_data == STEREO_PIXBUF_CROSS) 
2315                                 {
2316                                 pr->image_height /= 2;
2317                                 pr->stereo_pixbuf_offset_left = pr->image_height;
2318                                 }
2319                         
2320                         break;
2321                 default:
2322                         pr->image_width = gdk_pixbuf_get_width(pr->pixbuf);
2323                         pr->image_height = gdk_pixbuf_get_height(pr->pixbuf);
2324                         if (pr->stereo_data == STEREO_PIXBUF_SBS) 
2325                                 {
2326                                 pr->image_width /= 2;
2327                                 pr->stereo_pixbuf_offset_right = pr->image_width;
2328                                 }
2329                         else if (pr->stereo_data == STEREO_PIXBUF_CROSS) 
2330                                 {
2331                                 pr->image_width /= 2;
2332                                 pr->stereo_pixbuf_offset_left = pr->image_width;
2333                                 }
2334                 }
2335 }
2336
2337 static void pr_set_pixbuf(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom, PrZoomFlags flags)
2338 {
2339         if (pixbuf) g_object_ref(pixbuf);
2340         if (pr->pixbuf) g_object_unref(pr->pixbuf);
2341         pr->pixbuf = pixbuf;
2342
2343         if (!pr->pixbuf)
2344                 {
2345                 GtkWidget *box;
2346
2347                 /* no pixbuf so just clear the window */
2348                 pr->image_width = 0;
2349                 pr->image_height = 0;
2350                 pr->scale = 1.0;
2351                 pr->zoom = zoom; /* don't throw away the zoom value, it is set by pixbuf_renderer_move, among others,
2352                                     and used for pixbuf_renderer_zoom_get */
2353
2354                 box = GTK_WIDGET(pr);
2355
2356 #if GTK_CHECK_VERSION(2,20,0)
2357                 if (gtk_widget_get_realized(box))
2358 #else
2359                 if (GTK_WIDGET_REALIZED(box))
2360 #endif
2361                         {
2362 #if !GTK_CHECK_VERSION(3,0,0)
2363                         gdk_window_clear(gtk_widget_get_window(box));
2364 #endif
2365                         pr->renderer->overlay_draw(pr->renderer, 0, 0, pr->viewport_width, pr->viewport_height);
2366                         if (pr->renderer2) pr->renderer2->overlay_draw(pr->renderer2, 0, 0, pr->viewport_width, pr->viewport_height);
2367                         }
2368
2369                 pr_update_signal(pr);
2370
2371                 return;
2372                 }
2373
2374         if (pr->stereo_mode & PR_STEREO_TEMP_DISABLE) 
2375                 {
2376                 gint disable = !pr->pixbuf || ! pr->stereo_data;
2377                 pr_stereo_temp_disable(pr, disable);
2378                 }
2379
2380         pr_pixbuf_size_sync(pr);
2381         pr->renderer->update_pixbuf(pr->renderer, flags & PR_ZOOM_LAZY);
2382         if (pr->renderer2) pr->renderer2->update_pixbuf(pr->renderer2, flags & PR_ZOOM_LAZY);
2383         pr_zoom_sync(pr, zoom, flags | PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0);
2384 }
2385
2386 void pixbuf_renderer_set_pixbuf(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom)
2387 {
2388         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2389
2390         pr_source_tile_unset(pr);
2391
2392         pr_set_pixbuf(pr, pixbuf, zoom, 0);
2393
2394         pr_update_signal(pr);
2395 }
2396
2397 void pixbuf_renderer_set_pixbuf_lazy(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom, gint orientation, StereoPixbufData stereo_data)
2398 {
2399         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2400
2401         pr_source_tile_unset(pr);
2402
2403         pr->orientation = orientation;
2404         pr->stereo_data = stereo_data;
2405         pr_set_pixbuf(pr, pixbuf, zoom, PR_ZOOM_LAZY);
2406
2407         pr_update_signal(pr);
2408 }
2409
2410 GdkPixbuf *pixbuf_renderer_get_pixbuf(PixbufRenderer *pr)
2411 {
2412         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), NULL);
2413
2414         return pr->pixbuf;
2415 }
2416
2417 void pixbuf_renderer_set_orientation(PixbufRenderer *pr, gint orientation)
2418 {
2419         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2420
2421         pr->orientation = orientation;
2422
2423         pr_pixbuf_size_sync(pr);
2424         if (0)
2425                 {
2426                 pr->renderer->update_pixbuf(pr->renderer, FALSE);
2427                 if (pr->renderer2) pr->renderer2->update_pixbuf(pr->renderer2, FALSE);
2428                 }
2429         pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE, 0, 0);
2430
2431         pr->renderer->update_sizes(pr->renderer);
2432         if (pr->renderer2) pr->renderer2->update_sizes(pr->renderer2);
2433 }
2434
2435 gint pixbuf_renderer_get_orientation(PixbufRenderer *pr)
2436 {
2437         if (!pr) return 1;
2438         return pr->orientation;
2439 }
2440
2441 void pixbuf_renderer_set_stereo_data(PixbufRenderer *pr, StereoPixbufData stereo_data)
2442 {
2443         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2444
2445         pr->stereo_data = stereo_data;
2446
2447         if (pr->stereo_mode & PR_STEREO_TEMP_DISABLE) 
2448                 {
2449                 gint disable = !pr->pixbuf || ! pr->stereo_data;
2450                 pr_stereo_temp_disable(pr, disable);
2451                 }
2452         pr_pixbuf_size_sync(pr);
2453         pr->renderer->update_pixbuf(pr->renderer, FALSE);
2454         if (pr->renderer2) pr->renderer2->update_pixbuf(pr->renderer2, FALSE);
2455         pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE, 0, 0);
2456 }
2457
2458 void pixbuf_renderer_set_post_process_func(PixbufRenderer *pr, PixbufRendererPostProcessFunc func, gpointer user_data, gboolean slow)
2459 {
2460         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2461
2462         pr->func_post_process = func;
2463         pr->post_process_user_data = user_data;
2464         pr->post_process_slow = func && slow;
2465
2466 }
2467
2468
2469 void pixbuf_renderer_move(PixbufRenderer *pr, PixbufRenderer *source)
2470 {
2471         GObject *object;
2472         PixbufRendererScrollResetType scroll_reset;
2473
2474         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2475         g_return_if_fail(IS_PIXBUF_RENDERER(source));
2476
2477         if (pr == source) return;
2478
2479         object = G_OBJECT(pr);
2480
2481         g_object_set(object, "zoom_min", source->zoom_min, NULL);
2482         g_object_set(object, "zoom_max", source->zoom_max, NULL);
2483         g_object_set(object, "loading", source->loading, NULL);
2484
2485         pr->complete = source->complete;
2486
2487         pr->x_scroll = source->x_scroll;
2488         pr->y_scroll = source->y_scroll;
2489         pr->x_mouse  = source->x_mouse;
2490         pr->y_mouse  = source->y_mouse;
2491
2492         scroll_reset = pr->scroll_reset;
2493         pr->scroll_reset = PR_SCROLL_RESET_NOCHANGE;
2494
2495         pr->func_post_process = source->func_post_process;
2496         pr->post_process_user_data = source->post_process_user_data;
2497         pr->post_process_slow = source->post_process_slow;
2498         pr->orientation = source->orientation;
2499         pr->stereo_data = source->stereo_data;
2500
2501         if (source->source_tiles_enabled)
2502                 {
2503                 pr_source_tile_unset(pr);
2504
2505                 pr->source_tiles_enabled = source->source_tiles_enabled;
2506                 pr->source_tiles_cache_size = source->source_tiles_cache_size;
2507                 pr->source_tile_width = source->source_tile_width;
2508                 pr->source_tile_height = source->source_tile_height;
2509                 pr->image_width = source->image_width;
2510                 pr->image_height = source->image_height;
2511
2512                 pr->func_tile_request = source->func_tile_request;
2513                 pr->func_tile_dispose = source->func_tile_dispose;
2514                 pr->func_tile_data = source->func_tile_data;
2515
2516                 pr->source_tiles = source->source_tiles;
2517                 source->source_tiles = NULL;
2518
2519                 pr_zoom_sync(pr, source->zoom, PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0);
2520                 }
2521         else
2522                 {
2523                 pixbuf_renderer_set_pixbuf(pr, source->pixbuf, source->zoom);
2524                 }
2525
2526         pr->scroll_reset = scroll_reset;
2527
2528         pixbuf_renderer_set_pixbuf(source, NULL, source->zoom);
2529 //      pr_queue_clear(source);
2530 //      pr_tile_free_all(source);
2531 }
2532
2533 void pixbuf_renderer_area_changed(PixbufRenderer *pr, gint x, gint y, gint w, gint h)
2534 {
2535         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2536
2537         if (pr->source_tiles_enabled)
2538                 {
2539                 pr_source_tile_changed(pr, x, y, w, h);
2540                 }
2541
2542         pr->renderer->area_changed(pr->renderer, x, y, w, h);
2543         if (pr->renderer2) pr->renderer2->area_changed(pr->renderer2, x, y, w, h);
2544 }
2545
2546 void pixbuf_renderer_zoom_adjust(PixbufRenderer *pr, gdouble increment)
2547 {
2548         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2549
2550         pr_zoom_adjust_real(pr, increment, PR_ZOOM_NONE, 0, 0);
2551 }
2552
2553 void pixbuf_renderer_zoom_adjust_at_point(PixbufRenderer *pr, gdouble increment, gint x, gint y)
2554 {
2555         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2556
2557         pr_zoom_adjust_real(pr, increment, PR_ZOOM_CENTER, x, y);
2558 }
2559
2560 void pixbuf_renderer_zoom_set(PixbufRenderer *pr, gdouble zoom)
2561 {
2562         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2563
2564         pr_zoom_sync(pr, zoom, PR_ZOOM_NONE, 0, 0);
2565 }
2566
2567 gdouble pixbuf_renderer_zoom_get(PixbufRenderer *pr)
2568 {
2569         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), 1.0);
2570
2571         return pr->zoom;
2572 }
2573
2574 gdouble pixbuf_renderer_zoom_get_scale(PixbufRenderer *pr)
2575 {
2576         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), 1.0);
2577
2578         return pr->scale;
2579 }
2580
2581 void pixbuf_renderer_zoom_set_limits(PixbufRenderer *pr, gdouble min, gdouble max)
2582 {
2583         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2584
2585         if (min > 1.0 || max < 1.0) return;
2586         if (min < 1.0 && min > -1.0) return;
2587         if (min < -200.0 || max > 200.0) return;
2588
2589         if (pr->zoom_min != min)
2590                 {
2591                 pr->zoom_min = min;
2592                 g_object_notify(G_OBJECT(pr), "zoom_min");
2593                 }
2594         if (pr->zoom_max != max)
2595                 {
2596                 pr->zoom_max = max;
2597                 g_object_notify(G_OBJECT(pr), "zoom_max");
2598                 }
2599 }
2600
2601 static void pr_stereo_set(PixbufRenderer *pr)
2602 {
2603         if (!pr->renderer) pr->renderer = RENDERER_NEW(pr);
2604         
2605         pr->renderer->stereo_set(pr->renderer, pr->stereo_mode & ~PR_STEREO_MIRROR_RIGHT & ~PR_STEREO_FLIP_RIGHT);
2606         
2607         if (pr->stereo_mode & (PR_STEREO_HORIZ | PR_STEREO_VERT | PR_STEREO_FIXED))
2608                 {
2609                 if (!pr->renderer2) pr->renderer2 = RENDERER_NEW(pr);
2610                 pr->renderer2->stereo_set(pr->renderer2, (pr->stereo_mode & ~PR_STEREO_MIRROR_LEFT & ~PR_STEREO_FLIP_LEFT) | PR_STEREO_RIGHT);
2611                 }
2612         else
2613                 {
2614                 if (pr->renderer2) pr->renderer2->free(pr->renderer2);
2615                 pr->renderer2 = NULL;
2616                 }
2617         if (pr->stereo_mode & PR_STEREO_HALF)
2618                 {
2619                 if (pr->stereo_mode & PR_STEREO_HORIZ) pr->aspect_ratio = 2.0;
2620                 else if (pr->stereo_mode & PR_STEREO_VERT) pr->aspect_ratio = 0.5;
2621                 else pr->aspect_ratio = 1.0;
2622                 }
2623         else
2624                 {
2625                 pr->aspect_ratio = 1.0;
2626                 }
2627 }
2628
2629 void pixbuf_renderer_stereo_set(PixbufRenderer *pr, gint stereo_mode)
2630 {
2631         gboolean redraw = !(pr->stereo_mode == stereo_mode) || pr->stereo_temp_disable;
2632         pr->stereo_mode = stereo_mode;
2633         if ((stereo_mode & PR_STEREO_TEMP_DISABLE) && pr->stereo_temp_disable) return;
2634         
2635         pr->stereo_temp_disable = FALSE;
2636         
2637         pr_stereo_set(pr);
2638         
2639         if (redraw) 
2640                 {
2641                 pr_size_sync(pr, pr->window_width, pr->window_height); /* recalculate new viewport */
2642                 pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0);
2643                 }
2644 }
2645
2646 void pixbuf_renderer_stereo_fixed_set(PixbufRenderer *pr, gint width, gint height, gint x1, gint y1, gint x2, gint y2)
2647 {
2648         pr->stereo_fixed_width = width;
2649         pr->stereo_fixed_height = height;
2650         pr->stereo_fixed_x_left = x1;
2651         pr->stereo_fixed_y_left = y1;
2652         pr->stereo_fixed_x_right = x2;
2653         pr->stereo_fixed_y_right = y2;
2654 }
2655
2656 gint pixbuf_renderer_stereo_get(PixbufRenderer *pr)
2657 {
2658         return pr->stereo_mode;
2659 }
2660
2661 static void pr_stereo_temp_disable(PixbufRenderer *pr, gboolean disable)
2662 {
2663         if (pr->stereo_temp_disable == disable) return;
2664         pr->stereo_temp_disable = disable;
2665         if (disable)
2666                 {
2667                 if (!pr->renderer) pr->renderer = RENDERER_NEW(pr);
2668                 pr->renderer->stereo_set(pr->renderer, PR_STEREO_NONE);
2669                 if (pr->renderer2) pr->renderer2->free(pr->renderer2);
2670                 pr->renderer2 = NULL;
2671                 pr->aspect_ratio = 1.0;
2672                 }
2673         else
2674                 {
2675                 pr_stereo_set(pr);
2676                 }
2677         pr_size_sync(pr, pr->window_width, pr->window_height); /* recalculate new viewport */
2678 }
2679
2680 gboolean pixbuf_renderer_get_pixel_colors(PixbufRenderer *pr, gint x_pixel, gint y_pixel, 
2681                                           gint *r_mouse, gint *g_mouse, gint *b_mouse)
2682 {
2683         GdkPixbuf *pb = pr->pixbuf;
2684         gint p_alpha, prs;
2685         guchar *p_pix, *pp;
2686         gint map_x, map_y, map_w, map_h;
2687         
2688         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
2689         g_return_val_if_fail(r_mouse != NULL && g_mouse != NULL && b_mouse != NULL, FALSE);
2690
2691         if (!pr->pixbuf && !pr->source_tiles_enabled)
2692                 {
2693                 *r_mouse = -1;
2694                 *g_mouse = -1;
2695                 *b_mouse = -1;
2696                 return FALSE;
2697                 }
2698         
2699         if (!pb) return FALSE;
2700
2701         pr_tile_region_map_orientation(pr->orientation,
2702                                         x_pixel, y_pixel,
2703                                         pr->image_width, pr->image_height,
2704                                         1, 1, /*single pixel */
2705                                         &map_x, &map_y,
2706                                         &map_w, &map_h);
2707
2708         if (map_x < 0 || map_x > gdk_pixbuf_get_width(pr->pixbuf) - 1) return  FALSE;
2709         if (map_y < 0 || map_y > gdk_pixbuf_get_height(pr->pixbuf) - 1) return  FALSE;
2710         
2711         p_alpha = gdk_pixbuf_get_has_alpha(pb);
2712         prs = gdk_pixbuf_get_rowstride(pb);
2713         p_pix = gdk_pixbuf_get_pixels(pb);
2714
2715         pp = p_pix + map_y * prs + (map_x * (p_alpha ? 4 : 3));
2716         *r_mouse = *pp;
2717         pp++;
2718         *g_mouse = *pp;
2719         pp++;
2720         *b_mouse = *pp;
2721         
2722         return TRUE;
2723 }
2724
2725 gboolean pixbuf_renderer_get_mouse_position(PixbufRenderer *pr, gint *x_pixel_return, gint *y_pixel_return)
2726 {
2727         gint x_pixel, y_pixel, x_pixel_clamped, y_pixel_clamped;
2728              
2729         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
2730         g_return_val_if_fail(x_pixel_return != NULL && y_pixel_return != NULL, FALSE);
2731
2732         if (!pr->pixbuf && !pr->source_tiles_enabled)
2733                 {
2734                 *x_pixel_return = -1;
2735                 *y_pixel_return = -1;
2736                 return FALSE;
2737                 }
2738         
2739         x_pixel = floor((gdouble)(pr->x_mouse - pr->x_offset + pr->x_scroll) / pr->scale);
2740         y_pixel = floor((gdouble)(pr->y_mouse - pr->y_offset + pr->y_scroll) / pr->scale / pr->aspect_ratio);
2741         x_pixel_clamped = CLAMP(x_pixel, 0, pr->image_width - 1);
2742         y_pixel_clamped = CLAMP(y_pixel, 0, pr->image_height - 1);
2743         
2744         if(x_pixel != x_pixel_clamped || y_pixel != y_pixel_clamped)
2745                 {
2746                 /* mouse is not on pr */
2747                 x_pixel = y_pixel = -1;
2748                 }
2749
2750         *x_pixel_return = x_pixel;
2751         *y_pixel_return = y_pixel;
2752         
2753         return TRUE;
2754 }
2755
2756 gboolean pixbuf_renderer_get_image_size(PixbufRenderer *pr, gint *width, gint *height)
2757 {
2758         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
2759         g_return_val_if_fail(width != NULL && height != NULL, FALSE);
2760
2761         if (!pr->pixbuf && !pr->source_tiles_enabled && (!pr->image_width || !pr->image_height))
2762                 {
2763                 *width = 0;
2764                 *height = 0;
2765                 return FALSE;
2766                 }
2767
2768         *width = pr->image_width;
2769         *height = pr->image_height;
2770         return TRUE;
2771 }
2772
2773 gboolean pixbuf_renderer_get_scaled_size(PixbufRenderer *pr, gint *width, gint *height)
2774 {
2775         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
2776         g_return_val_if_fail(width != NULL && height != NULL, FALSE);
2777
2778         if (!pr->pixbuf && !pr->source_tiles_enabled && (!pr->image_width || !pr->image_height))
2779                 {
2780                 *width = 0;
2781                 *height = 0;
2782                 return FALSE;
2783                 }
2784
2785         *width = pr->width;
2786         *height = pr->height;
2787         return TRUE;
2788 }
2789
2790 gboolean pixbuf_renderer_get_visible_rect(PixbufRenderer *pr, GdkRectangle *rect)
2791 {
2792         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
2793         g_return_val_if_fail(rect != NULL, FALSE);
2794
2795         if ((!pr->pixbuf && !pr->source_tiles_enabled) ||
2796             !pr->scale)
2797                 {
2798                 rect->x = 0;
2799                 rect->y = 0;
2800                 rect->width = 0;
2801                 rect->height = 0;
2802                 return FALSE;
2803                 }
2804
2805         rect->x = (gint)((gdouble)pr->x_scroll / pr->scale);
2806         rect->y = (gint)((gdouble)pr->y_scroll / pr->scale / pr->aspect_ratio);
2807         rect->width = (gint)((gdouble)pr->vis_width / pr->scale);
2808         rect->height = (gint)((gdouble)pr->vis_height / pr->scale / pr->aspect_ratio);
2809         return TRUE;
2810 }
2811
2812 gboolean pixbuf_renderer_get_virtual_rect(PixbufRenderer *pr, GdkRectangle *rect)
2813 {
2814         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
2815         g_return_val_if_fail(rect != NULL, FALSE);
2816
2817         if ((!pr->pixbuf && !pr->source_tiles_enabled))
2818                 {
2819                 rect->x = 0;
2820                 rect->y = 0;
2821                 rect->width = 0;
2822                 rect->height = 0;
2823                 return FALSE;
2824                 }
2825
2826         rect->x = pr->x_scroll;
2827         rect->y = pr->y_scroll;
2828         rect->width = pr->vis_width;
2829         rect->height = pr->vis_height;
2830         return TRUE;
2831 }
2832
2833 void pixbuf_renderer_set_size_early(PixbufRenderer *pr, guint width, guint height)
2834 {
2835         gdouble zoom;
2836         gint w, h;
2837
2838         zoom = pixbuf_renderer_zoom_get(pr);
2839         pr->image_width = width;
2840         pr->image_height = height;
2841
2842         pr_zoom_clamp(pr, zoom, PR_ZOOM_FORCE, NULL);
2843
2844         //w = width;
2845         //h = height;
2846
2847         //pr->width = width;
2848         //pr->height = height;
2849 }
2850
2851 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */