in debug mode print time information on selected events
[geeqie.git] / src / pixbuf-renderer.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <math.h>
15 #include "pixbuf-renderer.h"
16 #include "intl.h"
17
18 #include <gtk/gtk.h>
19
20
21 /* comment this out if not using this from within Geeqie
22  * defining GQ_BUILD does these things:
23  *   - Sets the shift-click scroller pixbuf to a nice icon instead of a black box
24  */
25 #define GQ_BUILD 1
26
27
28 #ifdef GQ_BUILD
29         #include "pixbuf_util.h"
30         
31         /* for debug */
32         #include "main.h"
33 #endif
34
35
36 /* size to use when breaking up image pane for rendering */
37 #define PR_TILE_SIZE 128
38
39 /* default size of tile cache (mb) */
40 #define PR_CACHE_SIZE_DEFAULT 8
41
42 /* default min and max zoom */
43 #define PR_ZOOM_MIN -32.0
44 #define PR_ZOOM_MAX 32.0
45
46 /* distance to drag mouse to disable image flip */
47 #define PR_DRAG_SCROLL_THRESHHOLD 4
48
49 /* increase pan rate when holding down shift */
50 #define PR_PAN_SHIFT_MULTIPLIER 6
51
52 /* scroller config */
53 #define PR_SCROLLER_UPDATES_PER_SEC 30
54 #define PR_SCROLLER_DEAD_ZONE 6
55
56 /* alpha channel checkerboard background (same as gimp) */
57 #define PR_ALPHA_CHECK1 0x00999999
58 #define PR_ALPHA_CHECK2 0x00666666
59 #define PR_ALPHA_CHECK_SIZE 16
60
61 /* when scaling image to below this size, use nearest pixel for scaling
62  * (below about 4, the other scale types become slow generating their conversion tables)
63  */
64 #define PR_MIN_SCALE_SIZE 8
65
66
67 typedef enum {
68         TILE_RENDER_NONE = 0,   /* do nothing */
69         TILE_RENDER_AREA,       /* render an area of the tile */
70         TILE_RENDER_ALL         /* render the whole tile */
71 } ImageTileRenderType;
72
73 typedef struct _ImageTile ImageTile;
74 typedef struct _QueueData QueueData;
75
76 struct _ImageTile
77 {
78         GdkPixmap *pixmap;      /* off screen buffer */
79         GdkPixbuf *pixbuf;      /* pixbuf area for zooming */
80         gint x;                 /* x offset into image */
81         gint y;                 /* y offset into image */
82         gint w;                 /* width that is visible (may be less if at edge of image) */
83         gint h;                 /* height '' */
84
85         gboolean blank;
86
87 /* render_todo: (explanation)
88         NONE    do nothing
89         AREA    render area of tile, usually only used when loading an image
90                 note: will jump to an ALL if render_done is not ALL.
91         ALL     render entire tile, if never done before w/ ALL, for expose events *only*
92 */
93
94         ImageTileRenderType render_todo;        /* what to do (see above) */
95         ImageTileRenderType render_done;        /* highest that has been done before on tile */
96
97         QueueData *qd;
98         QueueData *qd2;
99
100         guint size;             /* est. memory used by pixmap and pixbuf */
101 };
102
103 struct _QueueData
104 {
105         ImageTile *it;
106         gint x;
107         gint y;
108         gint w;
109         gint h;
110         gboolean new_data;
111 };
112
113 typedef struct _SourceTile SourceTile;
114 struct _SourceTile
115 {
116         gint x;
117         gint y;
118         GdkPixbuf *pixbuf;
119         gboolean blank;
120 };
121
122 typedef struct _OverlayData OverlayData;
123 struct _OverlayData
124 {
125         gint id;
126
127         GdkPixbuf *pixbuf;
128         GdkWindow *window;
129
130         gint x;
131         gint y;
132         gint relative;  /* x,y coordinates are relative, negative values start bottom right */
133
134         gint always;    /* hide temporarily when scrolling (not yet implemented) */
135 };
136
137 enum {
138         SIGNAL_ZOOM = 0,
139         SIGNAL_CLICKED,
140         SIGNAL_SCROLL_NOTIFY,
141         SIGNAL_RENDER_COMPLETE,
142         SIGNAL_DRAG,
143         SIGNAL_COUNT
144 };
145
146 enum {
147         PROP_0,
148         PROP_ZOOM_MIN,
149         PROP_ZOOM_MAX,
150         PROP_ZOOM_QUALITY,
151         PROP_ZOOM_2PASS,
152         PROP_ZOOM_EXPAND,
153         PROP_DITHER_QUALITY,
154         PROP_SCROLL_RESET,
155         PROP_DELAY_FLIP,
156         PROP_LOADING,
157         PROP_COMPLETE,
158         PROP_CACHE_SIZE_DISPLAY,
159         PROP_CACHE_SIZE_TILES,
160         PROP_WINDOW_FIT,
161         PROP_WINDOW_LIMIT,
162         PROP_WINDOW_LIMIT_VALUE,
163         PROP_AUTOFIT_LIMIT,
164         PROP_AUTOFIT_LIMIT_VALUE
165 };
166
167
168
169 static guint signals[SIGNAL_COUNT] = { 0 };
170 static GtkEventBoxClass *parent_class = NULL;
171
172
173
174 static void pixbuf_renderer_class_init(PixbufRendererClass *class);
175 static void pixbuf_renderer_init(PixbufRenderer *pr);
176 static void pixbuf_renderer_finalize(GObject *object);
177 static void pixbuf_renderer_set_property(GObject *object, guint prop_id,
178                                          const GValue *value, GParamSpec *pspec);
179 static void pixbuf_renderer_get_property(GObject *object, guint prop_id,
180                                          GValue *value, GParamSpec *pspec);
181 static gint pixbuf_renderer_expose(GtkWidget *widget, GdkEventExpose *event);
182
183 static void pr_render_complete_signal(PixbufRenderer *pr);
184
185 static void pr_overlay_list_clear(PixbufRenderer *pr);
186 static void pr_scroller_timer_set(PixbufRenderer *pr, gint start);
187 static void pr_border_draw(PixbufRenderer *pr, gint x, gint y, gint w, gint h);
188
189
190 static void pr_source_tile_free_all(PixbufRenderer *pr);
191 static void pr_tile_free_all(PixbufRenderer *pr);
192 static void pr_tile_invalidate_region(PixbufRenderer *pr, gint x, gint y, gint w, gint h);
193 static gint pr_tile_is_visible(PixbufRenderer *pr, ImageTile *it);
194 static void pr_queue_clear(PixbufRenderer *pr);
195 static void pr_queue_merge(QueueData *parent, QueueData *qd);
196 static void pr_queue(PixbufRenderer *pr, gint x, gint y, gint w, gint h,
197                      gint clamp, ImageTileRenderType render, gint new_data, gint only_existing);
198
199 static void pr_redraw(PixbufRenderer *pr, gint new_data);
200
201 static void pr_zoom_sync(PixbufRenderer *pr, gdouble zoom,
202                          gint force, gint new,
203                          gint center_point, gint px, gint py);
204
205 static void pr_signals_connect(PixbufRenderer *pr);
206 static void pr_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data);
207 static void pixbuf_renderer_paint(PixbufRenderer *pr, GdkRectangle *area);
208
209
210 /*
211  *-------------------------------------------------------------------
212  * Pixbuf Renderer object
213  *-------------------------------------------------------------------
214  */
215
216 GType pixbuf_renderer_get_type(void)
217 {
218         static GType pixbuf_renderer_type = 0;
219
220         if (!pixbuf_renderer_type)
221                 {
222                 static const GTypeInfo pixbuf_renderer_info =
223                         {
224                         sizeof(PixbufRendererClass),
225                         NULL,           /* base_init */
226                         NULL,           /* base_finalize */
227                         (GClassInitFunc)pixbuf_renderer_class_init,
228                         NULL,           /* class_finalize */
229                         NULL,           /* class_data */
230                         sizeof(PixbufRenderer),
231                         0,              /* n_preallocs */
232                         (GInstanceInitFunc)pixbuf_renderer_init,
233                         };
234
235                 pixbuf_renderer_type = g_type_register_static(GTK_TYPE_EVENT_BOX, "PixbufRenderer",
236                                                               &pixbuf_renderer_info, 0);
237                 }
238
239         return pixbuf_renderer_type;
240 }
241
242 static void pixbuf_renderer_class_init(PixbufRendererClass *class)
243 {
244         GObjectClass *gobject_class = G_OBJECT_CLASS(class);
245         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
246
247         parent_class = g_type_class_peek_parent(class);
248
249         gobject_class->set_property = pixbuf_renderer_set_property;
250         gobject_class->get_property = pixbuf_renderer_get_property;
251
252         gobject_class->finalize = pixbuf_renderer_finalize;
253
254         widget_class->expose_event = pixbuf_renderer_expose;
255
256         g_object_class_install_property(gobject_class,
257                                         PROP_ZOOM_MIN,
258                                         g_param_spec_double("zoom_min",
259                                                             "Zoom minimum",
260                                                             NULL,
261                                                             -1000.0,
262                                                             1000.0,
263                                                             PR_ZOOM_MIN,
264                                                             G_PARAM_READABLE | G_PARAM_WRITABLE));
265
266         g_object_class_install_property(gobject_class,
267                                         PROP_ZOOM_MAX,
268                                         g_param_spec_double("zoom_max",
269                                                             "Zoom maximum",
270                                                             NULL,
271                                                             -1000.0,
272                                                             1000.0,
273                                                             PR_ZOOM_MIN,
274                                                             G_PARAM_READABLE | G_PARAM_WRITABLE));
275
276         g_object_class_install_property(gobject_class,
277                                         PROP_ZOOM_QUALITY,
278                                         g_param_spec_uint("zoom_quality",
279                                                           "Zoom quality",
280                                                           NULL,
281                                                           GDK_INTERP_NEAREST,
282                                                           GDK_INTERP_HYPER,
283                                                           GDK_INTERP_BILINEAR,
284                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
285
286         g_object_class_install_property(gobject_class,
287                                         PROP_ZOOM_2PASS,
288                                         g_param_spec_boolean("zoom_2pass",
289                                                              "2 pass zoom",
290                                                              NULL,
291                                                              TRUE,
292                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
293
294         g_object_class_install_property(gobject_class,
295                                         PROP_ZOOM_EXPAND,
296                                         g_param_spec_boolean("zoom_expand",
297                                                              "Expand image in autozoom.",
298                                                              NULL,
299                                                              FALSE,
300                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
301
302         g_object_class_install_property(gobject_class,
303                                         PROP_DITHER_QUALITY,
304                                         g_param_spec_uint("dither_quality",
305                                                           "Dither quality",
306                                                           NULL,
307                                                           GDK_RGB_DITHER_NONE,
308                                                           GDK_RGB_DITHER_MAX,
309                                                           GDK_RGB_DITHER_NORMAL,
310                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
311
312         g_object_class_install_property(gobject_class,
313                                         PROP_SCROLL_RESET,
314                                         g_param_spec_uint("scroll_reset",
315                                                           "New image scroll reset",
316                                                           NULL,
317                                                           PR_SCROLL_RESET_TOPLEFT,
318                                                           PR_SCROLL_RESET_NOCHANGE,
319                                                           PR_SCROLL_RESET_TOPLEFT,
320                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
321
322         g_object_class_install_property(gobject_class,
323                                         PROP_DELAY_FLIP,
324                                         g_param_spec_boolean("delay_flip",
325                                                              "Delay image update",
326                                                              NULL,
327                                                              FALSE,
328                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
329
330         g_object_class_install_property(gobject_class,
331                                         PROP_LOADING,
332                                         g_param_spec_boolean("loading",
333                                                              "Image actively loading",
334                                                              NULL,
335                                                              FALSE,
336                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
337
338         g_object_class_install_property(gobject_class,
339                                         PROP_COMPLETE,
340                                         g_param_spec_boolean("complete",
341                                                              "Image rendering complete",
342                                                              NULL,
343                                                              FALSE,
344                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
345
346         g_object_class_install_property(gobject_class,
347                                         PROP_CACHE_SIZE_DISPLAY,
348                                         g_param_spec_uint("cache_display",
349                                                           "Display cache size MB",
350                                                           NULL,
351                                                           0,
352                                                           128,
353                                                           PR_CACHE_SIZE_DEFAULT,
354                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
355
356         g_object_class_install_property(gobject_class,
357                                         PROP_CACHE_SIZE_TILES,
358                                         g_param_spec_uint("cache_tiles",
359                                                           "Tile cache count",
360                                                           "Number of tiles to retain in memory at any one time.",
361                                                           0,
362                                                           256,
363                                                           PR_CACHE_SIZE_DEFAULT,
364                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
365
366         g_object_class_install_property(gobject_class,
367                                         PROP_WINDOW_FIT,
368                                         g_param_spec_boolean("window_fit",
369                                                              "Fit window to image size",
370                                                              NULL,
371                                                              FALSE,
372                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
373
374         g_object_class_install_property(gobject_class,
375                                         PROP_WINDOW_LIMIT,
376                                         g_param_spec_boolean("window_limit",
377                                                              "Limit size of parent window",
378                                                              NULL,
379                                                              FALSE,
380                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
381
382         g_object_class_install_property(gobject_class,
383                                         PROP_WINDOW_LIMIT_VALUE,
384                                         g_param_spec_uint("window_limit_value",
385                                                           "Size limit of parent window",
386                                                           NULL,
387                                                           10,
388                                                           150,
389                                                           100,
390                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
391         
392         g_object_class_install_property(gobject_class,
393                                         PROP_AUTOFIT_LIMIT,
394                                         g_param_spec_boolean("autofit_limit",
395                                                              "Limit size of image when autofitting",
396                                                              NULL,
397                                                              FALSE,
398                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
399
400         g_object_class_install_property(gobject_class,
401                                         PROP_AUTOFIT_LIMIT_VALUE,
402                                         g_param_spec_uint("autofit_limit_value",
403                                                           "Size limit of image when autofitting",
404                                                           NULL,
405                                                           10,
406                                                           150,
407                                                           100,
408                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
409
410
411         signals[SIGNAL_ZOOM] = 
412                 g_signal_new("zoom",
413                              G_OBJECT_CLASS_TYPE(gobject_class),
414                              G_SIGNAL_RUN_LAST,
415                              G_STRUCT_OFFSET(PixbufRendererClass, zoom),
416                              NULL, NULL,
417                              g_cclosure_marshal_VOID__DOUBLE,
418                              G_TYPE_NONE, 1,
419                              G_TYPE_DOUBLE);
420
421         signals[SIGNAL_CLICKED] = 
422                 g_signal_new("clicked",
423                              G_OBJECT_CLASS_TYPE(gobject_class),
424                              G_SIGNAL_RUN_LAST,
425                              G_STRUCT_OFFSET(PixbufRendererClass, clicked),
426                              NULL, NULL,
427                              g_cclosure_marshal_VOID__BOXED,
428                              G_TYPE_NONE, 1,
429                              GDK_TYPE_EVENT);
430
431         signals[SIGNAL_SCROLL_NOTIFY] = 
432                 g_signal_new("scroll-notify",
433                              G_OBJECT_CLASS_TYPE(gobject_class),
434                              G_SIGNAL_RUN_LAST,
435                              G_STRUCT_OFFSET(PixbufRendererClass, scroll_notify),
436                              NULL, NULL,
437                              g_cclosure_marshal_VOID__VOID,
438                              G_TYPE_NONE, 0);
439
440         signals[SIGNAL_RENDER_COMPLETE] = 
441                 g_signal_new("render-complete",
442                              G_OBJECT_CLASS_TYPE(gobject_class),
443                              G_SIGNAL_RUN_LAST,
444                              G_STRUCT_OFFSET(PixbufRendererClass, render_complete),
445                              NULL, NULL,
446                              g_cclosure_marshal_VOID__VOID,
447                              G_TYPE_NONE, 0);
448
449         signals[SIGNAL_DRAG] = 
450                 g_signal_new("drag",
451                              G_OBJECT_CLASS_TYPE(gobject_class),
452                              G_SIGNAL_RUN_LAST,
453                              G_STRUCT_OFFSET(PixbufRendererClass, drag),
454                              NULL, NULL,
455                              g_cclosure_marshal_VOID__BOXED,
456                              G_TYPE_NONE, 1,
457                              GDK_TYPE_EVENT);
458 }
459
460 static void pixbuf_renderer_init(PixbufRenderer *pr)
461 {
462         GtkWidget *box;
463
464         box = GTK_WIDGET(pr);
465
466         pr->zoom_min = PR_ZOOM_MIN;
467         pr->zoom_max = PR_ZOOM_MAX;
468         pr->zoom_quality = GDK_INTERP_BILINEAR;
469         pr->zoom_2pass = FALSE;
470
471         pr->zoom = 1.0;
472         pr->scale = 1.0;
473
474         pr->dither_quality = GDK_RGB_DITHER_NORMAL;
475
476         pr->scroll_reset = PR_SCROLL_RESET_TOPLEFT;
477
478         pr->draw_idle_id = -1;
479
480         pr->tile_width = PR_TILE_SIZE;
481         pr->tile_height = PR_TILE_SIZE;
482
483         pr->tiles = NULL;
484         pr->tile_cache_size = 0;
485
486         pr->tile_cache_max = PR_CACHE_SIZE_DEFAULT;
487
488         pr->scroller_id = -1;
489         pr->scroller_overlay = -1;
490
491         pr->source_tiles_enabled = FALSE;
492         pr->source_tiles = NULL;
493
494         gtk_widget_set_double_buffered(box, FALSE);
495         g_signal_connect_after(G_OBJECT(box), "size_allocate",
496                                G_CALLBACK(pr_size_cb), pr);
497
498         pr_signals_connect(pr);
499 }
500
501 static void pixbuf_renderer_finalize(GObject *object)
502 {
503         PixbufRenderer *pr;
504
505         pr = PIXBUF_RENDERER(object);
506
507         pr_queue_clear(pr);
508         pr_tile_free_all(pr);
509
510         if (pr->pixbuf) g_object_unref(pr->pixbuf);
511
512         pr_scroller_timer_set(pr, FALSE);
513         pr_overlay_list_clear(pr);
514
515         pr_source_tile_free_all(pr);
516 }
517
518 PixbufRenderer* pixbuf_renderer_new(void)
519 {
520         return g_object_new(TYPE_PIXBUF_RENDERER, NULL);
521 }
522
523 static void pixbuf_renderer_set_property(GObject *object, guint prop_id,
524                                          const GValue *value, GParamSpec *pspec)
525 {
526         PixbufRenderer *pr;
527
528         pr = PIXBUF_RENDERER(object);
529
530         switch (prop_id)
531                 {
532                 case PROP_ZOOM_MIN:
533                         pr->zoom_min = g_value_get_double(value);
534                         break;
535                 case PROP_ZOOM_MAX:
536                         pr->zoom_max = g_value_get_double(value);
537                         break;
538                 case PROP_ZOOM_QUALITY:
539                         pr->zoom_quality = g_value_get_uint(value);
540                         break;
541                 case PROP_ZOOM_2PASS:
542                         pr->zoom_2pass = g_value_get_boolean(value);
543                         break;
544                 case PROP_ZOOM_EXPAND:
545                         pr->zoom_expand = g_value_get_boolean(value);
546                         break;
547                 case PROP_DITHER_QUALITY:
548                         pr->dither_quality = g_value_get_uint(value);
549                         break;
550                 case PROP_SCROLL_RESET:
551                         pr->scroll_reset = g_value_get_uint(value);
552                         break;
553                 case PROP_DELAY_FLIP:
554                         pr->delay_flip = g_value_get_boolean(value);
555                         break;
556                 case PROP_LOADING:
557                         pr->loading = g_value_get_boolean(value);
558                         break;
559                 case PROP_COMPLETE:
560                         pr->complete = g_value_get_boolean(value);
561                         break;
562                 case PROP_CACHE_SIZE_DISPLAY:
563                         pr->tile_cache_max = g_value_get_uint(value);
564                         break;
565                 case PROP_CACHE_SIZE_TILES:
566                         pr->source_tiles_cache_size = g_value_get_uint(value);
567                         break;
568                 case PROP_WINDOW_FIT:
569                         pr->window_fit = g_value_get_boolean(value);
570                         break;
571                 case PROP_WINDOW_LIMIT:
572                         pr->window_limit = g_value_get_boolean(value);
573                         break;
574                 case PROP_WINDOW_LIMIT_VALUE:
575                         pr->window_limit_size = g_value_get_uint(value);
576                         break;
577                 case PROP_AUTOFIT_LIMIT:
578                         pr->autofit_limit = g_value_get_boolean(value);
579                         break;
580                 case PROP_AUTOFIT_LIMIT_VALUE:
581                         pr->autofit_limit_size = g_value_get_uint(value);
582                         break;
583                 default:
584                         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
585                         break;
586                 }
587 }
588
589 static void pixbuf_renderer_get_property(GObject *object, guint prop_id,
590                                          GValue *value, GParamSpec *pspec)
591 {
592         PixbufRenderer *pr;
593
594         pr = PIXBUF_RENDERER(object);
595
596         switch (prop_id)
597                 {
598                 case PROP_ZOOM_MIN:
599                         g_value_set_double(value, pr->zoom_min);
600                         break;
601                 case PROP_ZOOM_MAX:
602                         g_value_set_double(value, pr->zoom_max);
603                         break;
604                 case PROP_ZOOM_QUALITY:
605                         g_value_set_uint(value, pr->zoom_quality);
606                         break;
607                 case PROP_ZOOM_2PASS:
608                         g_value_set_boolean(value, pr->zoom_2pass);
609                         break;
610                 case PROP_ZOOM_EXPAND:
611                         g_value_set_boolean(value, pr->zoom_expand);
612                         break;
613                 case PROP_DITHER_QUALITY:
614                         g_value_set_uint(value, pr->dither_quality);
615                         break;
616                 case PROP_SCROLL_RESET:
617                         g_value_set_uint(value, pr->scroll_reset);
618                         break;
619                 case PROP_DELAY_FLIP:
620                         g_value_set_boolean(value, pr->delay_flip);
621                         break;
622                 case PROP_LOADING:
623                         g_value_set_boolean(value, pr->loading);
624                         break;
625                 case PROP_COMPLETE:
626                         g_value_set_boolean(value, pr->complete);
627                         break;
628                 case PROP_CACHE_SIZE_DISPLAY:
629                         g_value_set_uint(value, pr->tile_cache_max);
630                         break;
631                 case PROP_CACHE_SIZE_TILES:
632                         g_value_set_uint(value, pr->source_tiles_cache_size);
633                         break;
634                 case PROP_WINDOW_FIT:
635                         g_value_set_boolean(value, pr->window_fit);
636                         break;
637                 case PROP_WINDOW_LIMIT:
638                         g_value_set_boolean(value, pr->window_limit);
639                         break;
640                 case PROP_WINDOW_LIMIT_VALUE:
641                         g_value_set_uint(value, pr->window_limit_size);
642                         break;
643                 case PROP_AUTOFIT_LIMIT:
644                         g_value_set_boolean(value, pr->autofit_limit);
645                         break;
646                 case PROP_AUTOFIT_LIMIT_VALUE:
647                         g_value_set_uint(value, pr->autofit_limit_size);
648                         break;
649                 default:
650                         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
651                         break;
652                 }
653 }
654
655 static gint pixbuf_renderer_expose(GtkWidget *widget, GdkEventExpose *event)
656 {
657         if (GTK_WIDGET_DRAWABLE(widget))
658                 {
659                 if (!GTK_WIDGET_NO_WINDOW(widget))
660                         {
661                         if (event->window != widget->window)
662                                 {
663                                 GdkRectangle area;
664
665                                 gdk_window_get_position(event->window, &area.x, &area.y);
666
667                                 area.x += event->area.x;
668                                 area.y += event->area.y;
669                                 area.width = event->area.width;
670                                 area.height = event->area.height;
671                                 pixbuf_renderer_paint(PIXBUF_RENDERER(widget), &area);
672                                 }
673                         else
674                                 {
675                                 pixbuf_renderer_paint(PIXBUF_RENDERER(widget), &event->area);
676                                 }
677                         }
678                 }
679
680         return FALSE;
681 }
682
683 /*
684  *-------------------------------------------------------------------
685  * misc utilities
686  *-------------------------------------------------------------------
687  */
688
689 static void widget_set_cursor(GtkWidget *widget, gint icon)
690 {
691         GdkCursor *cursor;
692
693         if (!widget->window) return;
694
695         if (icon == -1)
696                 {
697                 cursor = NULL;
698                 }
699         else
700                 {
701                 cursor = gdk_cursor_new (icon);
702                 }
703
704         gdk_window_set_cursor(widget->window, cursor);
705
706         if (cursor) gdk_cursor_unref(cursor);
707 }
708
709 static gint pixmap_calc_size(GdkPixmap *pixmap)
710 {
711         gint w, h, d;
712
713         d = gdk_drawable_get_depth(pixmap);
714         gdk_drawable_get_size(pixmap, &w, &h);
715         return w * h * (d / 8);
716 }
717
718 static gint pr_clip_region(gint x, gint y, gint w, gint h,
719                            gint clip_x, gint clip_y, gint clip_w, gint clip_h,
720                            gint *rx, gint *ry, gint *rw, gint *rh)
721 {
722         if (clip_x + clip_w <= x ||
723             clip_x >= x + w ||
724             clip_y + clip_h <= y ||
725             clip_y >= y + h)
726                 {
727                 return FALSE;
728                 }
729
730         *rx = MAX(x, clip_x);
731         *rw = MIN((x + w), (clip_x + clip_w)) - *rx;
732
733         *ry = MAX(y, clip_y);
734         *rh = MIN((y + h), (clip_y + clip_h)) - *ry;
735
736         return TRUE;
737 }
738
739 static gint pr_parent_window_sizable(PixbufRenderer *pr)
740 {
741         GdkWindowState state;
742
743         if (!pr->parent_window) return FALSE;
744         if (!pr->window_fit) return FALSE;
745         if (!GTK_WIDGET(pr)->window) return FALSE;
746
747         if (!pr->parent_window->window) return FALSE;
748         state = gdk_window_get_state(pr->parent_window->window);
749         if (state & GDK_WINDOW_STATE_MAXIMIZED) return FALSE;
750
751         return TRUE;
752 }
753
754 static gint pr_parent_window_resize(PixbufRenderer *pr, gint w, gint h)
755 {
756         GtkWidget *widget;
757         GtkWidget *parent;
758         gint ww, wh;
759
760         if (!pr_parent_window_sizable(pr)) return FALSE;
761
762         if (pr->window_limit)
763                 {
764                 gint sw = gdk_screen_width() * pr->window_limit_size / 100;
765                 gint sh = gdk_screen_height() * pr->window_limit_size / 100;
766
767                 if (w > sw) w = sw;
768                 if (h > sh) h = sh;
769                 }
770
771         widget = GTK_WIDGET(pr);
772         parent = GTK_WIDGET(pr->parent_window);
773
774         w += (parent->allocation.width - widget->allocation.width);
775         h += (parent->allocation.height - widget->allocation.height);
776
777         gdk_drawable_get_size(parent->window, &ww, &wh);
778         if (w == ww && h == wh) return FALSE;
779
780         gdk_window_resize(parent->window, w, h);
781
782         return TRUE;
783 }
784
785 void pixbuf_renderer_set_parent(PixbufRenderer *pr, GtkWindow *window)
786 {
787         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
788         g_return_if_fail(window == NULL || GTK_IS_WINDOW(window));
789
790         pr->parent_window = GTK_WIDGET(window);
791 }
792
793 GtkWindow *pixbuf_renderer_get_parent(PixbufRenderer *pr)
794 {
795         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), NULL);
796
797         return GTK_WINDOW(pr->parent_window);
798 }
799
800
801 /*
802  *-------------------------------------------------------------------
803  * overlays
804  *-------------------------------------------------------------------
805  */
806
807 static void pr_overlay_get_position(PixbufRenderer *pr, OverlayData *od,
808                                     gint *x, gint *y, gint *w, gint *h)
809 {
810         gint px, py, pw, ph;
811
812         pw = gdk_pixbuf_get_width(od->pixbuf);
813         ph = gdk_pixbuf_get_height(od->pixbuf);
814         px = od->x;
815         py = od->y;
816
817         if (od->relative)
818                 {
819                 if (px < 0) px = pr->window_width - pw + px;
820                 if (py < 0) py = pr->window_height - ph + py;
821                 }
822
823         if (x) *x = px;
824         if (y) *y = py;
825         if (w) *w = pw;
826         if (h) *h = ph;
827 }
828
829 static void pr_overlay_draw(PixbufRenderer *pr, gint x, gint y, gint w, gint h,
830                             ImageTile *it)
831 {
832         GtkWidget *box;
833         GList *work;
834
835         box = GTK_WIDGET(pr);
836
837         work = pr->overlay_list;
838         while (work)
839                 {
840                 OverlayData *od;
841                 gint px, py, pw, ph;
842                 gint rx, ry, rw, rh;
843
844                 od = work->data;
845                 work = work->next;
846
847                 pr_overlay_get_position(pr, od, &px, &py, &pw, &ph);
848                 if (pr_clip_region(x, y, w, h, px, py, pw, ph, &rx, &ry, &rw, &rh))
849                         {
850                         if (!pr->overlay_buffer)
851                                 {
852                                 pr->overlay_buffer = gdk_pixmap_new(((GtkWidget *)pr)->window, pr->tile_width, pr->tile_height, -1);
853                                 }
854
855                         if (it)
856                                 {
857                                 gdk_draw_drawable(pr->overlay_buffer, box->style->fg_gc[GTK_WIDGET_STATE(box)],
858                                                   it->pixmap,
859                                                   rx - (pr->x_offset + (it->x - pr->x_scroll)),
860                                                   ry - (pr->y_offset + (it->y - pr->y_scroll)),
861                                                   0, 0, rw, rh);
862                                 gdk_draw_pixbuf(pr->overlay_buffer,
863                                                 box->style->fg_gc[GTK_WIDGET_STATE(box)],
864                                                 od->pixbuf,
865                                                 rx - px, ry - py,
866                                                 0, 0, rw, rh,
867                                                 pr->dither_quality, rx, ry);
868                                 gdk_draw_drawable(od->window, box->style->fg_gc[GTK_WIDGET_STATE(box)],
869                                                   pr->overlay_buffer,
870                                                   0, 0,
871                                                   rx - px, ry - py, rw, rh);
872                                 }
873                         else
874                                 {
875                                 /* no ImageTile means region may be larger than our scratch buffer */
876                                 gint sx, sy;
877
878                                 for (sx = rx; sx < rx + rw; sx += pr->tile_width)
879                                     for(sy = ry; sy < ry + rh; sy += pr->tile_height)
880                                         {
881                                         gint sw, sh;
882
883                                         sw = MIN(rx + rw - sx, pr->tile_width);
884                                         sh = MIN(ry + rh - sy, pr->tile_height);
885
886                                         gdk_draw_rectangle(pr->overlay_buffer,
887                                                            box->style->bg_gc[GTK_WIDGET_STATE(box)], TRUE,
888                                                            0, 0, sw, sh);
889                                         gdk_draw_pixbuf(pr->overlay_buffer,
890                                                         box->style->fg_gc[GTK_WIDGET_STATE(box)],
891                                                         od->pixbuf,
892                                                         sx - px, sy - py,
893                                                         0, 0, sw, sh,
894                                                         pr->dither_quality, sx, sy);
895                                         gdk_draw_drawable(od->window, box->style->fg_gc[GTK_WIDGET_STATE(box)],
896                                                           pr->overlay_buffer,
897                                                           0, 0,
898                                                           sx - px, sy - py, sw, sh);
899                                         }
900                                 }
901                         }
902                 }
903 }
904
905 static void pr_overlay_queue_draw(PixbufRenderer *pr, OverlayData *od)
906 {
907         gint x, y, w, h;
908
909         pr_overlay_get_position(pr, od, &x, &y, &w, &h);
910         pr_queue(pr, pr->x_scroll - pr->x_offset + x,
911                  pr->y_scroll - pr->y_offset + y,
912                  w, h,
913                  FALSE, TILE_RENDER_ALL, FALSE, FALSE);
914
915         pr_border_draw(pr, x, y, w, h);
916 }
917
918 static void pr_overlay_queue_all(PixbufRenderer *pr)
919 {
920         GList *work;
921
922         work = pr->overlay_list;
923         while (work)
924                 {
925                 OverlayData *od = work->data;
926                 work = work->next;
927
928                 pr_overlay_queue_draw(pr, od);
929                 }
930 }
931
932 static void pr_overlay_update_sizes(PixbufRenderer *pr)
933 {
934         GList *work;
935
936         work = pr->overlay_list;
937         while (work)
938                 {
939                 OverlayData *od = work->data;
940                 work = work->next;
941
942                 if (od->relative && od->window)
943                         {
944                         gint x, y, w, h;
945
946                         pr_overlay_get_position(pr, od, &x, &y, &w, &h);
947                         gdk_window_move_resize(od->window, x, y, w, h);
948                         }
949                 }
950 }
951
952 static OverlayData *pr_overlay_find(PixbufRenderer *pr, gint id)
953 {
954         GList *work;
955
956         work = pr->overlay_list;
957         while (work)
958                 {
959                 OverlayData *od = work->data;
960                 work = work->next;
961
962                 if (od->id == id) return od;
963                 }
964
965         return NULL;
966 }
967
968 gint pixbuf_renderer_overlay_add(PixbufRenderer *pr, GdkPixbuf *pixbuf, gint x, gint y,
969                                  gint relative, gint always)
970 {
971         OverlayData *od;
972         gint id;
973         gint px, py, pw, ph;
974         GdkWindowAttr attributes;
975         gint attributes_mask;
976
977         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), -1);
978         g_return_val_if_fail(pixbuf != NULL, -1);
979
980         id = 1;
981         while (pr_overlay_find(pr, id)) id++;
982
983         od = g_new0(OverlayData, 1);
984         od->id = id;
985         od->pixbuf = pixbuf;
986         g_object_ref(G_OBJECT(od->pixbuf));
987         od->x = x;
988         od->y = y;
989         od->relative = relative;
990         od->always = always;
991
992         pr_overlay_get_position(pr, od, &px, &py, &pw, &ph);
993
994         attributes.window_type = GDK_WINDOW_CHILD;
995         attributes.wclass = GDK_INPUT_OUTPUT;
996         attributes.width = pw;
997         attributes.height = ph;
998         attributes.event_mask = GDK_EXPOSURE_MASK;
999         attributes_mask = 0;
1000
1001         od->window = gdk_window_new(GTK_WIDGET(pr)->window, &attributes, attributes_mask);
1002         gdk_window_set_user_data (od->window, pr);
1003         gdk_window_move(od->window, px, py);
1004         gdk_window_show(od->window);
1005
1006         pr->overlay_list = g_list_append(pr->overlay_list, od);
1007
1008         pr_overlay_queue_draw(pr, od);
1009
1010         return od->id;
1011 }
1012
1013 static void pr_overlay_free(PixbufRenderer *pr, OverlayData *od)
1014 {
1015         pr->overlay_list = g_list_remove(pr->overlay_list, od);
1016
1017         if (od->pixbuf) g_object_unref(G_OBJECT(od->pixbuf));
1018         if (od->window) gdk_window_destroy(od->window);
1019         g_free(od);
1020
1021         if (!pr->overlay_list && pr->overlay_buffer)
1022                 {
1023                 g_object_unref(pr->overlay_buffer);
1024                 pr->overlay_buffer = NULL;
1025                 }
1026 }
1027
1028 static void pr_overlay_list_clear(PixbufRenderer *pr)
1029 {
1030         while (pr->overlay_list)
1031                 {
1032                 OverlayData *od;
1033
1034                 od = pr->overlay_list->data;
1035                 pr_overlay_free(pr, od);
1036                 }
1037 }
1038
1039 void pixbuf_renderer_overlay_set(PixbufRenderer *pr, gint id, GdkPixbuf *pixbuf, gint x, gint y)
1040 {
1041         OverlayData *od;
1042
1043         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1044
1045         od = pr_overlay_find(pr, id);
1046         if (!od) return;
1047
1048         if (pixbuf)
1049                 {
1050                 gint px, py, pw, ph;
1051
1052                 g_object_ref(G_OBJECT(pixbuf));
1053                 g_object_unref(G_OBJECT(od->pixbuf));
1054                 od->pixbuf = pixbuf;
1055
1056                 od->x = x;
1057                 od->y = y;
1058
1059                 pr_overlay_queue_draw(pr, od);
1060                 pr_overlay_get_position(pr, od, &px, &py, &pw, &ph);
1061                 gdk_window_move_resize(od->window, px, py, pw, ph);
1062                 }
1063         else
1064                 {
1065                 pr_overlay_queue_draw(pr, od);
1066                 pr_overlay_free(pr, od);
1067                 }
1068 }
1069
1070 gint pixbuf_renderer_overlay_get(PixbufRenderer *pr, gint id, GdkPixbuf **pixbuf, gint *x, gint *y)
1071 {
1072         OverlayData *od;
1073
1074         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
1075
1076         od = pr_overlay_find(pr, id);
1077         if (!od) return FALSE;
1078
1079         if (pixbuf) *pixbuf = od->pixbuf;
1080         if (x) *x = od->x;
1081         if (y) *y = od->y;
1082
1083         return TRUE;
1084 }
1085
1086 void pixbuf_renderer_overlay_remove(PixbufRenderer *pr, gint id)
1087 {
1088         pixbuf_renderer_overlay_set(pr, id, NULL, 0, 0);
1089 }
1090
1091 /*
1092  *-------------------------------------------------------------------
1093  * scroller overlay
1094  *-------------------------------------------------------------------
1095  */
1096
1097
1098 static gboolean pr_scroller_update_cb(gpointer data)
1099 {
1100         PixbufRenderer *pr = data;
1101         gint x, y;
1102         gint xinc, yinc;
1103
1104         /* this was a simple scroll by difference between scroller and mouse position,
1105          * but all this math results in a smoother result and accounts for a dead zone.
1106          */
1107
1108         if (abs(pr->scroller_xpos - pr->scroller_x) < PR_SCROLLER_DEAD_ZONE)
1109                 {
1110                 x = 0;
1111                 }
1112         else
1113                 {
1114                 gint shift = PR_SCROLLER_DEAD_ZONE / 2 * PR_SCROLLER_UPDATES_PER_SEC;
1115                 x = (pr->scroller_xpos - pr->scroller_x) / 2 * PR_SCROLLER_UPDATES_PER_SEC;
1116                 x += (x > 0) ? -shift : shift;
1117                 }
1118
1119         if (abs(pr->scroller_ypos - pr->scroller_y) < PR_SCROLLER_DEAD_ZONE)
1120                 {
1121                 y = 0;
1122                 }
1123         else
1124                 {
1125                 gint shift = PR_SCROLLER_DEAD_ZONE / 2 * PR_SCROLLER_UPDATES_PER_SEC;
1126                 y = (pr->scroller_ypos - pr->scroller_y) / 2 * PR_SCROLLER_UPDATES_PER_SEC;
1127                 y += (y > 0) ? -shift : shift;
1128                 }
1129
1130         if (abs(x) < PR_SCROLLER_DEAD_ZONE * PR_SCROLLER_UPDATES_PER_SEC)
1131                 {
1132                 xinc = x;
1133                 }
1134         else
1135                 {
1136                 xinc = pr->scroller_xinc;
1137
1138                 if (x >= 0)
1139                         {
1140                         if (xinc < 0) xinc = 0;
1141                         if (x < xinc) xinc = x;
1142                         if (x > xinc) xinc = MIN(xinc + x / PR_SCROLLER_UPDATES_PER_SEC, x);
1143                         }
1144                 else
1145                         {
1146                         if (xinc > 0) xinc = 0;
1147                         if (x > xinc) xinc = x;
1148                         if (x < xinc) xinc = MAX(xinc + x / PR_SCROLLER_UPDATES_PER_SEC, x);
1149                         }
1150                 }
1151
1152         if (abs(y) < PR_SCROLLER_DEAD_ZONE * PR_SCROLLER_UPDATES_PER_SEC)
1153                 {
1154                 yinc = y;
1155                 }
1156         else
1157                 {
1158                 yinc = pr->scroller_yinc;
1159
1160                 if (y >= 0)
1161                         {
1162                         if (yinc < 0) yinc = 0;
1163                         if (y < yinc) yinc = y;
1164                         if (y > yinc) yinc = MIN(yinc + y / PR_SCROLLER_UPDATES_PER_SEC, y);
1165                         }
1166                 else
1167                         {
1168                         if (yinc > 0) yinc = 0;
1169                         if (y > yinc) yinc = y;
1170                         if (y < yinc) yinc = MAX(yinc + y / PR_SCROLLER_UPDATES_PER_SEC, y);
1171                         }
1172                 }
1173
1174         pr->scroller_xinc = xinc;
1175         pr->scroller_yinc = yinc;
1176
1177         xinc = xinc / PR_SCROLLER_UPDATES_PER_SEC;
1178         yinc = yinc / PR_SCROLLER_UPDATES_PER_SEC;
1179
1180         pixbuf_renderer_scroll(pr, xinc, yinc);
1181
1182         return TRUE;
1183 }
1184
1185 static void pr_scroller_timer_set(PixbufRenderer *pr, gint start)
1186 {
1187         if (pr->scroller_id != -1)
1188                 {
1189                 g_source_remove(pr->scroller_id);
1190                 pr->scroller_id = -1;
1191                 }
1192
1193         if (start)
1194                 {
1195                 pr->scroller_id = g_timeout_add(1000 / PR_SCROLLER_UPDATES_PER_SEC,
1196                                                 pr_scroller_update_cb, pr);
1197                 }
1198 }
1199
1200 static void pr_scroller_start(PixbufRenderer *pr, gint x, gint y)
1201 {
1202         if (pr->scroller_overlay == -1)
1203                 {
1204                 GdkPixbuf *pixbuf;
1205                 gint w, h;
1206
1207 #ifdef GQ_BUILD
1208                 pixbuf = pixbuf_inline(PIXBUF_INLINE_SCROLLER);
1209 #else
1210                 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 32, 32);
1211                 gdk_pixbuf_fill(pixbuf, 0x000000ff);
1212 #endif
1213                 w = gdk_pixbuf_get_width(pixbuf);
1214                 h = gdk_pixbuf_get_height(pixbuf);
1215
1216                 pr->scroller_overlay = pixbuf_renderer_overlay_add(pr, pixbuf, x - w / 2, y - h / 2, FALSE, TRUE);
1217                 g_object_unref(pixbuf);
1218                 }
1219
1220         pr->scroller_x = x;
1221         pr->scroller_y = y;
1222         pr->scroller_xpos = x;
1223         pr->scroller_ypos = y;
1224
1225         pr_scroller_timer_set(pr, TRUE);
1226 }
1227
1228 static void pr_scroller_stop(PixbufRenderer *pr)
1229 {
1230         if (pr->scroller_id == -1) return;
1231
1232         pixbuf_renderer_overlay_remove(pr, pr->scroller_overlay);
1233         pr->scroller_overlay = -1;
1234
1235         pr_scroller_timer_set(pr, FALSE);
1236 }
1237
1238 /*
1239  *-------------------------------------------------------------------
1240  * borders
1241  *-------------------------------------------------------------------
1242  */
1243
1244 static void pr_border_draw(PixbufRenderer *pr, gint x, gint y, gint w, gint h)
1245 {
1246         GtkWidget *box;
1247         gint rx, ry, rw, rh;
1248
1249         box = GTK_WIDGET(pr);
1250
1251         if (!box->window) return;
1252
1253         if (!pr->pixbuf && !pr->source_tiles_enabled)
1254                 {
1255                 if (pr_clip_region(x, y, w, h,
1256                                    0, 0,
1257                                    pr->window_width, pr->window_height,
1258                                    &rx, &ry, &rw, &rh))
1259                         {
1260                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1261                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1262                         }
1263                 return;
1264                 }
1265
1266         if (pr->vis_width < pr->window_width)
1267                 {
1268                 if (pr->x_offset > 0 &&
1269                     pr_clip_region(x, y, w, h,
1270                                    0, 0,
1271                                    pr->x_offset, pr->window_height,
1272                                    &rx, &ry, &rw, &rh))
1273                         {
1274                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1275                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1276                         }
1277                 if (pr->window_width - pr->vis_width - pr->x_offset > 0 &&
1278                     pr_clip_region(x, y, w, h,
1279                                    pr->x_offset + pr->vis_width, 0,
1280                                    pr->window_width - pr->vis_width - pr->x_offset, pr->window_height,
1281                                    &rx, &ry, &rw, &rh))
1282                         {
1283                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1284                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1285                         }
1286                 }
1287         if (pr->vis_height < pr->window_height)
1288                 {
1289                 if (pr->y_offset > 0 &&
1290                     pr_clip_region(x, y, w, h,
1291                                    pr->x_offset, 0,
1292                                    pr->vis_width, pr->y_offset,
1293                                    &rx, &ry, &rw, &rh))
1294                         {
1295                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1296                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1297                         }
1298                 if (pr->window_height - pr->vis_height - pr->y_offset > 0 &&
1299                     pr_clip_region(x, y, w, h,
1300                                    pr->x_offset, pr->y_offset + pr->vis_height,
1301                                    pr->vis_width, pr->window_height - pr->vis_height - pr->y_offset,
1302                                    &rx, &ry, &rw, &rh))
1303                         {
1304                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1305                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1306                         }
1307                 }
1308 }
1309
1310 static void pr_border_clear(PixbufRenderer *pr)
1311 {
1312         pr_border_draw(pr, 0, 0, pr->window_width, pr->window_height);
1313 }
1314
1315 void pixbuf_renderer_set_color(PixbufRenderer *pr, GdkColor *color)
1316 {
1317         GtkStyle *style;
1318         GtkWidget *widget;
1319
1320         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1321
1322         widget = GTK_WIDGET(pr);
1323
1324         if (color) {
1325                 GdkColor *slot;
1326
1327                 style = gtk_style_copy(gtk_widget_get_style(widget));
1328                 slot = &style->bg[GTK_STATE_NORMAL];
1329
1330                 slot->red = color->red;
1331                 slot->green = color->green;
1332                 slot->blue = color->blue;
1333                 }
1334         else {
1335                 style = gtk_style_copy(gtk_widget_get_default_style());
1336         }
1337
1338         gtk_widget_set_style(widget, style);
1339
1340         if (GTK_WIDGET_VISIBLE(widget)) pr_border_clear(pr);
1341 }
1342
1343
1344 /*
1345  *-------------------------------------------------------------------
1346  * source tiles
1347  *-------------------------------------------------------------------
1348  */
1349
1350 static void pr_source_tile_free(SourceTile *st)
1351 {
1352         if (!st) return;
1353
1354         if (st->pixbuf) g_object_unref(st->pixbuf);
1355         g_free(st);
1356 }
1357
1358 static void pr_source_tile_free_all(PixbufRenderer *pr)
1359 {
1360         GList *work;
1361
1362         work = pr->source_tiles;
1363         while (work)
1364                 {
1365                 SourceTile *st;
1366
1367                 st = work->data;
1368                 work = work->next;
1369
1370                 pr_source_tile_free(st);
1371                 }
1372
1373         g_list_free(pr->source_tiles);
1374         pr->source_tiles = NULL;
1375 }
1376
1377 static void pr_source_tile_unset(PixbufRenderer *pr)
1378 {
1379         pr_source_tile_free_all(pr);
1380         pr->source_tiles_enabled = FALSE;
1381 }
1382
1383 static gint pr_source_tile_visible(PixbufRenderer *pr, SourceTile *st)
1384 {
1385         gint x1, y1, x2, y2;
1386
1387         if (!st) return FALSE;
1388
1389         x1 = (pr->x_scroll / pr->tile_width) * pr->tile_width;
1390         y1 = (pr->y_scroll / pr->tile_height) * pr->tile_height;
1391         x2 = ((pr->x_scroll + pr->vis_width) / pr->tile_width) * pr->tile_width + pr->tile_width;
1392         y2 = ((pr->y_scroll + pr->vis_height) / pr->tile_height) * pr->tile_height + pr->tile_height;
1393
1394         return !((double)st->x * pr->scale > (double)x2 ||
1395                  (double)(st->x + pr->source_tile_width) * pr->scale < (double)x1 ||
1396                  (double)st->y * pr->scale > (double)y2 ||
1397                  (double)(st->y + pr->source_tile_height) * pr->scale < (double)y1);
1398 }
1399
1400 static SourceTile *pr_source_tile_new(PixbufRenderer *pr, gint x, gint y)
1401 {
1402         SourceTile *st = NULL;
1403         gint count;
1404
1405         g_return_val_if_fail(pr->source_tile_width >= 1 && pr->source_tile_height >= 1, NULL);
1406
1407         if (pr->source_tiles_cache_size < 4) pr->source_tiles_cache_size = 4;
1408
1409         count = g_list_length(pr->source_tiles);
1410         if (count >= pr->source_tiles_cache_size)
1411                 {
1412                 GList *work;
1413
1414                 work = g_list_last(pr->source_tiles);
1415                 while (work && count >= pr->source_tiles_cache_size)
1416                         {
1417                         SourceTile *needle;
1418
1419                         needle = work->data;
1420                         work = work->prev;
1421
1422                         if (!pr_source_tile_visible(pr, needle))
1423                                 {
1424                                 pr->source_tiles = g_list_remove(pr->source_tiles, needle);
1425
1426                                 if (pr->func_tile_dispose)
1427                                         {
1428                                         pr->func_tile_dispose(pr, needle->x, needle->y,
1429                                                               pr->source_tile_width, pr->source_tile_height,
1430                                                               needle->pixbuf, pr->func_tile_data);
1431                                         }
1432
1433                                 if (!st)
1434                                         {
1435                                         st = needle;
1436                                         }
1437                                 else
1438                                         {
1439                                         pr_source_tile_free(needle);
1440                                         }
1441
1442                                 count--;
1443                                 }
1444                         }
1445                 }
1446
1447         if (!st)
1448                 {
1449                 st = g_new0(SourceTile, 1);
1450                 st->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
1451                                             pr->source_tile_width, pr->source_tile_height);
1452                 }
1453
1454         st->x = (x / pr->source_tile_width) * pr->source_tile_width;
1455         st->y = (y / pr->source_tile_height) * pr->source_tile_height;
1456         st->blank = TRUE;
1457
1458         pr->source_tiles = g_list_prepend(pr->source_tiles, st);
1459
1460         return st;
1461 }
1462
1463 static SourceTile *pr_source_tile_request(PixbufRenderer *pr, gint x, gint y)
1464 {
1465         SourceTile *st;
1466
1467         st = pr_source_tile_new(pr, x, y);
1468         if (!st) return NULL;
1469
1470         if (pr->func_tile_request &&
1471             pr->func_tile_request(pr, st->x, st->y,
1472                                    pr->source_tile_width, pr->source_tile_height, st->pixbuf, pr->func_tile_data))
1473                 {
1474                 st->blank = FALSE;
1475                 }
1476
1477         pr_tile_invalidate_region(pr, st->x * pr->scale, st->y * pr->scale,
1478                                   pr->source_tile_width * pr->scale, pr->source_tile_height * pr->scale);
1479
1480         return st;
1481 }
1482
1483 static SourceTile *pr_source_tile_find(PixbufRenderer *pr, gint x, gint y)
1484 {
1485         GList *work;
1486
1487         work = pr->source_tiles;
1488         while (work)
1489                 {
1490                 SourceTile *st = work->data;
1491
1492                 if (x >= st->x && x < st->x + pr->source_tile_width &&
1493                     y >= st->y && y < st->y + pr->source_tile_height)
1494                         {
1495                         if (work != pr->source_tiles)
1496                                 {
1497                                 pr->source_tiles = g_list_remove_link(pr->source_tiles, work);
1498                                 pr->source_tiles = g_list_concat(work, pr->source_tiles);
1499                                 }
1500                         return st;
1501                         }
1502
1503                 work = work->next;
1504                 }
1505
1506         return NULL;
1507 }
1508
1509 static GList *pr_source_tile_compute_region(PixbufRenderer *pr, gint x, gint y, gint w, gint h, gint request)
1510 {
1511         gint x1, y1;
1512         GList *list = NULL;
1513         gint sx, sy;
1514
1515         if (x < 0) x = 0;
1516         if (y < 0) y = 0;
1517         if (w > pr->image_width) w = pr->image_width;
1518         if (h > pr->image_height) h = pr->image_height;
1519
1520         sx = (x / pr->source_tile_width) * pr->source_tile_width;
1521         sy = (y / pr->source_tile_height) * pr->source_tile_height;
1522
1523         for (x1 = sx; x1 < x + w; x1+= pr->source_tile_width)
1524                 {
1525                 for (y1 = sy; y1 < y + h; y1 += pr->source_tile_height)
1526                         {
1527                         SourceTile *st;
1528
1529                         st = pr_source_tile_find(pr, x1, y1);
1530                         if (!st && request) st = pr_source_tile_request(pr, x1, y1);
1531
1532                         if (st) list = g_list_prepend(list, st);
1533                         }
1534                 }
1535
1536         return g_list_reverse(list);
1537 }
1538
1539 static void pr_source_tile_changed(PixbufRenderer *pr, gint x, gint y, gint width, gint height)
1540 {
1541         GList *work;
1542
1543         if (width < 1 || height < 1) return;
1544
1545         work = pr->source_tiles;
1546         while (work)
1547                 {
1548                 SourceTile *st;
1549                 gint rx, ry, rw, rh;
1550
1551                 st = work->data;
1552                 work = work->next;
1553
1554                 if (pr_clip_region(st->x, st->y, pr->source_tile_width, pr->source_tile_height,
1555                                    x, y, width, height,
1556                                    &rx, &ry, &rw, &rh))
1557                         {
1558                         GdkPixbuf *pixbuf;
1559
1560                         pixbuf = gdk_pixbuf_new_subpixbuf(st->pixbuf, rx - st->x, ry - st->y, rw, rh);
1561                         if (pr->func_tile_request &&
1562                             pr->func_tile_request(pr, rx, ry, rw, rh, pixbuf, pr->func_tile_data))
1563                                 {
1564                                 pr_tile_invalidate_region(pr, rx * pr->scale, ry * pr->scale,
1565                                                               rw * pr->scale, rh * pr->scale);
1566                                 }
1567                         g_object_unref(pixbuf);
1568                         }
1569                 }
1570 }
1571
1572 static gint pr_source_tile_render(PixbufRenderer *pr, ImageTile *it,
1573                                   gint x, gint y, gint w, gint h,
1574                                   gint new_data, gint fast)
1575 {
1576         GtkWidget *box;
1577         GList *list;
1578         GList *work;
1579         gint draw = FALSE;
1580
1581         box = GTK_WIDGET(pr);
1582
1583         if (pr->zoom == 1.0 || pr->scale == 1.0)
1584                 {
1585                 list = pr_source_tile_compute_region(pr, it->x + x, it->y + y, w, h, TRUE);
1586                 work = list;
1587                 while (work)
1588                         {
1589                         SourceTile *st;
1590                         gint rx, ry, rw, rh;
1591
1592                         st = work->data;
1593                         work = work->next;
1594
1595                         if (pr_clip_region(st->x, st->y, pr->source_tile_width, pr->source_tile_height,
1596                                            it->x + x, it->y + y, w, h,
1597                                            &rx, &ry, &rw, &rh))
1598                                 {
1599                                 if (st->blank)
1600                                         {
1601                                         gdk_draw_rectangle(it->pixmap, box->style->black_gc, TRUE,
1602                                                            rx - st->x, ry - st->y, rw, rh);
1603                                         }
1604                                 else /* (pr->zoom == 1.0 || pr->scale == 1.0) */
1605                                         {
1606                                         gdk_draw_pixbuf(it->pixmap,
1607                                                         box->style->fg_gc[GTK_WIDGET_STATE(box)],
1608                                                         st->pixbuf,
1609                                                         rx - st->x, ry - st->y,
1610                                                         rx - it->x, ry - it->y,
1611                                                         rw, rh,
1612                                                         pr->dither_quality, rx, ry);
1613                                         }
1614                                 }
1615                         }
1616                 }
1617         else
1618                 {
1619                 double scale_x, scale_y;
1620                 gint sx, sy, sw, sh;
1621
1622                 if (pr->image_width == 0 || pr->image_height == 0) return FALSE;
1623                 scale_x = (double)pr->width / pr->image_width;
1624                 scale_y = (double)pr->height / pr->image_height;
1625
1626                 sx = (double)(it->x + x) / scale_x;
1627                 sy = (double)(it->y + y) / scale_y;
1628                 sw = (double)w / scale_x;
1629                 sh = (double)h / scale_y;
1630
1631                 if (pr->width < PR_MIN_SCALE_SIZE || pr->height < PR_MIN_SCALE_SIZE) fast = TRUE;
1632
1633 #if 0
1634                 /* draws red over draw region, to check for leaks (regions not filled) */
1635                 pixbuf_set_rect_fill(it->pixbuf, x, y, w, h, 255, 0, 0, 255);
1636 #endif
1637
1638                 list = pr_source_tile_compute_region(pr, sx, sy, sw, sh, TRUE);
1639                 work = list;
1640                 while (work)
1641                         {
1642                         SourceTile *st;
1643                         gint rx, ry, rw, rh;
1644                         gint stx, sty, stw, sth;
1645
1646                         st = work->data;
1647                         work = work->next;
1648
1649                         stx = floor((double)st->x * scale_x);
1650                         sty = floor((double)st->y * scale_y);
1651                         stw = ceil ((double)(st->x + pr->source_tile_width) * scale_x) - stx;
1652                         sth = ceil ((double)(st->y + pr->source_tile_height) * scale_y) - sty;
1653
1654                         if (pr_clip_region(stx, sty, stw, sth,
1655                                            it->x + x, it->y + y, w, h,
1656                                            &rx, &ry, &rw, &rh))
1657                                 {
1658                                 if (st->blank)
1659                                         {
1660                                         gdk_draw_rectangle(it->pixmap, box->style->black_gc, TRUE,
1661                                                            rx - st->x, ry - st->y, rw, rh);
1662                                         }
1663                                 else
1664                                         {
1665                                         double offset_x;
1666                                         double offset_y;
1667
1668                                         /* may need to use unfloored stx,sty values here */
1669                                         offset_x = (double)(stx - it->x);
1670                                         offset_y = (double)(sty - it->y);
1671
1672                                         gdk_pixbuf_scale(st->pixbuf, it->pixbuf, rx - it->x, ry - it->y, rw, rh,
1673                                                  (double) 0.0 + offset_x,
1674                                                  (double) 0.0 + offset_y,
1675                                                  scale_x, scale_y,
1676                                                  (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality);
1677                                         draw = TRUE;
1678                                         }
1679                                 }
1680                         }
1681                 }
1682
1683         g_list_free(list);
1684
1685         return draw;
1686 }
1687
1688 void pixbuf_renderer_set_tiles(PixbufRenderer *pr, gint width, gint height,
1689                                gint tile_width, gint tile_height, gint cache_size,
1690                                PixbufRendererTileRequestFunc func_request,
1691                                PixbufRendererTileDisposeFunc func_dispose,
1692                                gpointer user_data,
1693                                gdouble zoom)
1694 {
1695         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1696         g_return_if_fail(tile_width >= 32 && tile_width >= 32);
1697         g_return_if_fail(width >= 32 && height > 32);
1698         g_return_if_fail(func_request != NULL);
1699
1700         if (pr->pixbuf) g_object_unref(pr->pixbuf);
1701         pr->pixbuf = NULL;
1702
1703         pr_source_tile_unset(pr);
1704
1705         if (cache_size < 4) cache_size = 4;
1706
1707         pr->source_tiles_enabled = TRUE;
1708         pr->source_tiles_cache_size = cache_size;
1709         pr->source_tile_width = tile_width;
1710         pr->source_tile_height = tile_height;
1711
1712         pr->image_width = width;
1713         pr->image_height = height;
1714
1715         pr->func_tile_request = func_request;
1716         pr->func_tile_dispose = func_dispose;
1717         pr->func_tile_data = user_data;
1718
1719         pr_zoom_sync(pr, zoom, TRUE, TRUE, FALSE, 0, 0);
1720         pr_redraw(pr, TRUE);
1721 }
1722
1723 void pixbuf_renderer_set_tiles_size(PixbufRenderer *pr, gint width, gint height)
1724 {
1725         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1726         g_return_if_fail(width >= 32 && height > 32);
1727
1728         if (!pr->source_tiles_enabled) return;
1729         if (pr->image_width == width && pr->image_height == height) return;
1730
1731         pr->image_width = width;
1732         pr->image_height = height;
1733
1734         pr_zoom_sync(pr, pr->zoom, TRUE, FALSE, FALSE, 0, 0);
1735 }
1736
1737 gint pixbuf_renderer_get_tiles(PixbufRenderer *pr)
1738 {
1739         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
1740
1741         return pr->source_tiles_enabled;
1742 }
1743
1744 static void pr_zoom_adjust_real(PixbufRenderer *pr, gdouble increment,
1745                                 gint center_point, gint x, gint y)
1746 {
1747         gdouble zoom = pr->zoom;
1748
1749         if (increment == 0.0) return;
1750
1751         if (zoom == 0.0)
1752                 {
1753                 if (pr->scale < 1.0)
1754                         {
1755                         zoom = 0.0 - 1.0 / pr->scale;
1756                         }
1757                 else
1758                         {
1759                         zoom = pr->scale;
1760                         }
1761                 }
1762
1763         if (increment < 0.0)
1764                 {
1765                 if (zoom >= 1.0 && zoom + increment < 1.0)
1766                         {
1767                         zoom = zoom + increment - 2.0;
1768                         }
1769                 else
1770                         {
1771                         zoom = zoom + increment;
1772                         }
1773                 }
1774         else
1775                 {
1776                 if (zoom <= -1.0 && zoom + increment > -1.0)
1777                         {
1778                         zoom = zoom + increment + 2.0;
1779                         }
1780                 else
1781                         {
1782                         zoom = zoom + increment;
1783                         }
1784                 }
1785
1786         pr_zoom_sync(pr, zoom, FALSE, FALSE, center_point, x, y);
1787 }
1788
1789 /*
1790  *-------------------------------------------------------------------
1791  * display tiles
1792  *-------------------------------------------------------------------
1793  */
1794
1795 static ImageTile *pr_tile_new(gint x, gint y, gint width, gint height)
1796 {
1797         ImageTile *it;
1798
1799         it = g_new0(ImageTile, 1);
1800
1801         it->x = x;
1802         it->y = y;
1803         it->w = width;
1804         it->h = height;
1805
1806         it->render_done = TILE_RENDER_NONE;
1807
1808         return it;
1809 }
1810
1811 static void pr_tile_free(ImageTile *it)
1812 {
1813         if (!it) return;
1814
1815         if (it->pixbuf) gdk_pixbuf_unref(it->pixbuf);
1816         if (it->pixmap) g_object_unref(it->pixmap);
1817
1818         g_free(it);
1819 }
1820
1821 static void pr_tile_free_all(PixbufRenderer *pr)
1822 {
1823         GList *work;
1824
1825         work = pr->tiles;
1826         while (work)
1827                 {
1828                 ImageTile *it;
1829
1830                 it = work->data;
1831                 work = work->next;
1832
1833                 pr_tile_free(it);
1834                 }
1835
1836         g_list_free(pr->tiles);
1837         pr->tiles = NULL;
1838         pr->tile_cache_size = 0;
1839 }
1840
1841 static ImageTile *pr_tile_add(PixbufRenderer *pr, gint x, gint y)
1842 {
1843         ImageTile *it;
1844
1845         it = pr_tile_new(x, y, pr->tile_width, pr->tile_height);
1846
1847         if (it->x + it->w > pr->width) it->w = pr->width - it->x;
1848         if (it->y + it->h > pr->height) it->h = pr->height - it->y;
1849
1850         pr->tiles = g_list_prepend(pr->tiles, it);
1851         pr->tile_cache_size += it->size;
1852
1853         return it;
1854 }
1855
1856 static void pr_tile_remove(PixbufRenderer *pr, ImageTile *it)
1857 {
1858         if (it->qd)
1859                 {
1860                 QueueData *qd = it->qd;
1861
1862                 it->qd = NULL;
1863                 pr->draw_queue = g_list_remove(pr->draw_queue, qd);
1864                 g_free(qd);
1865                 }
1866
1867         if (it->qd2)
1868                 {
1869                 QueueData *qd = it->qd2;
1870
1871                 it->qd2 = NULL;
1872                 pr->draw_queue_2pass = g_list_remove(pr->draw_queue_2pass, qd);
1873                 g_free(qd);
1874                 }
1875
1876         pr->tiles = g_list_remove(pr->tiles, it);
1877         pr->tile_cache_size -= it->size;
1878
1879         pr_tile_free(it);
1880 }
1881
1882 static void pr_tile_free_space(PixbufRenderer *pr, guint space, ImageTile *it)
1883 {
1884         GList *work;
1885         gint tile_max;
1886
1887         work = g_list_last(pr->tiles);
1888
1889         if (pr->source_tiles_enabled && pr->scale < 1.0)
1890                 {
1891                 gint tiles;
1892
1893                 tiles = (pr->vis_width / pr->tile_width + 1) * (pr->vis_height / pr->tile_height + 1);
1894                 tile_max = MAX(tiles * pr->tile_width * pr->tile_height * 3,
1895                                (gint)((double)pr->tile_cache_max * 1048576.0 * pr->scale));
1896                 }
1897         else
1898                 {
1899                 tile_max = pr->tile_cache_max * 1048576;
1900                 }
1901
1902         while (work && pr->tile_cache_size + space > tile_max)
1903                 {
1904                 ImageTile *needle;
1905
1906                 needle = work->data;
1907                 work = work->prev;
1908                 if (needle != it &&
1909                     ((!needle->qd && !needle->qd2) || !pr_tile_is_visible(pr, needle))) pr_tile_remove(pr, needle);
1910                 }
1911 }
1912
1913 static void pr_tile_invalidate_all(PixbufRenderer *pr)
1914 {
1915         GList *work;
1916
1917         work = pr->tiles;
1918         while (work)
1919                 {
1920                 ImageTile *it;
1921
1922                 it = work->data;
1923                 work = work->next;
1924
1925                 it->render_done = TILE_RENDER_NONE;
1926                 it->render_todo = TILE_RENDER_ALL;
1927                 it->blank = FALSE;
1928
1929                 it->w = MIN(pr->tile_width, pr->width - it->x);
1930                 it->h = MIN(pr->tile_height, pr->height - it->y);
1931                 }
1932 }
1933
1934 static void pr_tile_invalidate_region(PixbufRenderer *pr, gint x, gint y, gint w, gint h)
1935 {
1936         gint x1, x2;
1937         gint y1, y2;
1938         GList *work;
1939
1940         x1 = (gint)floor(x / pr->tile_width) * pr->tile_width;
1941         x2 = (gint)ceil((x + w) / pr->tile_width) * pr->tile_width;
1942
1943         y1 = (gint)floor(y / pr->tile_height) * pr->tile_height;
1944         y2 = (gint)ceil((y + h) / pr->tile_height) * pr->tile_height;
1945
1946         work = pr->tiles;
1947         while (work)
1948                 {
1949                 ImageTile *it;
1950
1951                 it = work->data;
1952                 work = work->next;
1953
1954                 if (it->x < x2 && it->x + it->w > x1 &&
1955                     it->y < y2 && it->y + it->h > y1)
1956                         {
1957                         it->render_done = TILE_RENDER_NONE;
1958                         it->render_todo = TILE_RENDER_ALL;
1959                         }
1960                 }
1961 }
1962
1963 static ImageTile *pr_tile_get(PixbufRenderer *pr, gint x, gint y, gint only_existing)
1964 {
1965         GList *work;
1966
1967         work = pr->tiles;
1968         while (work)
1969                 {
1970                 ImageTile *it;
1971
1972                 it = work->data;
1973                 if (it->x == x && it->y == y)
1974                         {
1975                         pr->tiles = g_list_delete_link(pr->tiles, work);
1976                         pr->tiles = g_list_prepend(pr->tiles, it);
1977                         return it;
1978                         }
1979
1980                 work = work->next;
1981                 }
1982
1983         if (only_existing) return NULL;
1984
1985         return pr_tile_add(pr, x, y);
1986 }
1987
1988 static void pr_tile_prepare(PixbufRenderer *pr, ImageTile *it)
1989 {
1990         if (!it->pixmap)
1991                 {
1992                 GdkPixmap *pixmap;
1993                 guint size;
1994
1995                 pixmap = gdk_pixmap_new(((GtkWidget *)pr)->window, pr->tile_width, pr->tile_height, -1);
1996
1997                 size = pixmap_calc_size(pixmap);
1998                 pr_tile_free_space(pr, size, it);
1999
2000                 it->pixmap = pixmap;
2001                 it->size += size;
2002                 pr->tile_cache_size += size;
2003                 }
2004         
2005         if ((pr->zoom != 1.0 || pr->source_tiles_enabled || (pr->pixbuf && gdk_pixbuf_get_has_alpha(pr->pixbuf)) ) &&
2006             !it->pixbuf)
2007                 {
2008                 GdkPixbuf *pixbuf;
2009                 guint size;
2010
2011                 if (pr->pixbuf)
2012                         {
2013                         pixbuf = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(pr->pixbuf),
2014                                                 gdk_pixbuf_get_has_alpha(pr->pixbuf),
2015                                                 gdk_pixbuf_get_bits_per_sample(pr->pixbuf),
2016                                                 pr->tile_width, pr->tile_height);
2017                         }
2018                 else
2019                         {
2020                         pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, pr->tile_width, pr->tile_height);
2021                         }
2022
2023                 size = gdk_pixbuf_get_rowstride(pixbuf) * pr->tile_height;
2024                 pr_tile_free_space(pr, size, it);
2025
2026                 it->pixbuf = pixbuf;
2027                 it->size += size;
2028                 pr->tile_cache_size += size;
2029                 }
2030 }
2031
2032 /*
2033  *-------------------------------------------------------------------
2034  * drawing
2035  *-------------------------------------------------------------------
2036  */
2037
2038 static void pr_tile_render(PixbufRenderer *pr, ImageTile *it,
2039                            gint x, gint y, gint w, gint h,
2040                            gint new_data, gint fast)
2041 {
2042         GtkWidget *box;
2043         gint has_alpha;
2044         gint draw = FALSE;
2045
2046         if (it->render_todo == TILE_RENDER_NONE && it->pixmap && !new_data) return;
2047
2048         if (it->render_done != TILE_RENDER_ALL)
2049                 {
2050                 x = 0;
2051                 y = 0;
2052                 w = it->w;
2053                 h = it->h;
2054                 if (!fast) it->render_done = TILE_RENDER_ALL;
2055                 }
2056         else if (it->render_todo != TILE_RENDER_AREA)
2057                 {
2058                 if (!fast) it->render_todo = TILE_RENDER_NONE;
2059                 return;
2060                 }
2061
2062         if (!fast) it->render_todo = TILE_RENDER_NONE;
2063
2064         if (new_data) it->blank = FALSE;
2065
2066         pr_tile_prepare(pr, it);
2067         has_alpha = (pr->pixbuf && gdk_pixbuf_get_has_alpha(pr->pixbuf));
2068
2069         box = GTK_WIDGET(pr);
2070
2071         /* FIXME checker colors for alpha should be configurable,
2072          * also should be drawn for blank = TRUE
2073          */
2074
2075         if (it->blank)
2076                 {
2077                 /* no data, do fast rect fill */
2078                 gdk_draw_rectangle(it->pixmap, box->style->black_gc, TRUE,
2079                                    0, 0, it->w, it->h);
2080                 }
2081         else if (pr->source_tiles_enabled)
2082                 {
2083                 draw = pr_source_tile_render(pr, it, x, y, w, h, new_data, fast);
2084                 }
2085         else if (pr->zoom == 1.0 || pr->scale == 1.0)
2086                 {
2087                 if (has_alpha)
2088                         {
2089                         gdk_pixbuf_composite_color(pr->pixbuf, it->pixbuf, x, y, w, h,
2090                                          (double) 0.0 - it->x,
2091                                          (double) 0.0 - it->y,
2092                                          1.0, 1.0, GDK_INTERP_NEAREST,
2093                                          255, it->x + x, it->y + y,
2094                                          PR_ALPHA_CHECK_SIZE, PR_ALPHA_CHECK1, PR_ALPHA_CHECK2);
2095                         draw = TRUE;
2096                         }
2097                 else
2098                         {
2099                         /* faster, simple */
2100                         gdk_draw_pixbuf(it->pixmap,
2101                                         box->style->fg_gc[GTK_WIDGET_STATE(box)],
2102                                         pr->pixbuf,
2103                                         it->x + x, it->y + y,
2104                                         x, y,
2105                                         w, h,
2106                                         pr->dither_quality, it->x + x, it->y + y);
2107                         }
2108                 }
2109         else
2110                 {
2111                 double scale_x, scale_y;
2112
2113                 if (pr->image_width == 0 || pr->image_height == 0) return;
2114                 scale_x = (double)pr->width / pr->image_width;
2115                 scale_y = (double)pr->height / pr->image_height;
2116
2117                 /* HACK: The pixbuf scalers get kinda buggy(crash) with extremely
2118                  * small sizes for anything but GDK_INTERP_NEAREST
2119                  */
2120                 if (pr->width < PR_MIN_SCALE_SIZE || pr->height < PR_MIN_SCALE_SIZE) fast = TRUE;
2121
2122                 if (!has_alpha)
2123                         {
2124                         gdk_pixbuf_scale(pr->pixbuf, it->pixbuf, x, y, w, h,
2125                                          (double) 0.0 - it->x,
2126                                          (double) 0.0 - it->y,
2127                                          scale_x, scale_y,
2128                                          (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality);
2129                         }
2130                 else
2131                         {
2132                         gdk_pixbuf_composite_color(pr->pixbuf, it->pixbuf, x, y, w, h,
2133                                          (double) 0.0 - it->x,
2134                                          (double) 0.0 - it->y,
2135                                          scale_x, scale_y,
2136                                          (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality,
2137                                          255, it->x + x, it->y + y,
2138                                          PR_ALPHA_CHECK_SIZE, PR_ALPHA_CHECK1, PR_ALPHA_CHECK2);
2139                         }
2140                 draw = TRUE;
2141                 }
2142
2143         if (draw && it->pixbuf && !it->blank)
2144                 {
2145                 gdk_draw_pixbuf(it->pixmap,
2146                                 box->style->fg_gc[GTK_WIDGET_STATE(box)],
2147                                 it->pixbuf,
2148                                 x, y,
2149                                 x, y,
2150                                 w, h,
2151                                 pr->dither_quality, it->x + x, it->y + y);
2152                 }
2153
2154 #if 0
2155         /* enable this line for debugging the edges of tiles */
2156         gdk_draw_rectangle(it->pixmap, box->style->white_gc,
2157                            FALSE, 0, 0, it->w, it->h);
2158 #endif
2159 }
2160
2161
2162 static void pr_tile_expose(PixbufRenderer *pr, ImageTile *it,
2163                            gint x, gint y, gint w, gint h,
2164                            gint new_data, gint fast)
2165 {
2166         GtkWidget *box;
2167
2168         pr_tile_render(pr, it, x, y, w, h, new_data, fast);
2169
2170         box = GTK_WIDGET(pr);
2171
2172         gdk_draw_drawable(box->window, box->style->fg_gc[GTK_WIDGET_STATE(box)],
2173                           it->pixmap, x, y,
2174                           pr->x_offset + (it->x - pr->x_scroll) + x, pr->y_offset + (it->y - pr->y_scroll) + y, w, h);
2175
2176         if (pr->overlay_list)
2177                 {
2178                 pr_overlay_draw(pr, pr->x_offset + (it->x - pr->x_scroll) + x,
2179                                 pr->y_offset + (it->y - pr->y_scroll) + y,
2180                                 w, h,
2181                                 it);
2182                 }
2183 }
2184
2185
2186 static gint pr_tile_is_visible(PixbufRenderer *pr, ImageTile *it)
2187 {
2188         return (it->x + it->w >= pr->x_scroll && it->x < pr->x_scroll + pr->vis_width &&
2189                 it->y + it->h >= pr->y_scroll && it->y < pr->y_scroll + pr->vis_height);
2190 }
2191
2192 /*
2193  *-------------------------------------------------------------------
2194  * draw queue
2195  *-------------------------------------------------------------------
2196  */
2197
2198 static gint pr_queue_draw_idle_cb(gpointer data)
2199 {
2200         PixbufRenderer *pr = data;
2201         QueueData *qd;
2202         gint fast;
2203
2204         if ((!pr->pixbuf && !pr->source_tiles_enabled) ||
2205             (!pr->draw_queue && !pr->draw_queue_2pass) ||
2206             pr->draw_idle_id == -1)
2207                 {
2208                 pr_render_complete_signal(pr);
2209
2210                 pr->draw_idle_id = -1;
2211                 return FALSE;
2212                 }
2213
2214         if (pr->draw_queue)
2215                 {
2216                 qd = pr->draw_queue->data;
2217                 fast = (pr->zoom_2pass && pr->zoom_quality != GDK_INTERP_NEAREST && pr->scale != 1.0);
2218                 }
2219         else
2220                 {
2221                 if (pr->loading)
2222                         {
2223                         /* still loading, wait till done (also drops the higher priority) */
2224
2225                         pr->draw_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
2226                                                            pr_queue_draw_idle_cb, pr, NULL);
2227                         pr->draw_idle_high = FALSE;
2228                         return FALSE;
2229                         }
2230
2231                 qd = pr->draw_queue_2pass->data;
2232                 fast = FALSE;
2233                 }
2234
2235         if (GTK_WIDGET_REALIZED(pr))
2236                 {
2237                 if (pr_tile_is_visible(pr, qd->it))
2238                         {
2239                         pr_tile_expose(pr, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
2240                         }
2241                 else if (qd->new_data)
2242                         {
2243                         /* if new pixel data, and we already have a pixmap, update the tile */
2244                         qd->it->blank = FALSE;
2245                         if (qd->it->pixmap && qd->it->render_done == TILE_RENDER_ALL)
2246                                 {
2247                                 pr_tile_render(pr, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
2248                                 }
2249                         }
2250                 }
2251
2252         if (pr->draw_queue)
2253                 {
2254                 qd->it->qd = NULL;
2255                 pr->draw_queue = g_list_remove(pr->draw_queue, qd);
2256                 if (fast)
2257                         {
2258                         if (qd->it->qd2)
2259                                 {
2260                                 pr_queue_merge(qd->it->qd2, qd);
2261                                 g_free(qd);
2262                                 }
2263                         else
2264                                 {
2265                                 qd->it->qd2 = qd;
2266                                 pr->draw_queue_2pass = g_list_append(pr->draw_queue_2pass, qd);
2267                                 }
2268                         }
2269                 else
2270                         {
2271                         g_free(qd);
2272                         }
2273                 }
2274         else
2275                 {
2276                 qd->it->qd2 = NULL;
2277                 pr->draw_queue_2pass = g_list_remove(pr->draw_queue_2pass, qd);
2278                 g_free(qd);
2279                 }
2280
2281         if (!pr->draw_queue && !pr->draw_queue_2pass)
2282                 {
2283                 pr_render_complete_signal(pr);
2284
2285                 pr->draw_idle_id = -1;
2286                 return FALSE;
2287                 }
2288
2289         return TRUE;
2290 }
2291
2292 static void pr_queue_list_free(GList *list)
2293 {
2294         GList *work;
2295
2296         work = list;
2297         while (work)
2298                 {
2299                 QueueData *qd;
2300
2301                 qd = work->data;
2302                 work = work->next;
2303
2304                 qd->it->qd = NULL;
2305                 qd->it->qd2 = NULL;
2306                 g_free(qd);
2307                 }
2308
2309         g_list_free(list);
2310 }
2311
2312 static void pr_queue_clear(PixbufRenderer *pr)
2313 {
2314         pr_queue_list_free(pr->draw_queue);
2315         pr->draw_queue = NULL;
2316
2317         pr_queue_list_free(pr->draw_queue_2pass);
2318         pr->draw_queue_2pass = NULL;
2319
2320         if (pr->draw_idle_id != -1) g_source_remove(pr->draw_idle_id);
2321         pr->draw_idle_id = -1;
2322 }
2323
2324 static void pr_queue_merge(QueueData *parent, QueueData *qd)
2325 {
2326         if (parent->x + parent->w < qd->x + qd->w)
2327                 {
2328                 parent->w += (qd->x + qd->w) - (parent->x + parent->w);
2329                 }
2330         if (parent->x > qd->x)
2331                 {
2332                 parent->w += parent->x - qd->x;
2333                 parent->x = qd->x;
2334                 }
2335
2336         if (parent->y + parent->h < qd->y + qd->h)
2337                 {
2338                 parent->h += (qd->y + qd->h) - (parent->y + parent->h);
2339                 }
2340         if (parent->y > qd->y)
2341                 {
2342                 parent->h += parent->y - qd->y;
2343                 parent->y = qd->y;
2344                 }
2345
2346         parent->new_data |= qd->new_data;
2347 }
2348
2349 static gint pr_clamp_to_visible(PixbufRenderer *pr, gint *x, gint *y, gint *w, gint *h)
2350 {
2351         gint nx, ny;
2352         gint nw, nh;
2353         gint vx, vy;
2354         gint vw, vh;
2355
2356         vw = pr->vis_width;
2357         vh = pr->vis_height;
2358
2359         vx = pr->x_scroll;
2360         vy = pr->y_scroll;
2361
2362         if (*x + *w < vx || *x > vx + vw || *y + *h < vy || *y > vy + vh) return FALSE;
2363
2364         /* now clamp it */
2365         nx = CLAMP(*x, vx, vx + vw);
2366         nw = CLAMP(*w - (nx - *x), 1, vw);
2367
2368         ny = CLAMP(*y, vy, vy + vh);
2369         nh = CLAMP(*h - (ny - *y), 1, vh);
2370
2371         *x = nx;
2372         *y = ny;
2373         *w = nw;
2374         *h = nh;
2375
2376         return TRUE;
2377 }
2378
2379 static gint pr_queue_to_tiles(PixbufRenderer *pr, gint x, gint y, gint w, gint h,
2380                               gint clamp, ImageTileRenderType render, gint new_data, gint only_existing)
2381 {
2382         gint i, j;
2383         gint x1, x2;
2384         gint y1, y2;
2385
2386         if (clamp && !pr_clamp_to_visible(pr, &x, &y, &w, &h)) return FALSE;
2387
2388         x1 = (gint)floor(x / pr->tile_width) * pr->tile_width;
2389         x2 = (gint)ceil((x + w) / pr->tile_width) * pr->tile_width;
2390
2391         y1 = (gint)floor(y / pr->tile_height) * pr->tile_height;
2392         y2 = (gint)ceil((y + h) / pr->tile_height) * pr->tile_height;
2393
2394         for (j = y1; j <= y2; j += pr->tile_height)
2395                 {
2396                 for (i = x1; i <= x2; i += pr->tile_width)
2397                         {
2398                         ImageTile *it;
2399
2400                         it = pr_tile_get(pr, i, j,
2401                                          (only_existing &&
2402                                           (i + pr->tile_width < pr->x_scroll ||
2403                                            i > pr->x_scroll + pr->vis_width ||
2404                                            j + pr->tile_height < pr->y_scroll ||
2405                                            j > pr->y_scroll + pr->vis_height)));
2406                         if (it)
2407                                 {
2408                                 QueueData *qd;
2409
2410                                 if ((render == TILE_RENDER_ALL && it->render_done != TILE_RENDER_ALL) ||
2411                                     (render == TILE_RENDER_AREA && it->render_todo != TILE_RENDER_ALL))
2412                                         {
2413                                         it->render_todo = render;
2414                                         }
2415
2416                                 qd = g_new(QueueData, 1);
2417                                 qd->it = it;
2418                                 qd->new_data = new_data;
2419
2420                                 if (i < x)
2421                                         {
2422                                         qd->x = x - i;
2423                                         }
2424                                 else
2425                                         {
2426                                         qd->x = 0;
2427                                         }
2428                                 qd->w = x + w - i - qd->x;
2429                                 if (qd->x + qd->w > pr->tile_width) qd->w = pr->tile_width - qd->x;
2430
2431                                 if (j < y)
2432                                         {
2433                                         qd->y = y - j;
2434                                         }
2435                                 else
2436                                         {
2437                                         qd->y = 0;
2438                                         }
2439                                 qd->h = y + h - j - qd->y;
2440                                 if (qd->y + qd->h > pr->tile_height) qd->h = pr->tile_height - qd->y;
2441
2442                                 if (qd->w < 1 || qd->h < 1)
2443                                         {
2444                                         g_free(qd);
2445                                         }
2446                                 else if (it->qd)
2447                                         {
2448                                         pr_queue_merge(it->qd, qd);
2449                                         g_free(qd);
2450                                         }
2451                                 else
2452                                         {
2453                                         it->qd = qd;
2454                                         pr->draw_queue = g_list_append(pr->draw_queue, qd);
2455                                         }
2456                                 }
2457                         }
2458                 }
2459
2460         return TRUE;
2461 }
2462
2463 static void pr_queue(PixbufRenderer *pr, gint x, gint y, gint w, gint h,
2464                      gint clamp, ImageTileRenderType render, gint new_data, gint only_existing)
2465 {
2466         gint nx, ny;
2467
2468         nx = CLAMP(x, 0, pr->width - 1);
2469         ny = CLAMP(y, 0, pr->height - 1);
2470         w -= (nx - x);
2471         h -= (ny - y);
2472         w = CLAMP(w, 0, pr->width - nx);
2473         h = CLAMP(h, 0, pr->height - ny);
2474         if (w < 1 || h < 1) return;
2475
2476         if (pr_queue_to_tiles(pr, nx, ny, w, h, clamp, render, new_data, only_existing) &&
2477             ((!pr->draw_queue && !pr->draw_queue_2pass) || pr->draw_idle_id == -1 || !pr->draw_idle_high))
2478                 {
2479                 if (pr->draw_idle_id != -1) g_source_remove(pr->draw_idle_id);
2480                 pr->draw_idle_id = g_idle_add_full(GDK_PRIORITY_REDRAW,
2481                                                    pr_queue_draw_idle_cb, pr, NULL);
2482                 pr->draw_idle_high = TRUE;
2483                 }
2484 }
2485
2486 static void pr_redraw(PixbufRenderer *pr, gint new_data)
2487 {
2488         pr_queue_clear(pr);
2489         pr_queue(pr, 0, 0, pr->width, pr->height, TRUE, TILE_RENDER_ALL, new_data, FALSE);
2490 }
2491
2492 /*
2493  *-------------------------------------------------------------------
2494  * signal emission
2495  *-------------------------------------------------------------------
2496  */
2497
2498 static void pr_update_signal(PixbufRenderer *pr)
2499 {
2500 #if 0
2501         printf("FIXME: send updated signal\n");
2502 #endif
2503         if (debug) printf("%s pixbuf renderer updated - started drawing %p\n", get_exec_time(), pr);
2504         pr->debug_updated = TRUE;
2505 }
2506
2507 static void pr_zoom_signal(PixbufRenderer *pr)
2508 {
2509         g_signal_emit(pr, signals[SIGNAL_ZOOM], 0, pr->zoom);
2510 }
2511
2512 static void pr_clicked_signal(PixbufRenderer *pr, GdkEventButton *bevent)
2513 {
2514         g_signal_emit(pr, signals[SIGNAL_CLICKED], 0, bevent);
2515 }
2516
2517 static void pr_scroll_notify_signal(PixbufRenderer *pr)
2518 {
2519         g_signal_emit(pr, signals[SIGNAL_SCROLL_NOTIFY], 0);
2520 }
2521
2522 static void pr_render_complete_signal(PixbufRenderer *pr)
2523 {
2524         if (!pr->complete)
2525                 {
2526                 g_signal_emit(pr, signals[SIGNAL_RENDER_COMPLETE], 0);
2527                 g_object_set(G_OBJECT(pr), "complete", TRUE, NULL);
2528                 }
2529         if (pr->debug_updated)
2530                 {
2531                 if (debug) printf("%s pixbuf renderer done %p\n", get_exec_time(), pr);
2532                 pr->debug_updated = FALSE;
2533                 }
2534 }
2535
2536 static void pr_drag_signal(PixbufRenderer *pr, GdkEventButton *bevent)
2537 {
2538         g_signal_emit(pr, signals[SIGNAL_DRAG], 0, bevent);
2539 }
2540
2541 /*
2542  *-------------------------------------------------------------------
2543  * sync and clamp
2544  *-------------------------------------------------------------------
2545  */
2546
2547 static gint pr_scroll_clamp(PixbufRenderer *pr)
2548 {
2549         gint old_xs;
2550         gint old_ys;
2551
2552         if (pr->zoom == 0.0)
2553                 {
2554                 pr->x_scroll = 0;
2555                 pr->y_scroll = 0;
2556
2557                 return FALSE;
2558                 }
2559
2560         old_xs = pr->x_scroll;
2561         old_ys = pr->y_scroll;
2562
2563         if (pr->x_offset > 0)
2564                 {
2565                 pr->x_scroll = 0;
2566                 }
2567         else
2568                 {
2569                 pr->x_scroll = CLAMP(pr->x_scroll, 0, pr->width - pr->vis_width);
2570                 }
2571
2572         if (pr->y_offset > 0)
2573                 {
2574                 pr->y_scroll = 0;
2575                 }
2576         else
2577                 {
2578                 pr->y_scroll = CLAMP(pr->y_scroll, 0, pr->height - pr->vis_height);
2579                 }
2580
2581         return (old_xs != pr->x_scroll || old_ys != pr->y_scroll);
2582 }
2583
2584 static gint pr_size_clamp(PixbufRenderer *pr)
2585 {
2586         gint old_vw, old_vh;
2587
2588         old_vw = pr->vis_width;
2589         old_vh = pr->vis_height;
2590
2591         if (pr->width < pr->window_width)
2592                 {
2593                 pr->vis_width = pr->width;
2594                 pr->x_offset = (pr->window_width - pr->width) / 2;
2595                 }
2596         else
2597                 {
2598                 pr->vis_width = pr->window_width;
2599                 pr->x_offset = 0;
2600                 }
2601
2602         if (pr->height < pr->window_height)
2603                 {
2604                 pr->vis_height = pr->height;
2605                 pr->y_offset = (pr->window_height - pr->height) / 2;
2606                 }
2607         else
2608                 {
2609                 pr->vis_height = pr->window_height;
2610                 pr->y_offset = 0;
2611                 }
2612
2613         return (old_vw != pr->vis_width || old_vh != pr->vis_height);
2614 }
2615
2616 static gint pr_zoom_clamp(PixbufRenderer *pr, gdouble zoom,
2617                           gint force, gint new, gint invalidate,
2618                           gint *redrawn)
2619 {
2620         gint w, h;
2621         gdouble scale;
2622         gint invalid;
2623
2624         zoom = CLAMP(zoom, pr->zoom_min, pr->zoom_max);
2625
2626         if (pr->zoom == zoom && !force) return FALSE;
2627
2628         w = pr->image_width;
2629         h = pr->image_height;
2630
2631         if (zoom == 0.0 && !pr->pixbuf)
2632                 {
2633                 scale = 1.0;
2634                 }
2635         else if (zoom == 0.0)
2636                 {
2637                 gint max_w;
2638                 gint max_h;
2639                 gint sizeable;
2640
2641                 sizeable = (new && pr_parent_window_sizable(pr));
2642
2643                 if (sizeable)
2644                         {
2645                         max_w = gdk_screen_width();
2646                         max_h = gdk_screen_height();
2647
2648                         if (pr->window_limit)
2649                                 {
2650                                 max_w = max_w * pr->window_limit_size / 100;
2651                                 max_h = max_h * pr->window_limit_size / 100;
2652                                 }
2653                         }
2654                 else
2655                         {
2656                         max_w = pr->window_width;
2657                         max_h = pr->window_height;
2658                         }
2659
2660                 if ((pr->zoom_expand && !sizeable) || w > max_w || h > max_h)
2661                         {
2662                         if ((gdouble)max_w / w > (gdouble)max_h / h)
2663                                 {
2664                                 scale = (gdouble)max_h / h;
2665                                 h = max_h;
2666                                 w = w * scale + 0.5;
2667                                 if (w > max_w) w = max_w;
2668                                 }
2669                         else
2670                                 {
2671                                 scale = (gdouble)max_w / w;
2672                                 w = max_w;
2673                                 h = h * scale + 0.5;
2674                                 if (h > max_h) h = max_h;
2675                                 }
2676
2677                         if (pr->autofit_limit)
2678                                 {
2679                                 gdouble factor = (gdouble)pr->autofit_limit_size / 100;
2680                                 w = w * factor + 0.5;
2681                                 h = h * factor + 0.5;
2682                                 scale = scale * factor;
2683                                 }
2684                         
2685                         if (w < 1) w = 1;
2686                         if (h < 1) h = 1;
2687                         }
2688                 else
2689                         {
2690                         scale = 1.0;
2691                         }
2692                 }
2693         else if (zoom > 0.0) /* zoom orig, in */
2694                 {
2695                 scale = zoom;
2696                 w = w * scale;
2697                 h = h * scale;
2698                 }
2699         else /* zoom out */
2700                 {
2701                 scale = 1.0 / (0.0 - zoom);
2702                 w = w * scale;
2703                 h = h * scale;
2704                 }
2705
2706         invalid = (pr->width != w || pr->height != h);
2707
2708         pr->zoom = zoom;
2709         pr->width = w;
2710         pr->height = h;
2711         pr->scale = scale;
2712
2713         if (invalidate || invalid)
2714                 {
2715                 pr_tile_invalidate_all(pr);
2716                 pr_redraw(pr, TRUE);
2717                 }
2718         if (redrawn) *redrawn = (invalidate || invalid);
2719
2720         return TRUE;
2721 }
2722
2723 static void pr_zoom_sync(PixbufRenderer *pr, gdouble zoom,
2724                          gint force, gint new,
2725                          gint center_point, gint px, gint py)
2726 {
2727         gdouble old_scale;
2728         gint old_cx, old_cy;
2729         gint clamped;
2730         gint sized;
2731         gint redrawn = FALSE;
2732
2733         old_scale = pr->scale;
2734         if (center_point)
2735                 {
2736                 px = CLAMP(px, 0, pr->width);
2737                 py = CLAMP(py, 0, pr->height);
2738                 old_cx = pr->x_scroll + (px - pr->x_offset);
2739                 old_cy = pr->y_scroll + (py - pr->y_offset);
2740                 }
2741         else
2742                 {
2743                 px = py = 0;
2744                 old_cx = pr->x_scroll + pr->vis_width / 2;
2745                 old_cy = pr->y_scroll + pr->vis_height / 2;
2746                 }
2747
2748         if (!pr_zoom_clamp(pr, zoom, force, new, force, &redrawn)) return;
2749
2750         clamped = pr_size_clamp(pr);
2751         sized = pr_parent_window_resize(pr, pr->width, pr->height);
2752
2753         if (force && new)
2754                 {
2755                 switch (pr->scroll_reset)
2756                         {
2757                         case PR_SCROLL_RESET_NOCHANGE:
2758                                 /* maintain old scroll position, do nothing */
2759                                 break;
2760                         case PR_SCROLL_RESET_CENTER:
2761                                 /* center new image */
2762                                 pr->x_scroll = ((double)pr->image_width / 2.0 * pr->scale) - pr->vis_width / 2;
2763                                 pr->y_scroll = ((double)pr->image_height / 2.0 * pr->scale) - pr->vis_height / 2;
2764                                 break;
2765                         case PR_SCROLL_RESET_TOPLEFT:
2766                         default:
2767                                 /* reset to upper left */
2768                                 pr->x_scroll = 0;
2769                                 pr->y_scroll = 0;
2770                                 break;
2771                         }
2772                 }
2773         else
2774                 {
2775                 /* user zoom does not force, so keep visible center point */
2776                 if (center_point)
2777                         {
2778                         pr->x_scroll = old_cx / old_scale * pr->scale - (px - pr->x_offset);
2779                         pr->y_scroll = old_cy / old_scale * pr->scale - (py - pr->y_offset);
2780                         }
2781                 else
2782                         {
2783                         pr->x_scroll = old_cx / old_scale * pr->scale - (pr->vis_width / 2);
2784                         pr->y_scroll = old_cy / old_scale * pr->scale - (pr->vis_height / 2);
2785                         }
2786                 }
2787
2788         pr_scroll_clamp(pr);
2789
2790         /* If the window was not sized, redraw the image - we know there will be no size/expose signal.
2791          * But even if a size is claimed, there is no guarantee that the window manager will allow it,
2792          * so redraw the window anyway :/
2793          */
2794         if (sized || clamped) pr_border_clear(pr);
2795         pr_redraw(pr, redrawn);
2796
2797         pr_scroll_notify_signal(pr);
2798         pr_zoom_signal(pr);
2799         pr_update_signal(pr);
2800 }
2801
2802 static void pr_size_sync(PixbufRenderer *pr, gint new_width, gint new_height)
2803 {
2804         gint zoom_changed = FALSE;
2805
2806         if (pr->window_width == new_width && pr->window_height == new_height) return;
2807
2808         pr->window_width = new_width;
2809         pr->window_height = new_height;
2810
2811         if (pr->zoom == 0.0)
2812                 {
2813                 gdouble old_scale = pr->scale;
2814                 pr_zoom_clamp(pr, 0.0, TRUE, FALSE, FALSE, NULL);
2815                 zoom_changed = (old_scale != pr->scale);
2816                 }
2817
2818         pr_size_clamp(pr);
2819         pr_scroll_clamp(pr);
2820
2821         pr_overlay_update_sizes(pr);
2822
2823         /* ensure scroller remains visible */
2824         if (pr->scroller_overlay != -1)
2825                 {
2826                 gint update = FALSE;
2827
2828                 if (pr->scroller_x > new_width)
2829                         {
2830                         pr->scroller_x = new_width;
2831                         pr->scroller_xpos = new_width;
2832                         update = TRUE;
2833                         }
2834                 if (pr->scroller_y > new_height)
2835                         {
2836                         pr->scroller_y = new_height;
2837                         pr->scroller_ypos = new_height;
2838                         update = TRUE;
2839                         }
2840
2841                 if (update)
2842                         {
2843                         GdkPixbuf *pixbuf;
2844
2845                         if (pixbuf_renderer_overlay_get(pr, pr->scroller_overlay, &pixbuf, NULL, NULL))
2846                                 {
2847                                 gint w, h;
2848
2849                                 w = gdk_pixbuf_get_width(pixbuf);
2850                                 h = gdk_pixbuf_get_height(pixbuf);
2851                                 pixbuf_renderer_overlay_set(pr, pr->scroller_overlay, pixbuf,
2852                                                             pr->scroller_x - w / 2, pr->scroller_y - h / 2);
2853                                 }
2854                         }
2855                 }
2856
2857         pr_border_clear(pr);
2858
2859         pr_scroll_notify_signal(pr);
2860         if (zoom_changed) pr_zoom_signal(pr);
2861         pr_update_signal(pr);
2862 }
2863
2864 static void pr_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data)
2865 {
2866         PixbufRenderer *pr = data;
2867
2868         pr_size_sync(pr, allocation->width, allocation->height);
2869 }
2870
2871 static void pixbuf_renderer_paint(PixbufRenderer *pr, GdkRectangle *area)
2872 {
2873         gint x, y;
2874
2875         pr_border_draw(pr, area->x, area->y, area->width, area->height);
2876
2877         x = MAX(0, (gint)area->x - pr->x_offset + pr->x_scroll);
2878         y = MAX(0, (gint)area->y - pr->y_offset + pr->y_scroll);
2879
2880         pr_queue(pr, x, y,
2881                  MIN((gint)area->width, pr->width - x),
2882                  MIN((gint)area->height, pr->height - y),
2883                  FALSE, TILE_RENDER_ALL, FALSE, FALSE);
2884 }
2885
2886 /*
2887  *-------------------------------------------------------------------
2888  * scrolling
2889  *-------------------------------------------------------------------
2890  */
2891
2892 void pixbuf_renderer_scroll(PixbufRenderer *pr, gint x, gint y)
2893 {
2894         gint old_x, old_y;
2895         gint x_off, y_off;
2896         gint w, h;
2897
2898         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
2899
2900         if (!pr->pixbuf && !pr->source_tiles_enabled) return;
2901
2902         old_x = pr->x_scroll;
2903         old_y = pr->y_scroll;
2904
2905         pr->x_scroll += x;
2906         pr->y_scroll += y;
2907
2908         pr_scroll_clamp(pr);
2909         if (pr->x_scroll == old_x && pr->y_scroll == old_y) return;
2910
2911         pr_scroll_notify_signal(pr);
2912
2913         x_off = pr->x_scroll - old_x;
2914         y_off = pr->y_scroll - old_y;
2915
2916         w = pr->vis_width - abs(x_off);
2917         h = pr->vis_height - abs(y_off);
2918
2919         if (w < 1 || h < 1)
2920                 {
2921                 /* scrolled completely to new material */
2922                 pr_queue(pr, 0, 0, pr->width, pr->height, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
2923                 return;
2924                 }
2925         else
2926                 {
2927                 gint x1, y1;
2928                 gint x2, y2;
2929                 GtkWidget *box;
2930                 GdkGC *gc;
2931                 GdkEvent *event;
2932
2933                 if (x_off < 0)
2934                         {
2935                         x1 = abs(x_off);
2936                         x2 = 0;
2937                         }
2938                 else
2939                         {
2940                         x1 = 0;
2941                         x2 = abs(x_off);
2942                         }
2943
2944                 if (y_off < 0)
2945                         {
2946                         y1 = abs(y_off);
2947                         y2 = 0;
2948                         }
2949                 else
2950                         {
2951                         y1 = 0;
2952                         y2 = abs(y_off);
2953                         }
2954
2955                 box = GTK_WIDGET(pr);
2956
2957                 gc = gdk_gc_new(box->window);
2958                 gdk_gc_set_exposures(gc, TRUE);
2959                 gdk_draw_drawable(box->window, gc,
2960                                   box->window,
2961                                   x2 + pr->x_offset, y2 + pr->y_offset,
2962                                   x1 + pr->x_offset, y1 + pr->y_offset, w, h);
2963                 g_object_unref(gc);
2964
2965                 if (pr->overlay_list)
2966                         {
2967                         pr_overlay_queue_all(pr);
2968                         }
2969
2970                 w = pr->vis_width - w;
2971                 h = pr->vis_height - h;
2972
2973                 if (w > 0)
2974                         {
2975                         pr_queue(pr,
2976                                  x_off > 0 ? pr->x_scroll + (pr->vis_width - w) : pr->x_scroll, pr->y_scroll,
2977                                  w, pr->vis_height, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
2978                         }
2979                 if (h > 0)
2980                         {
2981                         /* FIXME, to optimize this, remove overlap */
2982                         pr_queue(pr,
2983                                  pr->x_scroll, y_off > 0 ? pr->y_scroll + (pr->vis_height - h) : pr->y_scroll,
2984                                  pr->vis_width, h, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
2985                         }
2986
2987                 /* process exposures here, "expose_event" seems to miss a few with obstructed windows */
2988                 while ((event = gdk_event_get_graphics_expose(box->window)) != NULL)
2989                         {
2990                         pixbuf_renderer_paint(pr, &event->expose.area);
2991
2992                         if (event->expose.count == 0)
2993                                 {
2994                                 gdk_event_free(event);
2995                                 break;
2996                                 }
2997                         gdk_event_free(event);
2998                         }
2999                 }
3000 }
3001
3002 void pixbuf_renderer_scroll_to_point(PixbufRenderer *pr, gint x, gint y,
3003                                      gdouble x_align, gdouble y_align)
3004 {
3005         gint px, py;
3006         gint ax, ay;
3007
3008         x_align = CLAMP(x_align, 0.0, 1.0);
3009         y_align = CLAMP(y_align, 0.0, 1.0);
3010
3011         ax = (gdouble)pr->vis_width * x_align;
3012         ay = (gdouble)pr->vis_height * y_align;
3013
3014         px = (gdouble)x * pr->scale - (pr->x_scroll + ax);
3015         py = (gdouble)y * pr->scale - (pr->y_scroll + ay);
3016
3017         pixbuf_renderer_scroll(pr, px, py);
3018 }
3019
3020 /* get or set coordinates of viewport center in the image, in range 0.0 - 1.0 */
3021
3022 void pixbuf_renderer_get_scroll_center(PixbufRenderer *pr, gdouble *x, gdouble *y)
3023 {
3024         gint src_x, src_y;
3025         
3026         src_x = pr->x_scroll + pr->vis_width / 2;
3027         src_y = pr->y_scroll + pr->vis_height / 2;
3028
3029         *x = (gdouble)src_x / pr->width;
3030         *y = (gdouble)src_y / pr->height;
3031 }
3032
3033 void pixbuf_renderer_set_scroll_center(PixbufRenderer *pr, gdouble x, gdouble y)
3034 {
3035         gdouble dst_x, dst_y;
3036
3037         dst_x = x * pr->width  - pr->vis_width  / 2 - pr->x_scroll + CLAMP(pr->subpixel_x_scroll, -1.0, 1.0);
3038         dst_y = y * pr->height - pr->vis_height / 2 - pr->y_scroll + CLAMP(pr->subpixel_y_scroll, -1.0, 1.0);
3039
3040         pr->subpixel_x_scroll = dst_x - (int)dst_x;
3041         pr->subpixel_y_scroll = dst_y - (int)dst_y;
3042         
3043         pixbuf_renderer_scroll(pr, (int)dst_x, (int)dst_y);
3044 }
3045
3046
3047 /*
3048  *-------------------------------------------------------------------
3049  * mouse
3050  *-------------------------------------------------------------------
3051  */
3052
3053 static gint pr_mouse_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3054 {
3055         PixbufRenderer *pr;
3056         gint accel;
3057
3058         pr = PIXBUF_RENDERER(widget);
3059
3060         if (pr->scroller_id != -1)
3061                 {
3062                 pr->scroller_xpos = bevent->x;
3063                 pr->scroller_ypos = bevent->y;
3064                 }
3065
3066         if (!pr->in_drag || !gdk_pointer_is_grabbed()) return FALSE;
3067
3068         if (pr->drag_moved < PR_DRAG_SCROLL_THRESHHOLD)
3069                 {
3070                 pr->drag_moved++;
3071                 }
3072         else
3073                 {
3074                 widget_set_cursor(widget, GDK_FLEUR);
3075                 }
3076
3077         if (bevent->state & GDK_SHIFT_MASK)
3078                 {
3079                 accel = PR_PAN_SHIFT_MULTIPLIER;
3080                 }
3081         else
3082                 {
3083                 accel = 1;
3084                 }
3085
3086         /* do the scroll */
3087         pixbuf_renderer_scroll(pr, (pr->drag_last_x - bevent->x) * accel,
3088                                (pr->drag_last_y - bevent->y) * accel);
3089
3090         pr_drag_signal(pr, bevent);
3091
3092         pr->drag_last_x = bevent->x;
3093         pr->drag_last_y = bevent->y;
3094
3095         return FALSE;
3096 }
3097
3098 static gint pr_mouse_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3099 {
3100         PixbufRenderer *pr;
3101         GtkWidget *parent;
3102
3103         pr = PIXBUF_RENDERER(widget);
3104
3105         if (pr->scroller_id != -1) return TRUE;
3106
3107         switch (bevent->button)
3108                 {
3109                 case 1:
3110                         pr->in_drag = TRUE;
3111                         pr->drag_last_x = bevent->x;
3112                         pr->drag_last_y = bevent->y;
3113                         pr->drag_moved = 0;
3114                         gdk_pointer_grab(widget->window, FALSE,
3115                                          GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
3116                                          NULL, NULL, bevent->time);
3117                         gtk_grab_add(widget);
3118                         break;
3119                 case 2:
3120                         pr->drag_moved = 0;
3121                         break;
3122                 case 3:
3123                         pr_clicked_signal(pr, bevent);
3124                         break;
3125                 default:
3126                         break;
3127                 }
3128
3129         parent = gtk_widget_get_parent(widget);
3130         if (widget && GTK_WIDGET_CAN_FOCUS(parent))
3131                 {
3132                 gtk_widget_grab_focus(parent);
3133                 }
3134
3135         return FALSE;
3136 }
3137
3138 static gint pr_mouse_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3139 {
3140         PixbufRenderer *pr;
3141
3142         pr = PIXBUF_RENDERER(widget);
3143
3144         if (pr->scroller_id != -1)
3145                 {
3146                 pr_scroller_stop(pr);
3147                 return TRUE;
3148                 }
3149
3150         if (gdk_pointer_is_grabbed() && GTK_WIDGET_HAS_GRAB(pr))
3151                 {
3152                 gtk_grab_remove(widget);
3153                 gdk_pointer_ungrab(bevent->time);
3154                 widget_set_cursor(widget, -1);
3155                 }
3156
3157         if (pr->drag_moved < PR_DRAG_SCROLL_THRESHHOLD)
3158                 {
3159                 if (bevent->button == 1 && (bevent->state & GDK_SHIFT_MASK))
3160                         {
3161                         pr_scroller_start(pr, bevent->x, bevent->y);
3162                         }
3163                 else if (bevent->button == 1 || bevent->button == 2)
3164                         {
3165                         pr_clicked_signal(pr, bevent);
3166                         }
3167                 }
3168
3169         pr->in_drag = FALSE;
3170
3171         return FALSE;
3172 }
3173
3174 static gint pr_mouse_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
3175 {
3176         PixbufRenderer *pr;
3177
3178         pr = PIXBUF_RENDERER(widget);
3179
3180         if (pr->scroller_id != -1)
3181                 {
3182                 pr->scroller_xpos = pr->scroller_x;
3183                 pr->scroller_ypos = pr->scroller_y;
3184                 pr->scroller_xinc = 0;
3185                 pr->scroller_yinc = 0;
3186                 }
3187
3188         return FALSE;
3189 }
3190
3191 static void pr_mouse_drag_cb(GtkWidget *widget, GdkDragContext *context, gpointer data)
3192 {
3193         PixbufRenderer *pr;
3194
3195         pr = PIXBUF_RENDERER(widget);
3196
3197         pr->drag_moved = PR_DRAG_SCROLL_THRESHHOLD;
3198 }
3199
3200 static void pr_signals_connect(PixbufRenderer *pr)
3201 {
3202         g_signal_connect(G_OBJECT(pr), "motion_notify_event",
3203                          G_CALLBACK(pr_mouse_motion_cb), pr);
3204         g_signal_connect(G_OBJECT(pr), "button_press_event",
3205                          G_CALLBACK(pr_mouse_press_cb), pr);
3206         g_signal_connect(G_OBJECT(pr), "button_release_event",
3207                          G_CALLBACK(pr_mouse_release_cb), pr);
3208         g_signal_connect(G_OBJECT(pr), "leave_notify_event",
3209                          G_CALLBACK(pr_mouse_leave_cb), pr);
3210
3211         gtk_widget_set_events(GTK_WIDGET(pr), GDK_POINTER_MOTION_MASK |
3212                                               GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
3213                                               GDK_LEAVE_NOTIFY_MASK);
3214
3215         g_signal_connect(G_OBJECT(pr), "drag_begin",
3216                          G_CALLBACK(pr_mouse_drag_cb), pr);
3217
3218 }
3219
3220 /*
3221  *-------------------------------------------------------------------
3222  * public
3223  *-------------------------------------------------------------------
3224  */
3225
3226 static void pr_pixbuf_sync(PixbufRenderer *pr, gdouble zoom)
3227 {
3228         if (!pr->pixbuf)
3229                 {
3230                 GtkWidget *box;
3231
3232                 /* no pixbuf so just clear the window */
3233                 pr->image_width = 0;
3234                 pr->image_height = 0;
3235                 pr->scale = 1.0;
3236
3237                 box = GTK_WIDGET(pr);
3238
3239                 if (GTK_WIDGET_REALIZED(box))
3240                         {
3241                         gdk_window_clear(box->window);
3242                         pr_overlay_draw(pr, 0, 0, pr->window_width, pr->window_height, NULL);
3243                         }
3244
3245                 pr_update_signal(pr);
3246
3247                 return;
3248                 }
3249
3250         pr->image_width = gdk_pixbuf_get_width(pr->pixbuf);
3251         pr->image_height = gdk_pixbuf_get_height(pr->pixbuf);
3252
3253         pr_zoom_sync(pr, zoom, TRUE, TRUE, FALSE, 0, 0);
3254 }
3255
3256 static void pr_set_pixbuf(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom)
3257 {
3258         if (pixbuf) g_object_ref(pixbuf);
3259         if (pr->pixbuf) g_object_unref(pr->pixbuf);
3260         pr->pixbuf = pixbuf;
3261
3262         pr_pixbuf_sync(pr, zoom);
3263 }
3264
3265 void pixbuf_renderer_set_pixbuf(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom)
3266 {
3267         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3268
3269         pr_source_tile_unset(pr);
3270
3271         pr_set_pixbuf(pr, pixbuf, zoom);
3272
3273         pr_update_signal(pr);
3274 }
3275
3276 GdkPixbuf *pixbuf_renderer_get_pixbuf(PixbufRenderer *pr)
3277 {
3278         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), NULL);
3279
3280         return pr->pixbuf;
3281 }
3282
3283 void pixbuf_renderer_move(PixbufRenderer *pr, PixbufRenderer *source)
3284 {
3285         GObject *object;
3286         PixbufRendererScrollResetType scroll_reset;
3287
3288         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3289         g_return_if_fail(IS_PIXBUF_RENDERER(source));
3290
3291         if (pr == source) return;
3292
3293         object = G_OBJECT(pr);
3294
3295         g_object_set(object, "zoom_min", source->zoom_min, NULL);
3296         g_object_set(object, "zoom_max", source->zoom_max, NULL);
3297         g_object_set(object, "loading", source->loading, NULL);
3298
3299         pr->complete = source->complete;
3300
3301         pr->x_scroll = source->x_scroll;
3302         pr->y_scroll = source->y_scroll;
3303
3304         scroll_reset = pr->scroll_reset;
3305         pr->scroll_reset = PR_SCROLL_RESET_NOCHANGE;
3306
3307         if (source->source_tiles_enabled)
3308                 {
3309                 pr_source_tile_unset(pr);
3310
3311                 pr->source_tiles_enabled = source->source_tiles_enabled;
3312                 pr->source_tiles_cache_size = source->source_tiles_cache_size;
3313                 pr->source_tile_width = source->source_tile_width;
3314                 pr->source_tile_height = source->source_tile_height;
3315                 pr->image_width = source->image_width;
3316                 pr->image_height = source->image_height;
3317
3318                 pr->func_tile_request = source->func_tile_request;
3319                 pr->func_tile_dispose = source->func_tile_dispose;
3320                 pr->func_tile_data = source->func_tile_data;
3321
3322                 pr->source_tiles = source->source_tiles;
3323                 source->source_tiles = NULL;
3324
3325                 pr_zoom_sync(pr, source->zoom, TRUE, TRUE, FALSE, 0, 0);
3326                 pr_redraw(pr, TRUE);
3327                 }
3328         else
3329                 {
3330                 pixbuf_renderer_set_pixbuf(pr, source->pixbuf, source->zoom);
3331                 }
3332
3333         pr->scroll_reset = scroll_reset;
3334
3335         pixbuf_renderer_set_pixbuf(source, NULL, source->zoom);
3336         pr_queue_clear(source);
3337         pr_tile_free_all(source);
3338 }
3339
3340 void pixbuf_renderer_area_changed(PixbufRenderer *pr, gint x, gint y, gint width, gint height)
3341 {
3342         gint x1, y1, x2, y2;
3343
3344         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3345
3346         if (pr->source_tiles_enabled)
3347                 {
3348                 pr_source_tile_changed(pr, x, y, width, height);
3349                 }
3350
3351         if (pr->scale != 1.0 && pr->zoom_quality != GDK_INTERP_NEAREST)
3352                 {
3353                 /* increase region when using a zoom quality that may access surrounding pixels */
3354                 y -= 1;
3355                 height += 2;
3356                 }
3357
3358         x1 = (gint)floor((double)x * pr->scale);
3359         y1 = (gint)floor((double)y * pr->scale);
3360         x2 = (gint)ceil((double)(x + width) * pr->scale);
3361         y2 = (gint)ceil((double)(y + height) * pr->scale);
3362
3363         pr_queue(pr, x1, y1, x2 - x1, y2 - y1, FALSE, TILE_RENDER_AREA, TRUE, TRUE);
3364 }
3365
3366 void pixbuf_renderer_zoom_adjust(PixbufRenderer *pr, gdouble increment)
3367 {
3368         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3369
3370         pr_zoom_adjust_real(pr, increment, FALSE, 0, 0);
3371 }
3372
3373 void pixbuf_renderer_zoom_adjust_at_point(PixbufRenderer *pr, gdouble increment, gint x, gint y)
3374 {
3375         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3376
3377         pr_zoom_adjust_real(pr, increment, TRUE, x, y);
3378 }
3379
3380 void pixbuf_renderer_zoom_set(PixbufRenderer *pr, gdouble zoom)
3381 {
3382         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3383
3384         pr_zoom_sync(pr, zoom, FALSE, FALSE, FALSE, 0, 0);
3385 }
3386
3387 gdouble pixbuf_renderer_zoom_get(PixbufRenderer *pr)
3388 {
3389         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), 1.0);
3390
3391         return pr->zoom;
3392 }
3393
3394 gdouble pixbuf_renderer_zoom_get_scale(PixbufRenderer *pr)
3395 {
3396         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), 1.0);
3397
3398         return pr->scale;
3399 }
3400
3401 void pixbuf_renderer_zoom_set_limits(PixbufRenderer *pr, gdouble min, gdouble max)
3402 {
3403         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3404
3405         if (min > 1.0 || max < 1.0) return;
3406         if (min < 1.0 && min > -1.0) return;
3407         if (min < -200.0 || max > 200.0) return;
3408
3409         if (pr->zoom_min != min)
3410                 {
3411                 pr->zoom_min = min;
3412                 g_object_notify(G_OBJECT(pr), "zoom_min");
3413                 }
3414         if (pr->zoom_max != max)
3415                 {
3416                 pr->zoom_max = max;
3417                 g_object_notify(G_OBJECT(pr), "zoom_max");
3418                 }
3419 }
3420
3421 gint pixbuf_renderer_get_image_size(PixbufRenderer *pr, gint *width, gint *height)
3422 {
3423         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
3424         g_return_val_if_fail(width != NULL && height != NULL, FALSE);
3425
3426         if (!pr->pixbuf && !pr->source_tiles_enabled)
3427                 {
3428                 *width = 0;
3429                 *height = 0;
3430                 return FALSE;
3431                 }
3432
3433         *width = pr->image_width;
3434         *height = pr->image_height;
3435         return TRUE;
3436 }
3437
3438 gint pixbuf_renderer_get_scaled_size(PixbufRenderer *pr, gint *width, gint *height)
3439 {
3440         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
3441         g_return_val_if_fail(width != NULL && height != NULL, FALSE);
3442
3443         if (!pr->pixbuf && !pr->source_tiles_enabled)
3444                 {
3445                 *width = 0;
3446                 *height = 0;
3447                 return FALSE;
3448                 }
3449
3450         *width = pr->width;
3451         *height = pr->height;
3452         return TRUE;
3453 }
3454
3455 gint pixbuf_renderer_get_visible_rect(PixbufRenderer *pr, GdkRectangle *rect)
3456 {
3457         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
3458         g_return_val_if_fail(rect != NULL, FALSE);
3459
3460         if ((!pr->pixbuf && !pr->source_tiles_enabled) ||
3461             !pr->scale)
3462                 {
3463                 rect->x = 0;
3464                 rect->y = 0;
3465                 rect->width = 0;
3466                 rect->height = 0;
3467                 return FALSE;
3468                 }
3469
3470         rect->x = (gint)((gdouble)pr->x_scroll / pr->scale);
3471         rect->y = (gint)((gdouble)pr->y_scroll / pr->scale);
3472         rect->width = (gint)((gdouble)pr->vis_width / pr->scale);
3473         rect->height = (gint)((gdouble)pr->vis_height / pr->scale);
3474         return TRUE;
3475 }
3476
3477 gint pixbuf_renderer_get_virtual_rect(PixbufRenderer *pr, GdkRectangle *rect)
3478 {
3479         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
3480         g_return_val_if_fail(rect != NULL, FALSE);
3481
3482         if ((!pr->pixbuf && !pr->source_tiles_enabled))
3483                 {
3484                 rect->x = 0;
3485                 rect->y = 0;
3486                 rect->width = 0;
3487                 rect->height = 0;
3488                 return FALSE;
3489                 }
3490
3491         rect->x = pr->x_scroll;
3492         rect->y = pr->y_scroll;
3493         rect->width = pr->vis_width;
3494         rect->height = pr->vis_height;
3495         return TRUE;
3496 }
3497
3498