##### Note: GQview CVS on sourceforge is not always up to date, please use #####
[geeqie.git] / src / image.c
1 /*
2  * GQview
3  * (C) 2004 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
13 #include "gqview.h"
14 #include "image.h"
15
16
17 #include "image-load.h"
18 #include "collect.h"
19 #include "exif.h"
20 #include "pixbuf_util.h"
21 #include "ui_fileops.h"
22
23 #include <math.h>
24
25
26 #define IMAGE_TILE_SIZE 512
27 #define IMAGE_ZOOM_MIN -32.0
28 #define IMAGE_ZOOM_MAX 32.0
29
30 /* size of the image loader buffer (512 bytes x defined number) */
31 #define IMAGE_LOAD_BUFFER_COUNT 8
32
33 /* define this so that more bytes are read per idle loop on larger images (> 1MB) */
34 #define IMAGE_THROTTLE_LARGER_IMAGES 1
35
36 /* throttle factor to increase read bytes by (2 is double, 3 is triple, etc.) */
37 #define IMAGE_THROTTLE_FACTOR 4
38
39 /* the file size at which throttling take place */
40 #define IMAGE_THROTTLE_THRESHOLD 1048576
41
42 /* distance to drag mouse to disable image flip */
43 #define IMAGE_DRAG_SCROLL_THRESHHOLD 4
44
45 /* alpha channel checkerboard background (same as gimp) */
46 #define IMAGE_ALPHA_CHECK1 0x00999999
47 #define IMAGE_ALPHA_CHECK2 0x00666666
48 #define IMAGE_ALPHA_CHECK_SIZE 16
49
50 #define IMAGE_AUTO_REFRESH_TIME 3000
51
52 /* when scaling image to below this size, use nearest pixel for scaling
53  * (below about 4, the other scale types become slow generating their conversion tables)
54  */
55 #define IMAGE_MIN_SCALE_SIZE 8
56
57
58 typedef enum {
59         TILE_RENDER_NONE = 0,   /* do nothing */
60         TILE_RENDER_AREA,       /* render an area of the tile */
61         TILE_RENDER_ALL         /* render the whole tile */
62 } TileRenderType;
63
64 typedef struct _ImageTile ImageTile;
65 struct _ImageTile
66 {
67         GdkPixmap *pixmap;      /* off screen buffer */
68         GdkPixbuf *pixbuf;      /* pixbuf area for zooming */
69         gint x;                 /* x offset into image */
70         gint y;                 /* y offset into image */
71         gint w;                 /* width that is visible (may be less if at edge of image) */
72         gint h;                 /* height '' */
73
74         gint blank;
75
76 /* render_todo: (explanation)
77         NONE    do nothing
78         AREA    render area of tile, usually only used when loading an image
79                 note: will jump to an ALL if render_done is not ALL.
80         ALL     render entire tile, if never done before w/ ALL, for expose events *only*
81 */
82
83         TileRenderType render_todo;     /* what to do (see above) */
84         TileRenderType render_done;     /* highest that has been done before on tile */
85 };
86
87 typedef struct _QueueData QueueData;
88 struct _QueueData
89 {
90         ImageTile *it;
91         gint x;
92         gint y;
93         gint w;
94         gint h;
95         gint new_data;
96 };
97
98 typedef struct _CacheData CacheData;
99 struct _CacheData
100 {
101         GdkPixmap *pixmap;
102         GdkPixbuf *pixbuf;
103         ImageTile *it;
104         guint size;
105 };
106
107 typedef struct _OverlayData OverlayData;
108 struct _OverlayData
109 {
110         gint id;
111
112         GdkPixbuf *pixbuf;
113
114         gint x;
115         gint y;
116         gint relative;  /* x,y coordinates are relative, negative values start bottom right */
117
118         gint visible;
119         gint always;    /* hide temporarily when scrolling */
120 };
121
122 /* needed to be declared before the source_tile stuff */
123 static void image_pixbuf_sync(ImageWindow *imd, gdouble zoom, gint blank, gint new);
124 static void image_zoom_sync(ImageWindow *imd, gdouble zoom,
125                             gint force, gint blank, gint new,
126                             gint center_point, gint px, gint py);
127 static void image_queue(ImageWindow *imd, gint x, gint y, gint w, gint h,
128                         gint clamp, TileRenderType render, gint new_data);
129
130 static gint util_clip_region(gint x, gint y, gint w, gint h,
131                              gint clip_x, gint clip_y, gint clip_w, gint clip_h,
132                              gint *rx, gint *ry, gint *rw, gint *rh);
133
134 /*
135  *-------------------------------------------------------------------
136  * source tiles
137  *-------------------------------------------------------------------
138  */
139
140 typedef struct _SourceTile SourceTile;
141 struct _SourceTile
142 {
143         gint x;
144         gint y;
145         GdkPixbuf *pixbuf;
146         gint blank;
147 };
148
149 static void source_tile_free(SourceTile *st)
150 {
151         if (!st) return;
152
153         if (st->pixbuf) gdk_pixbuf_unref(st->pixbuf);
154         g_free(st);
155 }
156
157 static void source_tile_free_all(ImageWindow *imd)
158 {
159         GList *work;
160
161         work = imd->source_tiles;
162         while (work)
163                 {
164                 SourceTile *st = work->data;
165                 work = work->next;
166
167                 source_tile_free(st);
168                 }
169
170         g_list_free(imd->source_tiles);
171         imd->source_tiles = NULL;
172 }
173
174 static gint source_tile_visible(ImageWindow *imd, SourceTile *st)
175 {
176         gint x, y, w, h;
177
178         if (!st) return FALSE;
179
180         x = (imd->x_scroll / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE;
181         y = (imd->y_scroll / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE;
182         w = ((imd->x_scroll + imd->vis_width) / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE + IMAGE_TILE_SIZE;
183         h = ((imd->y_scroll + imd->vis_height) / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE + IMAGE_TILE_SIZE;
184
185         return !((double)st->x * imd->scale < (double)x ||
186                  (double)(st->x + imd->source_tile_width) * imd->scale > (double)w ||
187                  (double)st->y * imd->scale < (double)y ||
188                  (double)(st->y + imd->source_tile_height) * imd->scale > (double)h);
189 }
190
191 static SourceTile *source_tile_new(ImageWindow *imd, gint x, gint y)
192 {
193         SourceTile *st = NULL;
194         gint count;
195
196         if (imd->source_tiles_cache_size < 4) imd->source_tiles_cache_size = 4;
197
198         if (imd->source_tile_width < 1 || imd->source_tile_height < 1)
199                 {
200                 printf("warning: source tile size too small %d x %d\n", imd->source_tile_width, imd->source_tile_height);
201                 return NULL;
202                 }
203
204         count = g_list_length(imd->source_tiles);
205         if (count >= imd->source_tiles_cache_size)
206                 {
207                 GList *work;
208
209                 work = g_list_last(imd->source_tiles);
210                 while (work && count >= imd->source_tiles_cache_size)
211                         {
212                         SourceTile *needle;
213
214                         needle = work->data;
215                         work = work->prev;
216
217                         if (!source_tile_visible(imd, needle))
218                                 {
219                                 imd->source_tiles = g_list_remove(imd->source_tiles, needle);
220
221                                 if (imd->func_tile_dispose)
222                                         {
223                                         if (debug) printf("tile dispose: %d x %d @ %d x %d\n",
224                                                          needle->x, needle->y, imd->x_scroll, imd->y_scroll);
225                                         imd->func_tile_dispose(imd, needle->x, needle->y,
226                                                                imd->source_tile_width, imd->source_tile_height,
227                                                                needle->pixbuf, imd->data_tile);
228                                         }
229
230                                 if (!st)
231                                         {
232                                         st = needle;
233                                         }
234                                 else
235                                         {
236                                         source_tile_free(needle);
237                                         }
238
239                                 count--;
240                                 }
241                         else if (debug)
242                                 {
243                                 printf("we still think %d x %d is visble\n", needle->x, needle->y);
244                                 }
245                         }
246
247                 if (debug)
248                         {
249                         printf("cache count %d, max is %d\n", count, imd->source_tiles_cache_size);
250                         }
251                 }
252
253         if (!st)
254                 {
255                 st = g_new0(SourceTile, 1);
256                 st->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
257                                             imd->source_tile_width, imd->source_tile_height);
258                 }
259
260         st->x = (x / imd->source_tile_width) * imd->source_tile_width;
261         st->y = (y / imd->source_tile_height) * imd->source_tile_height;
262         st->blank = TRUE;
263
264         imd->source_tiles = g_list_prepend(imd->source_tiles, st);
265
266         if (debug)
267                 {
268                 printf("tile request: %d x %d\n", st->x, st->y);
269                 if (!source_tile_visible(imd, st)) printf("tile request for invisible tile!\n");
270                 }
271
272         return st;
273 }
274
275 static void image_tile_invalidate(ImageWindow *imd, gint x, gint y, gint w, gint h)
276 {
277         gint i, j;
278         gint x1, x2;
279         gint y1, y2;
280         GList *work;
281
282         x1 = (gint)floor(x / imd->tile_width) * imd->tile_width;
283         x2 = (gint)ceil((x + w) / imd->tile_width) * imd->tile_width;
284
285         y1 = (gint)floor(y / imd->tile_height) * imd->tile_height;
286         y2 = (gint)ceil((y + h) / imd->tile_height) * imd->tile_height;
287
288         work = g_list_nth(imd->tiles, y1 / imd->tile_height * imd->tile_cols + (x1 / imd->tile_width));
289         for (j = y1; j <= y2; j += imd->tile_height)
290                 {
291                 GList *tmp;
292                 tmp = work;
293                 for (i = x1; i <= x2; i += imd->tile_width)
294                         {
295                         if (tmp)
296                                 {
297                                 ImageTile *it = tmp->data;
298
299                                 it->render_done = TILE_RENDER_NONE;
300                                 it->render_todo = TILE_RENDER_ALL;
301
302                                 tmp = tmp->next;
303                                 }
304                         }
305                 work = g_list_nth(work, imd->tile_cols);        /* step 1 row */
306                 }
307 }
308
309 static SourceTile *source_tile_request(ImageWindow *imd, gint x, gint y)
310 {
311         SourceTile *st;
312
313         st = source_tile_new(imd, x, y);
314
315         if (imd->func_tile_request &&
316             imd->func_tile_request(imd, st->x, st->y,
317                                    imd->source_tile_width, imd->source_tile_height, st->pixbuf, imd->data_tile))
318                 {
319                 st->blank = FALSE;
320                 }
321 #if 0
322         /* fixme: somehow invalidate the new st region */
323         image_queue(imd, st->x, st->y, imd->source_tile_width, imd->source_tile_height, FALSE, TILE_RENDER_AREA, TRUE);
324 #endif
325         image_tile_invalidate(imd, st->x * imd->scale, st->y * imd->scale,
326                               imd->source_tile_width * imd->scale, imd->source_tile_height * imd->scale);
327
328         return st;
329 }
330
331 static SourceTile *source_tile_find(ImageWindow *imd, gint x, gint y)
332 {
333         GList *work;
334
335         work = imd->source_tiles;
336         while (work)
337                 {
338                 SourceTile *st = work->data;
339
340                 if (x >= st->x && x < st->x + imd->source_tile_width &&
341                     y >= st->y && y < st->y + imd->source_tile_height)
342                         {
343                         if (work != imd->source_tiles)
344                                 {
345                                 imd->source_tiles = g_list_remove_link(imd->source_tiles, work);
346                                 imd->source_tiles = g_list_concat(work, imd->source_tiles);
347                                 }
348                         return st;
349                         }
350
351                 work = work->next;
352                 }
353
354         return NULL;
355 }
356
357 static GList *source_tile_compute_region(ImageWindow *imd, gint x, gint y, gint w, gint h, gint request)
358 {
359         gint x1, y1;
360         GList *list = NULL;
361         gint sx, sy;
362
363         if (x < 0) x = 0;
364         if (y < 0) y = 0;
365         if (w > imd->image_width) w = imd->image_width;
366         if (h > imd->image_height) h = imd->image_height;
367
368         sx = (x / imd->source_tile_width) * imd->source_tile_width;
369         sy = (y / imd->source_tile_height) * imd->source_tile_height;
370
371         for (x1 = sx; x1 < x + w; x1+= imd->source_tile_width)
372                 {
373                 for (y1 = sy; y1 < y + h; y1 += imd->source_tile_height)
374                         {
375                         SourceTile *st;
376
377                         st = source_tile_find(imd, x1, y1);
378                         if (!st && request) st = source_tile_request(imd, x1, y1);
379
380                         if (st) list = g_list_prepend(list, st);
381                         }
382                 }
383
384         return g_list_reverse(list);
385 }
386
387 static void source_tile_changed(ImageWindow *imd, gint x, gint y, gint width, gint height)
388 {
389         GList *work;
390
391         work = imd->source_tiles;
392         while (work)
393                 {
394                 SourceTile *st;
395                 gint rx, ry, rw, rh;
396
397                 st = work->data;
398                 work = work->next;
399
400                 if (util_clip_region(st->x, st->y, imd->source_tile_width, imd->source_tile_height,
401                                      x, y, width, height,
402                                      &rx, &ry, &rw, &rh))
403                         {
404                         GdkPixbuf *pixbuf;
405
406                         pixbuf = gdk_pixbuf_new_subpixbuf(st->pixbuf, rx - st->x, ry - st->y, rw, rh);
407                         if (imd->func_tile_request &&
408                             imd->func_tile_request(imd, rx, ry, rw, rh, pixbuf, imd->data_tile))
409                                 {
410                                 image_tile_invalidate(imd, rx * imd->scale, ry * imd->scale, rw * imd->scale, rh * imd->scale);
411                                 }
412                         g_object_unref(pixbuf);
413                         }
414                 }
415 }
416
417
418 static gint source_tile_render(ImageWindow *imd, ImageTile *it,
419                                gint x, gint y, gint w, gint h,
420                                gint new_data, gint fast)
421 {
422         GList *list;
423         GList *work;
424         gint draw = FALSE;
425
426         if (imd->zoom == 1.0 || imd->scale == 1.0)
427                 {
428                 list = source_tile_compute_region(imd, it->x + x, it->y + y, w, h, TRUE);
429                 work = list;
430                 while (work)
431                         {
432                         SourceTile *st;
433                         gint rx, ry, rw, rh;
434
435                         st = work->data;
436                         work = work->next;
437
438                         if (util_clip_region(st->x, st->y, imd->source_tile_width, imd->source_tile_height,
439                                              it->x + x, it->y + y, w, h,
440                                              &rx, &ry, &rw, &rh))
441                                 {
442                                 if (st->blank)
443                                         {
444                                         gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
445                                                            rx - st->x, ry - st->y, rw, rh);
446                                         }
447                                 else /* (imd->zoom == 1.0 || imd->scale == 1.0) */
448                                         {
449                                         gdk_draw_pixbuf(it->pixmap,
450                                                         imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
451                                                         st->pixbuf,
452                                                         rx - st->x, ry - st->y,
453                                                         rx - it->x, ry - it->y,
454                                                         rw, rh,
455                                                         (GdkRgbDither)dither_quality, rx, ry);
456                                         }
457                                 }
458                         }
459                 }
460         else
461                 {
462                 double scale_x, scale_y;
463                 gint sx, sy, sw, sh;
464
465                 if (imd->image_width == 0 || imd->image_height == 0) return FALSE;
466                 scale_x = (double)imd->width / imd->image_width;
467                 scale_y = (double)imd->height / imd->image_height;
468
469                 sx = (double)(it->x + x) / scale_x;
470                 sy = (double)(it->y + y) / scale_y;
471                 sw = (double)w / scale_x;
472                 sh = (double)h / scale_y;
473
474                 if (imd->width < IMAGE_MIN_SCALE_SIZE || imd->height < IMAGE_MIN_SCALE_SIZE) fast = TRUE;
475
476 #if 0
477                 /* draws red over draw region, to check for leaks (regions not filled) */
478                 pixbuf_draw_rect(it->pixbuf, x, y, w, h, 255, 0, 0, 255, FALSE);
479 #endif
480
481                 list = source_tile_compute_region(imd, sx, sy, sw, sh, TRUE);
482                 work = list;
483                 while (work)
484                         {
485                         SourceTile *st;
486                         gint rx, ry, rw, rh;
487                         gint stx, sty, stw, sth;
488
489                         st = work->data;
490                         work = work->next;
491
492                         stx = floor((double)st->x * scale_x);
493                         sty = floor((double)st->y * scale_y);
494                         stw = ceil ((double)(st->x + imd->source_tile_width) * scale_x) - stx;
495                         sth = ceil ((double)(st->y + imd->source_tile_height) * scale_y) - sty;
496
497                         if (util_clip_region(stx, sty, stw, sth,
498                                              it->x + x, it->y + y, w, h,
499                                              &rx, &ry, &rw, &rh))
500                                 {
501                                 if (st->blank)
502                                         {
503                                         gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
504                                            rx - st->x, ry - st->y, rw, rh);
505                                         }
506                                 else
507                                         {
508                                         double offset_x;
509                                         double offset_y;
510
511                                         /* may need to use unfloored stx,sty values here */
512                                         offset_x = ((double)stx < (double)it->x) ?
513                                                     (double)stx - (double)it->x  : 0.0;
514                                         offset_y = ((double)sty < (double)it->y) ?
515                                                     (double)sty - (double)it->y  : 0.0;
516
517                                         gdk_pixbuf_scale(st->pixbuf, it->pixbuf, rx - it->x, ry - it->y, rw, rh,
518                                                  (double) 0.0 + rx - it->x + offset_x,
519                                                  (double) 0.0 + ry - it->y + offset_y,
520                                                  scale_x, scale_y,
521                                                  (fast) ? GDK_INTERP_NEAREST : (GdkInterpType)zoom_quality);
522                                         draw = TRUE;
523                                         }
524                                 }
525                         }
526                 }
527
528         g_list_free(list);
529
530         return draw;
531 }
532
533 static void image_source_tile_unset(ImageWindow *imd)
534 {
535         source_tile_free_all(imd);
536
537         imd->source_tiles_enabled = FALSE;
538 }
539
540 void image_set_image_as_tiles(ImageWindow *imd, gint width, gint height,
541                               gint tile_width, gint tile_height, gint cache_size,
542                               ImageTileRequestFunc func_tile_request,
543                               ImageTileDisposeFunc func_tile_dispose,
544                               gpointer data,
545                               gdouble zoom)
546 {
547         /* FIXME: unset any current image */
548         image_source_tile_unset(imd);
549
550         if (tile_width < 32 || tile_height < 32)
551                 {
552                 printf("warning: tile size too small %d x %d (min 32x32)\n", tile_width, tile_height);
553                 return;
554                 }
555         if (width < 32 || height < 32)
556                 {
557                 printf("warning: tile canvas too small %d x %d (min 32x32)\n", width, height);
558                 return;
559                 }
560         if (!func_tile_request)
561                 {
562                 printf("warning: tile request function is null\n");
563                 }
564
565         printf("Setting source tiles to size %d x %d, grid is %d x %d\n", tile_width, tile_height, width, height);
566
567         if (cache_size < 4) cache_size = 4;
568
569         imd->source_tiles_enabled = TRUE;
570         imd->source_tiles_cache_size = cache_size;
571         imd->source_tile_width = tile_width;
572         imd->source_tile_height = tile_height;
573
574         imd->image_width = width;
575         imd->image_height = height;
576
577         imd->func_tile_request = func_tile_request;
578         imd->func_tile_dispose = func_tile_dispose;
579         imd->data_tile = data;
580
581         image_zoom_sync(imd, zoom, TRUE, FALSE, TRUE, FALSE, 0, 0);
582 }
583
584
585 static void image_queue_clear(ImageWindow *imd);
586
587 static void image_update_title(ImageWindow *imd);
588 static void image_update_util(ImageWindow *imd);
589 static void image_complete_util(ImageWindow *imd, gint preload);
590
591 static void image_button_do(ImageWindow *imd, GdkEventButton *bevent);
592
593 static void image_overlay_draw(ImageWindow *imd, gint x, gint y, gint w, gint h);
594 static void image_overlay_queue_all(ImageWindow *imd);
595
596 static void image_scroller_timer_set(ImageWindow *imd, gint start);
597
598
599 static gint util_clip_region(gint x, gint y, gint w, gint h,
600                              gint clip_x, gint clip_y, gint clip_w, gint clip_h,
601                              gint *rx, gint *ry, gint *rw, gint *rh)
602 {
603         if (clip_x + clip_w <= x ||
604             clip_x >= x + w ||
605             clip_y + clip_h <= y ||
606             clip_y >= y + h)
607                 {
608                 return FALSE;
609                 }
610
611         *rx = MAX(x, clip_x);
612         *rw = MIN((x + w), (clip_x + clip_w)) - *rx;
613
614         *ry = MAX(y, clip_y);
615         *rh = MIN((y + h), (clip_y + clip_h)) - *ry;
616
617         return TRUE;
618 }
619
620
621 /*
622  *-------------------------------------------------------------------
623  * tile cache
624  *-------------------------------------------------------------------
625  */
626
627 static gint pixmap_calc_size(GdkPixmap *pixmap)
628 {
629         gint w, h, d;
630
631         d = gdk_drawable_get_depth(pixmap);
632         gdk_drawable_get_size(pixmap, &w, &h);
633         return w * h * (d / 8);
634 }
635
636 static void image_tile_cache_remove(ImageWindow *imd, ImageTile *it)
637 {
638         GList *work;
639
640         work = imd->tile_cache;
641         while(work)
642                 {
643                 CacheData *cd = work->data;
644                 work = work->next;
645
646                 if (cd->it == it)
647                         {
648                         imd->tile_cache = g_list_remove(imd->tile_cache, cd);
649                         imd->tile_cache_size -= cd->size;
650                         g_free(cd);
651                         }
652                 }
653 }
654
655 static void image_tile_cache_free(ImageWindow *imd, CacheData *cd)
656 {
657         imd->tile_cache = g_list_remove(imd->tile_cache, cd);
658         if (cd->pixmap)
659                 {
660                 g_object_unref(cd->it->pixmap);
661                 cd->it->pixmap = NULL;
662                 cd->it->render_done = TILE_RENDER_NONE;
663                 }
664         if (cd->pixbuf)
665                 {
666                 gdk_pixbuf_unref(cd->it->pixbuf);
667                 cd->it->pixbuf = NULL;
668                 }
669         imd->tile_cache_size -= cd->size;
670         g_free(cd);
671 }
672
673 static void image_tile_cache_free_space(ImageWindow *imd, gint space, ImageTile *it)
674 {
675         GList *work;
676         gint tile_max;
677
678         work = g_list_last(imd->tile_cache);
679
680         if (imd->source_tiles_enabled && imd->scale < 1.0)
681                 {
682                 gint tiles;
683
684                 tiles = (imd->vis_width / IMAGE_TILE_SIZE + 1) * (imd->vis_width / IMAGE_TILE_SIZE + 1);
685                 tile_max = MAX(tiles * IMAGE_TILE_SIZE * IMAGE_TILE_SIZE * 3,
686                                (gint)((double)tile_cache_max * 1048576.0 * imd->scale));
687                 }
688         else
689                 {
690                 tile_max = tile_cache_max * 1048576;
691                 }
692
693         while (work && imd->tile_cache_size > 0 && imd->tile_cache_size + space > tile_max)
694                 {
695                 CacheData *cd = work->data;
696                 work = work->prev;
697                 if (cd->it != it) image_tile_cache_free(imd, cd);
698                 }
699 }
700
701 static void image_tile_cache_add(ImageWindow *imd, ImageTile *it,
702                                  GdkPixmap *pixmap, GdkPixbuf *pixbuf, guint size)
703 {
704         CacheData *cd;
705
706         cd = g_new(CacheData, 1);
707         cd->pixmap = pixmap;
708         cd->pixbuf = pixbuf;
709         cd->it = it;
710         cd->size = size;
711
712         imd->tile_cache = g_list_prepend(imd->tile_cache, cd);
713
714         imd->tile_cache_size += cd->size;
715 }
716
717 static void image_tile_prepare(ImageWindow *imd, ImageTile *it)
718 {
719         if (!it->pixmap)
720                 {
721                 GdkPixmap *pixmap;
722                 guint size;
723
724                 pixmap = gdk_pixmap_new(imd->image->window, imd->tile_width, imd->tile_height, -1);
725
726                 size = pixmap_calc_size(pixmap);
727                 image_tile_cache_free_space(imd, size, it);
728
729                 it->pixmap = pixmap;
730                 image_tile_cache_add(imd, it, pixmap, NULL, size);
731                 }
732         
733         if ((imd->zoom != 1.0 || imd->source_tiles_enabled || (imd->pixbuf && gdk_pixbuf_get_has_alpha(imd->pixbuf)) ) &&
734             !it->pixbuf)
735                 {
736                 GdkPixbuf *pixbuf;
737                 guint size;
738
739                 if (imd->pixbuf)
740                         {
741                         pixbuf = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(imd->pixbuf),
742                                                 gdk_pixbuf_get_has_alpha(imd->pixbuf),
743                                                 gdk_pixbuf_get_bits_per_sample(imd->pixbuf),
744                                                 imd->tile_width, imd->tile_height);
745                         }
746                 else
747                         {
748                          pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, imd->tile_width, imd->tile_height);
749                         }
750
751                 size = gdk_pixbuf_get_rowstride(pixbuf) * imd->tile_height;
752                 image_tile_cache_free_space(imd, size, it);
753
754                 it->pixbuf = pixbuf;
755                 image_tile_cache_add(imd, it, NULL, pixbuf, size);
756                 }
757 }
758
759 /*
760  *-------------------------------------------------------------------
761  * tiles
762  *-------------------------------------------------------------------
763  */
764
765 static ImageTile *image_tile_new(gint w, gint h)
766 {
767         ImageTile *it;
768
769         it = g_new0(ImageTile, 1);
770         it->w = w;
771         it->h = h;
772         it->pixmap = NULL;
773         it->pixbuf = NULL;
774         it->blank = TRUE;
775         it->render_todo = TILE_RENDER_NONE;
776         it->render_done = TILE_RENDER_NONE;
777
778         return it;
779 }
780
781 static void image_tile_free(ImageTile *it)
782 {
783         if (!it) return;
784
785         if (it->pixbuf) gdk_pixbuf_unref(it->pixbuf);
786         if (it->pixmap) g_object_unref(it->pixmap);
787
788         g_free(it);
789 }
790
791 static void image_tile_sync_count(ImageWindow *imd, gint n)
792 {
793         gint l;
794
795         l = g_list_length(imd->tiles);
796
797         if (l == n) return;
798
799         if (l < n)
800                 {
801                 while (l < n)
802                         {
803                         imd->tiles = g_list_prepend(imd->tiles, image_tile_new(imd->tile_width, imd->tile_height));
804                         l++;
805                         }
806                 }
807         else
808                 {
809                 /* This should remove from the tail of the GList, but with large images there are many tiles,
810                  * making this significantly faster for those cases.
811                  */
812                 while (l > n && imd->tiles)
813                         {
814                         ImageTile *it = imd->tiles->data;
815                         imd->tiles = g_list_remove(imd->tiles, it);
816                         image_tile_cache_remove(imd, it);
817                         image_tile_free(it);
818                         l--;
819                         }
820                 }
821 }
822
823 static void image_tile_sync(ImageWindow *imd, gint width, gint height, gint blank)
824 {
825         gint rows;
826         gint x, y;
827         GList *work;
828
829         imd->tile_cols = (width + imd->tile_width - 1) / imd->tile_width;
830
831         rows = (height + imd->tile_height - 1) / imd->tile_height;
832
833         image_tile_sync_count(imd, imd->tile_cols * rows);
834
835         x = y = 0;
836         work = imd->tiles;
837         while(work)
838                 {
839                 ImageTile *it = work->data;
840                 work = work->next;
841
842                 it->x = x;
843                 it->y = y;
844                 if (x + imd->tile_width > width)
845                         {
846                         it->w = width - x;
847                         }       
848                 else
849                         {
850                         it->w = imd->tile_width;
851                         }
852                 if (y + imd->tile_height > height)
853                         {
854                         it->h = height - y;
855                         }
856                 else
857                         {
858                         it->h = imd->tile_height;
859                         }
860
861                 it->blank = blank;
862                 it->render_todo = TILE_RENDER_NONE;
863                 it->render_done = TILE_RENDER_NONE;
864
865                 x += imd->tile_width;
866                 if (x >= width)
867                         {
868                         x = 0;
869                         y += imd->tile_height;
870                         }
871                 }
872
873         /* all it's are now useless in queue */
874         image_queue_clear(imd);
875 }
876
877 static void image_tile_render(ImageWindow *imd, ImageTile *it,
878                               gint x, gint y, gint w, gint h,
879                               gint new_data, gint fast)
880 {
881         gint has_alpha;
882         gint draw = FALSE;
883
884         if (it->render_todo == TILE_RENDER_NONE && it->pixmap && !new_data) return;
885
886         if (it->render_done != TILE_RENDER_ALL)
887                 {
888                 x = 0;
889                 y = 0;
890                 w = it->w;
891                 h = it->h;
892                 if (!fast) it->render_done = TILE_RENDER_ALL;
893                 }
894         else if (it->render_todo != TILE_RENDER_AREA)
895                 {
896                 if (!fast) it->render_todo = TILE_RENDER_NONE;
897                 return;
898                 }
899
900         if (!fast) it->render_todo = TILE_RENDER_NONE;
901
902         if (new_data) it->blank = FALSE;
903
904         image_tile_prepare(imd, it);
905         has_alpha = (imd->pixbuf && gdk_pixbuf_get_has_alpha(imd->pixbuf));
906
907         /* FIXME checker colors for alpha should be configurable,
908          * also should be drawn for blank = TRUE
909          */
910
911         if (it->blank)
912                 {
913                 /* no data, do fast rect fill */
914                 gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
915                                    0, 0, it->w, it->h);
916                 }
917         else if (imd->source_tiles_enabled)
918                 {
919                 draw = source_tile_render(imd, it, x, y, w, h, new_data, fast);
920                 }
921         else if (imd->zoom == 1.0 || imd->scale == 1.0)
922                 {
923                 if (has_alpha)
924                         {
925                         gdk_pixbuf_composite_color(imd->pixbuf, it->pixbuf, x, y, w, h,
926                                          (double) 0.0 - it->x,
927                                          (double) 0.0 - it->y,
928                                          1.0, 1.0, GDK_INTERP_NEAREST,
929                                          255, it->x + x, it->y + y,
930                                          IMAGE_ALPHA_CHECK_SIZE, IMAGE_ALPHA_CHECK1, IMAGE_ALPHA_CHECK2);
931                         draw = TRUE;
932                         }
933                 else
934                         {
935                         /* faster, simple */
936                         gdk_draw_pixbuf(it->pixmap,
937                                         imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
938                                         imd->pixbuf,
939                                         it->x + x, it->y + y,
940                                         x, y,
941                                         w, h,
942                                         (GdkRgbDither)dither_quality, it->x + x, it->y + y);
943                         }
944                 }
945         else
946                 {
947                 double scale_x, scale_y;
948
949                 if (imd->image_width == 0 || imd->image_height == 0) return;
950                 scale_x = (double)imd->width / imd->image_width;
951                 scale_y = (double)imd->height / imd->image_height;
952
953                 /* HACK: The pixbuf scalers get kinda buggy(crash) with extremely
954                  * small sizes for anything but GDK_INTERP_NEAREST
955                  */
956                 if (imd->width < IMAGE_MIN_SCALE_SIZE || imd->height < IMAGE_MIN_SCALE_SIZE) fast = TRUE;
957
958                 if (!has_alpha)
959                         {
960                         gdk_pixbuf_scale(imd->pixbuf, it->pixbuf, x, y, w, h,
961                                          (double) 0.0 - it->x,
962                                          (double) 0.0 - it->y,
963                                          scale_x, scale_y,
964                                          (fast) ? GDK_INTERP_NEAREST : (GdkInterpType)zoom_quality);
965                         }
966                 else
967                         {
968                         gdk_pixbuf_composite_color(imd->pixbuf, it->pixbuf, x, y, w, h,
969                                          (double) 0.0 - it->x,
970                                          (double) 0.0 - it->y,
971                                          scale_x, scale_y,
972                                          (fast) ? GDK_INTERP_NEAREST : (GdkInterpType)zoom_quality,
973                                          255, it->x + x, it->y + y,
974                                          IMAGE_ALPHA_CHECK_SIZE, IMAGE_ALPHA_CHECK1, IMAGE_ALPHA_CHECK2);
975                         }
976                 draw = TRUE;
977                 }
978
979         if (draw && it->pixbuf && !it->blank)
980                 {
981                 gdk_draw_pixbuf(it->pixmap,
982                                 imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
983                                 it->pixbuf,
984                                 x, y,
985                                 x, y,
986                                 w, h,
987                                 (GdkRgbDither)dither_quality, it->x + x, it->y + y);
988                 }
989 }
990
991 static void image_tile_expose(ImageWindow *imd, ImageTile *it,
992                               gint x, gint y, gint w, gint h,
993                               gint new_data, gint fast)
994 {
995         image_tile_render(imd, it, x, y, w, h, new_data, fast);
996
997         gdk_draw_drawable(imd->image->window, imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
998                           it->pixmap, x, y,
999                           imd->x_offset + (it->x - imd->x_scroll) + x, imd->y_offset + (it->y - imd->y_scroll) + y, w, h);
1000
1001         if (imd->overlay_list)
1002                 {
1003                 image_overlay_draw(imd, imd->x_offset + (it->x - imd->x_scroll) + x,
1004                                         imd->y_offset + (it->y - imd->y_scroll) + y,
1005                                         w, h);
1006                 }
1007 }
1008
1009 static gint image_tile_is_visible(ImageWindow *imd, ImageTile *it)
1010 {
1011         return (it->x + it->w >= imd->x_scroll && it->x <= imd->x_scroll + imd->window_width - imd->x_offset * 2 &&
1012                 it->y + it->h >= imd->y_scroll && it->y <= imd->y_scroll + imd->window_height - imd->y_offset * 2);
1013 }
1014
1015 /*
1016  *-------------------------------------------------------------------
1017  * render queue
1018  *-------------------------------------------------------------------
1019  */
1020
1021
1022 static gint image_queue_draw_idle_cb(gpointer data)
1023 {
1024         ImageWindow *imd = data;
1025         QueueData *qd;
1026         gint fast;
1027
1028         if ((!imd->pixbuf && !imd->source_tiles_enabled) || (!imd->draw_queue && !imd->draw_queue_2pass) || imd->draw_idle_id == -1)
1029                 {
1030                 if (!imd->completed) image_complete_util(imd, FALSE);
1031
1032                 imd->draw_idle_id = -1;
1033                 return FALSE;
1034                 }
1035
1036         if (imd->draw_queue)
1037                 {
1038                 qd = imd->draw_queue->data;
1039                 fast = (two_pass_zoom && (GdkInterpType)zoom_quality != GDK_INTERP_NEAREST && imd->scale != 1.0);
1040                 }
1041         else
1042                 {
1043                 if (imd->il)
1044                         {
1045                         /* still loading, wait till done (also drops the higher priority) */
1046
1047                         imd->draw_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
1048                                                             image_queue_draw_idle_cb, imd, NULL);
1049                         imd->draw_idle_high = FALSE;
1050                         return FALSE;
1051                         }
1052                 qd = imd->draw_queue_2pass->data;
1053                 fast = FALSE;
1054                 }
1055
1056         if (GTK_WIDGET_REALIZED(imd->image))
1057                 {
1058                 if (image_tile_is_visible(imd, qd->it))
1059                         {
1060                         image_tile_expose(imd, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
1061                         }
1062                 else if (qd->new_data)
1063                         {
1064                         /* if new pixel data, and we already have a pixmap, update the tile */
1065                         qd->it->blank = FALSE;
1066                         if (qd->it->pixmap && qd->it->render_done == TILE_RENDER_ALL)
1067                                 {
1068                                 image_tile_render(imd, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
1069                                 }
1070                         }
1071                 }
1072
1073         if (imd->draw_queue)
1074                 {
1075                 imd->draw_queue = g_list_remove(imd->draw_queue, qd);
1076                 if (fast)
1077                         {
1078                         imd->draw_queue_2pass = g_list_append(imd->draw_queue_2pass, qd);
1079                         }
1080                 else
1081                         {
1082                         g_free(qd);
1083                         }
1084                 }
1085         else
1086                 {
1087                 imd->draw_queue_2pass = g_list_remove(imd->draw_queue_2pass, qd);
1088                 g_free(qd);
1089                 }
1090
1091         if (!imd->draw_queue && !imd->draw_queue_2pass)
1092                 {
1093                 if (!imd->completed) image_complete_util(imd, FALSE);
1094
1095                 imd->draw_idle_id = -1;
1096                 return FALSE;
1097                 }
1098
1099         return TRUE;
1100 }
1101
1102 static QueueData *image_queue_combine(ImageWindow *imd, QueueData *qd)
1103 {
1104         QueueData *found = NULL;
1105         GList *work;
1106
1107         work = imd->draw_queue;
1108         while (work && !found)
1109                 {
1110                 found = work->data;
1111                 work = work->next;
1112
1113                 if (found->it != qd->it) found = NULL;
1114                 }
1115
1116         if (found)
1117                 {
1118                 if (found->x + found->w < qd->x + qd->w) found->w += (qd->x + qd->w) - (found->x + found->w);
1119                 if (found->x > qd->x)
1120                         {
1121                         found->w += found->x - qd->x;
1122                         found->x = qd->x;
1123                         }
1124
1125                 if (found->y + found->h < qd->y + qd->h) found->h += (qd->y + qd->h) - (found->y + found->h);
1126                 if (found->y > qd->y)
1127                         {
1128                         found->h += found->y - qd->y;
1129                         found->y = qd->y;
1130                         }
1131                 found->new_data |= qd->new_data;
1132                 }
1133
1134         return found;
1135 }
1136
1137 static gint image_clamp_to_visible(ImageWindow *imd, gint *x, gint *y, gint *w, gint *h)
1138 {
1139         gint nx, ny;
1140         gint nw, nh;
1141         gint vx, vy;
1142         gint vw, vh;
1143
1144         vw = imd->vis_width;
1145         vh = imd->vis_height;
1146
1147         vx = imd->x_scroll;
1148         vy = imd->y_scroll;
1149
1150         if (*x + *w < vx || *x > vx + vw || *y + *h < vy || *y > vy + vh) return FALSE;
1151
1152         /* now clamp it */
1153         nx = CLAMP(*x, vx, vx + vw);
1154         nw = CLAMP(*w - (nx - *x), 1, vw);
1155
1156         ny = CLAMP(*y, vy, vy + vh);
1157         nh = CLAMP(*h - (ny - *y), 1, vh);
1158
1159         *x = nx;
1160         *y = ny;
1161         *w = nw;
1162         *h = nh;
1163
1164         return TRUE;
1165 }
1166
1167 static gint image_queue_to_tiles(ImageWindow *imd, gint x, gint y, gint w, gint h,
1168                                  gint clamp, TileRenderType render, gint new_data)
1169 {
1170         gint i, j;
1171         gint x1, x2;
1172         gint y1, y2;
1173         GList *work;
1174
1175         if (clamp && !image_clamp_to_visible(imd, &x, &y, &w, &h)) return FALSE;
1176
1177         x1 = (gint)floor(x / imd->tile_width) * imd->tile_width;
1178         x2 = (gint)ceil((x + w) / imd->tile_width) * imd->tile_width;
1179
1180         y1 = (gint)floor(y / imd->tile_height) * imd->tile_height;
1181         y2 = (gint)ceil((y + h) / imd->tile_height) * imd->tile_height;
1182
1183         work = g_list_nth(imd->tiles, y1 / imd->tile_height * imd->tile_cols + (x1 / imd->tile_width));
1184         for (j = y1; j <= y2; j += imd->tile_height)
1185                 {
1186                 GList *tmp;
1187                 tmp = work;
1188                 for (i = x1; i <= x2; i += imd->tile_width)
1189                         {
1190                         if (tmp)
1191                                 {
1192                                 ImageTile *it = tmp->data;
1193                                 QueueData *qd;
1194
1195                                 if ((render == TILE_RENDER_ALL && it->render_done != TILE_RENDER_ALL) ||
1196                                     (render == TILE_RENDER_AREA && it->render_todo != TILE_RENDER_ALL))
1197                                         {
1198                                         it->render_todo = render;
1199                                         }
1200
1201                                 qd = g_new(QueueData, 1);
1202                                 qd->it = it;
1203                                 qd->new_data = new_data;
1204
1205                                 if (i < x)
1206                                         {
1207                                         qd->x = x - i;
1208                                         }
1209                                 else
1210                                         {
1211                                         qd->x = 0;
1212                                         }
1213                                 qd->w = x + w - i - qd->x;
1214                                 if (qd->x + qd->w > imd->tile_width) qd->w = imd->tile_width - qd->x;
1215
1216
1217                                 if (j < y)
1218                                         {
1219                                         qd->y = y - j;
1220                                         }
1221                                 else
1222                                         {
1223                                         qd->y = 0;
1224                                         }
1225                                 qd->h = y + h - j - qd->y;
1226                                 if (qd->y + qd->h > imd->tile_height) qd->h = imd->tile_height - qd->y;
1227
1228                                 if (qd->w < 1 || qd->h < 1 || /* <--- sanity checks, rare cases cause this */
1229                                     image_queue_combine(imd, qd))
1230                                         {
1231                                         g_free(qd);
1232                                         }
1233                                 else
1234                                         {
1235                                         imd->draw_queue = g_list_append(imd->draw_queue, qd);
1236                                         }
1237
1238                                 tmp = tmp->next;
1239                                 }
1240                         }
1241                 work = g_list_nth(work, imd->tile_cols);        /* step 1 row */
1242                 }
1243
1244         return TRUE;
1245 }
1246
1247 static void image_queue(ImageWindow *imd, gint x, gint y, gint w, gint h,
1248                         gint clamp, TileRenderType render, gint new_data)
1249 {
1250         gint nx, ny;
1251
1252         nx = CLAMP(x, 0, imd->width - 1);
1253         ny = CLAMP(y, 0, imd->height - 1);
1254         w -= (nx - x);
1255         h -= (ny - y);
1256         w = CLAMP(w, 0, imd->width - nx);
1257         h = CLAMP(h, 0, imd->height - ny);
1258         if (w < 1 || h < 1) return;
1259
1260         if (image_queue_to_tiles(imd, nx, ny, w, h, clamp, render, new_data) &&
1261             ((!imd->draw_queue && !imd->draw_queue_2pass) || imd->draw_idle_id == -1 || !imd->draw_idle_high))
1262                 {
1263                 if (imd->draw_idle_id != -1) g_source_remove(imd->draw_idle_id);
1264                 imd->draw_idle_id = g_idle_add_full(GDK_PRIORITY_REDRAW,
1265                                                     image_queue_draw_idle_cb, imd, NULL);
1266                 imd->draw_idle_high = TRUE;
1267                 }
1268 }
1269
1270 static void image_queue_list_free(GList *list)
1271 {
1272         GList *work;
1273
1274         work = list;
1275         while (work)
1276                 {
1277                 QueueData *qd;
1278
1279                 qd = work->data;
1280                 work = work->next;
1281                 g_free(qd);
1282                 }
1283
1284         g_list_free(list);
1285 }
1286
1287 static void image_queue_clear(ImageWindow *imd)
1288 {
1289         image_queue_list_free(imd->draw_queue);
1290         imd->draw_queue = NULL;
1291
1292         image_queue_list_free(imd->draw_queue_2pass);
1293         imd->draw_queue_2pass = NULL;
1294
1295         if (imd->draw_idle_id != -1) g_source_remove(imd->draw_idle_id);
1296         imd->draw_idle_id = -1;
1297 }
1298
1299 /*
1300  *-------------------------------------------------------------------
1301  * core calculations
1302  *-------------------------------------------------------------------
1303  */
1304
1305 static gint image_top_window_sizable(ImageWindow *imd)
1306 {
1307         if (!imd->top_window) return FALSE;
1308         if (!fit_window) return FALSE;
1309         if (!imd->top_window_sync) return FALSE;
1310         if (!imd->image->window) return FALSE;
1311         if (window_maximized(imd->top_window)) return FALSE;
1312
1313         return TRUE;
1314 }
1315
1316 static gint image_size_top_window(ImageWindow *imd, gint w, gint h)
1317 {
1318         gint ww, wh;
1319
1320         if (!image_top_window_sizable(imd)) return FALSE;
1321
1322         if (limit_window_size)
1323                 {
1324                 gint sw = gdk_screen_width() * max_window_size / 100;
1325                 gint sh = gdk_screen_height() * max_window_size / 100;
1326
1327                 if (w > sw) w = sw;
1328                 if (h > sh) h = sh;
1329                 }
1330
1331         w += (imd->top_window->allocation.width - imd->image->allocation.width);
1332         h += (imd->top_window->allocation.height - imd->image->allocation.height);
1333
1334         gdk_drawable_get_size(imd->top_window->window, &ww, &wh);
1335         if (w == ww && h == wh) return FALSE;
1336
1337         gdk_window_resize(imd->top_window->window, w, h);
1338
1339         return TRUE;
1340 }
1341
1342 static void image_redraw(ImageWindow *imd, gint new_data)
1343 {
1344         image_queue_clear(imd);
1345         image_queue(imd, 0, 0, imd->width, imd->height, TRUE, TILE_RENDER_ALL, new_data);
1346 }
1347
1348 static void image_border_draw(ImageWindow *imd, gint x, gint y, gint w, gint h)
1349 {
1350         gint rx, ry, rw, rh;
1351
1352         if (!imd->image->window) return;
1353
1354         if (!imd->pixbuf && !imd->source_tiles_enabled)
1355                 {
1356                 if (util_clip_region(x, y, w, h,
1357                         0, 0,
1358                         imd->window_width, imd->window_height,
1359                         &rx, &ry, &rw, &rh))
1360                         {
1361                         gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1362                         image_overlay_draw(imd, rx, ry, rw, rh);
1363                         }
1364                 return;
1365                 }
1366
1367         if (imd->vis_width < imd->window_width)
1368                 {
1369                 if (imd->x_offset > 0 &&
1370                     util_clip_region(x, y, w, h,
1371                         0, 0,
1372                         imd->x_offset, imd->window_height,
1373                         &rx, &ry, &rw, &rh))
1374                         {
1375                         gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1376                         image_overlay_draw(imd, rx, ry, rw, rh);
1377                         }
1378                 if (imd->window_width - imd->vis_width - imd->x_offset > 0 &&
1379                     util_clip_region(x, y, w, h,
1380                         imd->x_offset + imd->vis_width, 0,
1381                         imd->window_width - imd->vis_width - imd->x_offset, imd->window_height,
1382                         &rx, &ry, &rw, &rh))
1383                         {
1384                         gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1385                         image_overlay_draw(imd, rx, ry, rw, rh);
1386                         }
1387                 }
1388         if (imd->vis_height < imd->window_height)
1389                 {
1390                 if (imd->y_offset > 0 &&
1391                     util_clip_region(x, y, w, h,
1392                         imd->x_offset, 0,
1393                         imd->vis_width, imd->y_offset,
1394                         &rx, &ry, &rw, &rh))
1395                         {
1396                         gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1397                         image_overlay_draw(imd, rx, ry, rw, rh);
1398                         }
1399                 if (imd->window_height - imd->vis_height - imd->y_offset > 0 &&
1400                     util_clip_region(x, y, w, h,
1401                         imd->x_offset, imd->y_offset + imd->vis_height,
1402                         imd->vis_width, imd->window_height - imd->vis_height - imd->y_offset,
1403                         &rx, &ry, &rw, &rh))
1404                         {
1405                         gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1406                         image_overlay_draw(imd, rx, ry, rw, rh);
1407                         }
1408                 }
1409 }
1410
1411 static void image_border_clear(ImageWindow *imd)
1412 {
1413         image_border_draw(imd, 0, 0, imd->window_width, imd->window_height);
1414 }
1415
1416 static void image_scroll_notify(ImageWindow *imd)
1417 {
1418         if (imd->func_scroll_notify && imd->scale)
1419                 {
1420                 imd->func_scroll_notify(imd,
1421                                         (gint)((gdouble)imd->x_scroll / imd->scale),
1422                                         (gint)((gdouble)imd->y_scroll / imd->scale),
1423                                         (gint)((gdouble)imd->image_width - imd->vis_width / imd->scale),
1424                                         (gint)((gdouble)imd->image_height - imd->vis_height / imd->scale),
1425                                         imd->data_scroll_notify);
1426                 }
1427 }
1428
1429 static gint image_scroll_clamp(ImageWindow *imd)
1430 {
1431         gint old_xs;
1432         gint old_ys;
1433
1434         if (imd->zoom == 0.0)
1435                 {
1436                 imd->x_scroll = 0;
1437                 imd->y_scroll = 0;
1438
1439                 image_scroll_notify(imd);
1440                 return FALSE;
1441                 }
1442
1443         old_xs = imd->x_scroll;
1444         old_ys = imd->y_scroll;
1445
1446         if (imd->x_offset > 0)
1447                 {
1448                 imd->x_scroll = 0;
1449                 }
1450         else
1451                 {
1452                 imd->x_scroll = CLAMP(imd->x_scroll, 0, imd->width - imd->vis_width);
1453                 }
1454
1455         if (imd->y_offset > 0)
1456                 {
1457                 imd->y_scroll = 0;
1458                 }
1459         else
1460                 {
1461                 imd->y_scroll = CLAMP(imd->y_scroll, 0, imd->height - imd->vis_height);
1462                 }
1463
1464         image_scroll_notify(imd);
1465
1466         return (old_xs != imd->x_scroll || old_ys != imd->y_scroll);
1467 }
1468
1469 static gint image_zoom_clamp(ImageWindow *imd, gdouble zoom, gint force, gint new)
1470 {
1471         gint w, h;
1472         gdouble scale;
1473
1474         zoom = CLAMP(zoom, imd->zoom_min, imd->zoom_max);
1475
1476         if (imd->zoom == zoom && !force) return FALSE;
1477
1478         w = imd->image_width;
1479         h = imd->image_height;
1480
1481         if (zoom == 0.0 && !imd->pixbuf)
1482                 {
1483                 scale = 1.0;
1484                 }
1485         else if (zoom == 0.0)
1486                 {
1487                 gint max_w;
1488                 gint max_h;
1489                 gint sizeable;
1490
1491                 sizeable = (new && image_top_window_sizable(imd));
1492
1493                 if (sizeable)
1494                         {
1495                         max_w = gdk_screen_width();
1496                         max_h = gdk_screen_height();
1497
1498                         if (limit_window_size)
1499                                 {
1500                                 max_w = max_w * max_window_size / 100;
1501                                 max_h = max_h * max_window_size / 100;
1502                                 }
1503                         }
1504                 else
1505                         {
1506                         max_w = imd->window_width;
1507                         max_h = imd->window_height;
1508                         }
1509
1510                 if ((zoom_to_fit_expands && !sizeable) || w > max_w || h > max_h)
1511                         {
1512                         if ((gdouble)max_w / w > (gdouble)max_h / h)
1513                                 {
1514                                 scale = (gdouble)max_h / h;
1515                                 h = max_h;
1516                                 w = w * scale + 0.5;
1517                                 if (w > max_w) w = max_w;
1518                                 }
1519                         else
1520                                 {
1521                                 scale = (gdouble)max_w / w;
1522                                 w = max_w;
1523                                 h = h * scale + 0.5;
1524                                 if (h > max_h) h = max_h;
1525                                 }
1526                         if (w < 1) w = 1;
1527                         if (h < 1) h = 1;
1528                         }
1529                 else
1530                         {
1531                         scale = 1.0;
1532                         }
1533                 }
1534         else if (zoom > 0.0) /* zoom orig, in */
1535                 {
1536                 scale = zoom;
1537                 w = w * scale;
1538                 h = h * scale;
1539                 }
1540         else /* zoom out */
1541                 {
1542                 scale = 1.0 / (0.0 - zoom);
1543                 w = w * scale;
1544                 h = h * scale;
1545                 }
1546
1547         imd->zoom = zoom;
1548         imd->width = w;
1549         imd->height = h;
1550         imd->scale = scale;
1551
1552         return TRUE;
1553 }
1554
1555 static gint image_size_clamp(ImageWindow *imd)
1556 {
1557         gint old_vw, old_vh;
1558
1559         old_vw = imd->vis_width;
1560         old_vh = imd->vis_height;
1561
1562         if (imd->width < imd->window_width)
1563                 {
1564                 imd->vis_width = imd->width;
1565                 imd->x_offset = (imd->window_width - imd->width) / 2;
1566                 }
1567         else
1568                 {
1569                 imd->vis_width = imd->window_width;
1570                 imd->x_offset = 0;
1571                 }
1572
1573         if (imd->height < imd->window_height)
1574                 {
1575                 imd->vis_height = imd->height;
1576                 imd->y_offset = (imd->window_height - imd->height) / 2;
1577                 }
1578         else
1579                 {
1580                 imd->vis_height = imd->window_height;
1581                 imd->y_offset = 0;
1582                 }
1583
1584         return (old_vw != imd->vis_width || old_vh != imd->vis_height);
1585 }
1586
1587 static void image_size_sync(ImageWindow *imd, gint new_width, gint new_height)
1588 {
1589         if (imd->window_width == new_width && imd->window_height == new_height) return;
1590
1591         imd->window_width = new_width;
1592         imd->window_height = new_height;
1593
1594         if (imd->zoom == 0.0) image_zoom_clamp(imd, 0.0, TRUE, FALSE);
1595
1596         image_size_clamp(imd);
1597         image_scroll_clamp(imd);
1598
1599 #if 0
1600         gtk_widget_set_size_request(imd->image, imd->window_width, imd->window_height);
1601 #endif
1602
1603         /* ensure scroller remains visible */
1604         if (imd->scroller_overlay != -1)
1605                 {
1606                 gint update = FALSE;
1607
1608                 if (imd->scroller_x > new_width)
1609                         {
1610                         imd->scroller_x = new_width;
1611                         imd->scroller_xpos = new_width;
1612                         update = TRUE;
1613                         }
1614                 if (imd->scroller_y > new_height)
1615                         {
1616                         imd->scroller_y = new_height;
1617                         imd->scroller_ypos = new_height;
1618                         update = TRUE;
1619                         }
1620
1621                 if (update)
1622                         {
1623                         GdkPixbuf *pixbuf;
1624
1625                         if (image_overlay_get(imd, imd->scroller_overlay, &pixbuf, NULL, NULL))
1626                                 {
1627                                 gint w, h;
1628
1629                                 w = gdk_pixbuf_get_width(pixbuf);
1630                                 h = gdk_pixbuf_get_height(pixbuf);
1631                                 image_overlay_set(imd, imd->scroller_overlay, pixbuf,
1632                                                   imd->scroller_x - w / 2, imd->scroller_y - h / 2);
1633                                 }
1634                         }
1635                 }
1636
1637         /* clear any borders */
1638         image_border_clear(imd);
1639         
1640         image_tile_sync(imd, imd->width, imd->height, FALSE);
1641 #if 0
1642         /* no longer needed? (expose event should be doing this for us) */
1643         image_redraw(imd, FALSE);
1644 #endif
1645
1646         if (imd->title_show_zoom) image_update_title(imd);
1647         image_update_util(imd);
1648 }
1649
1650 /*
1651  *-------------------------------------------------------------------
1652  * misc
1653  *-------------------------------------------------------------------
1654  */
1655
1656 static void image_update_title(ImageWindow *imd)
1657 {
1658         gchar *title = NULL;
1659         gchar *zoom = NULL;
1660         gchar *collection = NULL;
1661
1662         if (!imd->top_window) return;
1663
1664         if (imd->collection && collection_to_number(imd->collection) >= 0)
1665                 {
1666                 const gchar *name;
1667                 name = imd->collection->name;
1668                 if (!name) name = _("Untitled");
1669                 collection = g_strdup_printf(" (Collection %s)", name);
1670                 }
1671
1672         if (imd->title_show_zoom)
1673                 {
1674                 gchar *buf = image_zoom_get_as_text(imd);
1675                 zoom = g_strconcat(" [", buf, "]", NULL);
1676                 g_free(buf);
1677                 }
1678
1679         title = g_strdup_printf("%s%s%s%s%s%s",
1680                 imd->title ? imd->title : "",
1681                 imd->image_name ? imd->image_name : "",
1682                 zoom ? zoom : "",
1683                 collection ? collection : "",
1684                 imd->image_name ? " - " : "",
1685                 imd->title_right ? imd->title_right : "");
1686
1687         gtk_window_set_title(GTK_WINDOW(imd->top_window), title);
1688
1689         g_free(title);
1690         g_free(zoom);
1691         g_free(collection);
1692 }
1693
1694 static void image_update_util(ImageWindow *imd)
1695 {
1696         if (imd->func_update) imd->func_update(imd, imd->data_update);
1697 }
1698
1699 static void image_complete_util(ImageWindow *imd, gint preload)
1700 {
1701         if (imd->il && imd->pixbuf != image_loader_get_pixbuf(imd->il)) return;
1702
1703         if (debug) printf("image load completed \"%s\" (%s)\n",
1704                           (preload) ? imd->read_ahead_path : imd->image_path,
1705                           (preload) ? "preload" : "current");
1706
1707         if (!preload) imd->completed = TRUE;
1708         if (imd->func_complete) imd->func_complete(imd, preload, imd->data_complete);
1709 }
1710
1711 static void image_new_util(ImageWindow *imd)
1712 {
1713         if (imd->func_new) imd->func_new(imd, imd->data_new);
1714 }
1715
1716 static void image_scroll_real(ImageWindow *imd, gint x, gint y)
1717 {
1718         gint old_x, old_y;
1719         gint x_off, y_off;
1720         gint w, h;
1721
1722         if (!imd->pixbuf && !imd->source_tiles_enabled) return;
1723
1724         old_x = imd->x_scroll;
1725         old_y = imd->y_scroll;
1726
1727         imd->x_scroll += x;
1728         imd->y_scroll += y;
1729
1730         image_scroll_clamp(imd);
1731         if (imd->x_scroll == old_x && imd->y_scroll == old_y) return;
1732
1733         if (imd->overlay_list)
1734                 {
1735                 gint new_x, new_y;
1736
1737                 new_x = imd->x_scroll;
1738                 new_y = imd->y_scroll;
1739                 imd->x_scroll = old_x;
1740                 imd->y_scroll = old_y;
1741                 image_overlay_queue_all(imd);
1742                 imd->x_scroll = new_x;
1743                 imd->y_scroll = new_y;
1744                 }
1745
1746         x_off = imd->x_scroll - old_x;
1747         y_off = imd->y_scroll - old_y;
1748
1749         w = imd->vis_width - abs(x_off);
1750         h = imd->vis_height - abs(y_off);
1751
1752         if (w < 1 || h < 1)
1753                 {
1754                 /* scrolled completely to new material */
1755                 image_queue(imd, 0, 0, imd->width, imd->height, TRUE, TILE_RENDER_ALL, FALSE);
1756                 return;
1757                 }
1758         else
1759                 {
1760                 gint x1, y1;
1761                 gint x2, y2;
1762                 GdkGC *gc;
1763
1764                 if (x_off < 0)
1765                         {
1766                         x1 = abs(x_off);
1767                         x2 = 0;
1768                         }
1769                 else
1770                         {
1771                         x1 = 0;
1772                         x2 = abs(x_off);
1773                         }
1774
1775                 if (y_off < 0)
1776                         {
1777                         y1 = abs(y_off);
1778                         y2 = 0;
1779                         }
1780                 else
1781                         {
1782                         y1 = 0;
1783                         y2 = abs(y_off);
1784                         }
1785
1786                 gc = gdk_gc_new(imd->image->window);
1787                 gdk_gc_set_exposures(gc, TRUE);
1788                 gdk_draw_drawable(imd->image->window, gc,
1789                                   imd->image->window,
1790                                   x2 + imd->x_offset, y2 + imd->y_offset,
1791                                   x1 + imd->x_offset, y1 + imd->y_offset, w, h);
1792                 g_object_unref(gc);
1793
1794                 if (imd->overlay_list)
1795                         {
1796                         image_overlay_queue_all(imd);
1797                         }
1798
1799                 w = imd->vis_width - w;
1800                 h = imd->vis_height - h;
1801
1802                 if (w > 0)
1803                         {
1804                         image_queue(imd,
1805                                     x_off > 0 ? imd->x_scroll + (imd->vis_width - w) : imd->x_scroll, imd->y_scroll,
1806                                     w, imd->vis_height, TRUE, TILE_RENDER_ALL, FALSE);
1807                         }
1808                 if (h > 0)
1809                         {
1810                         /* FIXME, to optimize this, remove overlap */
1811                         image_queue(imd,
1812                                     imd->x_scroll, y_off > 0 ? imd->y_scroll + (imd->vis_height - h) : imd->y_scroll,
1813                                     imd->vis_width, h, TRUE, TILE_RENDER_ALL, FALSE);
1814                         }
1815                 }
1816 }
1817
1818 static void widget_set_cursor(GtkWidget *widget, gint icon)
1819 {
1820         GdkCursor *cursor;
1821
1822         if (!widget->window) return;
1823
1824         if (icon == -1)
1825                 {
1826                 cursor = NULL;
1827                 }
1828         else
1829                 {
1830                 cursor = gdk_cursor_new (icon);
1831                 }
1832
1833         gdk_window_set_cursor(widget->window, cursor);
1834
1835         if (cursor) gdk_cursor_unref(cursor);
1836 }
1837
1838 /*
1839  *-------------------------------------------------------------------
1840  * image pixbuf handling
1841  *-------------------------------------------------------------------
1842  */
1843
1844 static void image_zoom_sync(ImageWindow *imd, gdouble zoom,
1845                             gint force, gint blank, gint new,
1846                             gint center_point, gint px, gint py)
1847 {
1848         gdouble old_scale;
1849         gint old_cx, old_cy;
1850         gint clamped;
1851         gint sized;
1852
1853         old_scale = imd->scale;
1854         if (center_point)
1855                 {
1856                 px = CLAMP(px, 0, imd->width);
1857                 py = CLAMP(py, 0, imd->height);
1858                 old_cx = imd->x_scroll + (px - imd->x_offset);
1859                 old_cy = imd->y_scroll + (py - imd->y_offset);
1860                 }
1861         else
1862                 {
1863                 px = py = 0;
1864                 old_cx = imd->x_scroll + imd->vis_width / 2;
1865                 old_cy = imd->y_scroll + imd->vis_height / 2;
1866                 }
1867
1868         if (!image_zoom_clamp(imd, zoom, force, new)) return;
1869
1870         clamped = image_size_clamp(imd);
1871         sized = image_size_top_window(imd, imd->width, imd->height);
1872
1873         if (force)
1874                 {
1875                 /* force means new image, so update scroll offset per options */
1876                 switch (scroll_reset_method)
1877                         {
1878                         case SCROLL_RESET_NOCHANGE:
1879                                 /* maintain old scroll position, do nothing */
1880                                 break;
1881                         case SCROLL_RESET_CENTER:
1882                                 /* center new image */
1883                                 imd->x_scroll = ((double)imd->image_width / 2.0 * imd->scale) - imd->vis_width / 2;
1884                                 imd->y_scroll = ((double)imd->image_height / 2.0 * imd->scale) - imd->vis_height / 2;
1885                                 break;
1886                         case SCROLL_RESET_TOPLEFT:
1887                         default:
1888                                 /* reset to upper left */
1889                                 imd->x_scroll = 0;
1890                                 imd->y_scroll = 0;
1891                                 break;
1892                         }
1893                 }
1894         else
1895                 {
1896                 /* user zoom does not force, so keep visible center point */
1897                 if (center_point)
1898                         {
1899                         imd->x_scroll = old_cx / old_scale * imd->scale - (px - imd->x_offset);
1900                         imd->y_scroll = old_cy / old_scale * imd->scale - (py - imd->y_offset);
1901                         }
1902                 else
1903                         {
1904                         imd->x_scroll = old_cx / old_scale * imd->scale - (imd->vis_width / 2);
1905                         imd->y_scroll = old_cy / old_scale * imd->scale - (imd->vis_height / 2);
1906                         }
1907                 }
1908         image_scroll_clamp(imd);
1909
1910         image_tile_sync(imd, imd->width, imd->height, blank);
1911
1912         /* If the window was not sized, redraw the image - we know there will be no size/expose signal.
1913          * But even if a size is claimed, there is no guarantee that the window manager will allow it,
1914          * so redraw the window anyway :/
1915          */
1916         if (sized || clamped) image_border_clear(imd);
1917         image_redraw(imd, FALSE);
1918
1919         if (imd->title_show_zoom) image_update_title(imd);
1920         image_update_util(imd);
1921 }
1922
1923 static void image_pixbuf_sync(ImageWindow *imd, gdouble zoom, gint blank, gint new)
1924 {
1925         if (!imd->pixbuf)
1926                 {
1927                 /* no pixbuf so just clear the window */
1928                 imd->image_width = 0;
1929                 imd->image_height = 0;
1930                 imd->scale = 1.0;
1931
1932                 if (imd->image->window)
1933                         {
1934                         gdk_window_clear(imd->image->window);
1935                         image_overlay_draw(imd, 0, 0, imd->window_width, imd->window_height);
1936                         }
1937
1938                 image_update_util(imd);
1939                 
1940                 return;
1941                 }
1942
1943         imd->image_width = gdk_pixbuf_get_width(imd->pixbuf);
1944         imd->image_height = gdk_pixbuf_get_height(imd->pixbuf);
1945
1946 #if 0
1947         /* reset scrolling */
1948         imd->x_scroll = 0;
1949         imd->y_scroll = 0;
1950 #endif
1951
1952         image_zoom_sync(imd, zoom, TRUE, blank, new, FALSE, 0, 0);
1953 }
1954
1955 static void image_set_pixbuf(ImageWindow *imd, GdkPixbuf *pixbuf, gdouble zoom, gint new)
1956 {
1957         if (pixbuf) g_object_ref(pixbuf);
1958         if (imd->pixbuf) g_object_unref(imd->pixbuf);
1959         imd->pixbuf = pixbuf;
1960
1961         image_pixbuf_sync(imd, zoom, FALSE, new);
1962 }
1963
1964 static void image_alter_real(ImageWindow *imd, AlterType type, gint clamp)
1965 {
1966         GdkPixbuf *new = NULL;
1967         gint x, y;
1968         gint t;
1969
1970         imd->delay_alter_type = ALTER_NONE;
1971
1972         if (!imd->pixbuf) return;
1973
1974         x = imd->x_scroll + (imd->vis_width / 2);
1975         y = imd->y_scroll + (imd->vis_height / 2);
1976
1977         switch (type)
1978                 {
1979                 case ALTER_ROTATE_90:
1980                         new = pixbuf_copy_rotate_90(imd->pixbuf, FALSE);
1981                         t = x;
1982                         x = imd->height - y;
1983                         y = t;
1984                         break;
1985                 case ALTER_ROTATE_90_CC:
1986                         new = pixbuf_copy_rotate_90(imd->pixbuf, TRUE);
1987                         t = x;
1988                         x = y;
1989                         y = imd->width - t;
1990                         break;
1991                 case ALTER_ROTATE_180:
1992                         new = pixbuf_copy_mirror(imd->pixbuf, TRUE, TRUE);
1993                         x = imd->width - x;
1994                         y = imd->height - y;
1995                         break;
1996                 case ALTER_MIRROR:
1997                         new = pixbuf_copy_mirror(imd->pixbuf, TRUE, FALSE);
1998                         x = imd->width - x;
1999                         break;
2000                 case ALTER_FLIP:
2001                         new = pixbuf_copy_mirror(imd->pixbuf, FALSE, TRUE);
2002                         y = imd->height - y;
2003                         break;
2004                 case ALTER_NONE:
2005                 default:
2006                         return;
2007                         break;
2008                 }
2009
2010         if (!new) return;
2011
2012         if (clamp)
2013                 {
2014                 image_set_pixbuf(imd, new, imd->zoom, TRUE);
2015                 g_object_unref(new);
2016
2017                 if (imd->zoom != 0.0)
2018                         {
2019                         image_scroll(imd, x - (imd->vis_width / 2), y - (imd->vis_height / 2));
2020                         }
2021                 }
2022         else
2023                 {
2024                 g_object_unref(imd->pixbuf);
2025                 imd->pixbuf = new;
2026                 }
2027 }
2028
2029 static void image_post_process(ImageWindow *imd, gint clamp)
2030 {
2031         if (exif_rotate_enable && imd->pixbuf)
2032                 {
2033                 ExifData *ed;
2034                 gint orientation;
2035
2036                 ed = exif_read(imd->image_path);
2037                 if (ed && exif_get_integer(ed, "Orientation", &orientation))
2038                         {
2039                         /* see http://jpegclub.org/exif_orientation.html 
2040                           1        2       3      4         5            6           7          8
2041
2042                         888888  888888      88  88      8888888888  88                  88  8888888888
2043                         88          88      88  88      88  88      88  88          88  88      88  88
2044                         8888      8888    8888  8888    88          8888888888  8888888888          88
2045                         88          88      88  88
2046                         88          88  888888  888888
2047                         */
2048
2049                         switch (orientation)
2050                                 {
2051                                 case EXIF_ORIENTATION_TOP_LEFT:
2052                                         /* normal -- nothing to do */
2053                                         break;
2054                                 case EXIF_ORIENTATION_TOP_RIGHT:
2055                                         /* mirrored */
2056                                         imd->delay_alter_type = ALTER_MIRROR;
2057                                         break;
2058                                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
2059                                         /* upside down */
2060                                         imd->delay_alter_type = ALTER_ROTATE_180;
2061                                         break;
2062                                 case EXIF_ORIENTATION_BOTTOM_LEFT:
2063                                         /* flipped */
2064                                         imd->delay_alter_type = ALTER_FLIP;
2065                                         break;
2066                                 case EXIF_ORIENTATION_LEFT_TOP:
2067                                         /* not implemented -- too wacky to fix in one step */
2068                                         break;
2069                                 case EXIF_ORIENTATION_RIGHT_TOP:
2070                                         /* rotated -90 (270) */
2071                                         imd->delay_alter_type = ALTER_ROTATE_90;
2072                                         break;
2073                                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
2074                                         /* not implemented -- too wacky to fix in one step */
2075                                         break;
2076                                 case EXIF_ORIENTATION_LEFT_BOTTOM:
2077                                         /* rotated 90 */
2078                                         imd->delay_alter_type = ALTER_ROTATE_90_CC;
2079                                         break;
2080                                 default:
2081                                         /* The other values are out of range */
2082                                         break;
2083                                 }
2084                         }
2085                 exif_free(ed);
2086                 }
2087
2088         if (imd->delay_alter_type != ALTER_NONE)
2089                 {
2090                 image_alter_real(imd, imd->delay_alter_type, clamp);
2091                 }
2092 }
2093
2094 /*
2095  *-------------------------------------------------------------------
2096  * read ahead (prebuffer)
2097  *-------------------------------------------------------------------
2098  */
2099
2100 static void image_read_ahead_cancel(ImageWindow *imd)
2101 {
2102         if (debug) printf("read ahead cancelled for :%s\n", imd->read_ahead_path);
2103
2104         image_loader_free(imd->read_ahead_il);
2105         imd->read_ahead_il = NULL;
2106
2107         if (imd->read_ahead_pixbuf) g_object_unref(imd->read_ahead_pixbuf);
2108         imd->read_ahead_pixbuf = NULL;
2109
2110         g_free(imd->read_ahead_path);
2111         imd->read_ahead_path = NULL;
2112 }
2113
2114 static void image_read_ahead_done_cb(ImageLoader *il, gpointer data)
2115 {
2116         ImageWindow *imd = data;
2117
2118         if (debug) printf("read ahead done for :%s\n", imd->read_ahead_path);
2119
2120         imd->read_ahead_pixbuf = image_loader_get_pixbuf(imd->read_ahead_il);
2121         if (imd->read_ahead_pixbuf)
2122                 {
2123                 g_object_ref(imd->read_ahead_pixbuf);
2124                 }
2125         else
2126                 {
2127                 imd->read_ahead_pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
2128                 }
2129         image_loader_free(imd->read_ahead_il);
2130         imd->read_ahead_il = NULL;
2131
2132         image_complete_util(imd, TRUE);
2133 }
2134
2135 static void image_read_ahead_error_cb(ImageLoader *il, gpointer data)
2136 {
2137         /* we even treat errors as success, maybe at least some of the file was ok */
2138         image_read_ahead_done_cb(il, data);
2139 }
2140
2141 static void image_read_ahead_start(ImageWindow *imd)
2142 {
2143         /* already started ? */
2144         if (!imd->read_ahead_path || imd->read_ahead_il || imd->read_ahead_pixbuf) return;
2145
2146         /* still loading ?, do later */
2147         if (imd->il) return;
2148
2149         if (debug) printf("read ahead started for :%s\n", imd->read_ahead_path);
2150
2151         imd->read_ahead_il = image_loader_new(imd->read_ahead_path);
2152
2153         image_loader_set_error_func(imd->read_ahead_il, image_read_ahead_error_cb, imd);
2154         if (!image_loader_start(imd->read_ahead_il, image_read_ahead_done_cb, imd))
2155                 {
2156                 image_read_ahead_cancel(imd);
2157                 image_complete_util(imd, TRUE);
2158                 }
2159 }
2160
2161 static void image_read_ahead_set(ImageWindow *imd, const gchar *path)
2162 {
2163         if (imd->read_ahead_path && path && strcmp(imd->read_ahead_path, path) == 0) return;
2164
2165         image_read_ahead_cancel(imd);
2166
2167         imd->read_ahead_path = g_strdup(path);
2168
2169         if (debug) printf("read ahead set to :%s\n", imd->read_ahead_path);
2170
2171         image_read_ahead_start(imd);
2172 }
2173
2174 /*
2175  *-------------------------------------------------------------------
2176  * post buffering
2177  *-------------------------------------------------------------------
2178  */
2179
2180 static void image_post_buffer_set(ImageWindow *imd, const gchar *path, GdkPixbuf *pixbuf)
2181 {
2182         g_free(imd->prev_path);
2183         if (imd->prev_pixbuf) g_object_unref(imd->prev_pixbuf);
2184
2185         if (path && pixbuf)
2186                 {
2187                 imd->prev_path = g_strdup(path);
2188                         
2189                 g_object_ref(pixbuf);
2190                 imd->prev_pixbuf = pixbuf;
2191                 }
2192         else
2193                 {
2194                 imd->prev_path = NULL;
2195                 imd->prev_pixbuf = NULL;
2196                 }
2197
2198         if (debug) printf("post buffer set: %s\n", path);
2199 }
2200
2201 static gint image_post_buffer_get(ImageWindow *imd)
2202 {
2203         gint success;
2204
2205         if (imd->prev_pixbuf &&
2206             imd->image_path && imd->prev_path && strcmp(imd->image_path, imd->prev_path) == 0)
2207                 {
2208                 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2209                 imd->pixbuf = imd->prev_pixbuf;
2210                 success = TRUE;
2211                 }
2212         else
2213                 {
2214                 if (imd->prev_pixbuf) g_object_unref(imd->prev_pixbuf);
2215                 success = FALSE;
2216                 }
2217
2218         imd->prev_pixbuf = NULL;
2219
2220         g_free(imd->prev_path);
2221         imd->prev_path = NULL;
2222
2223         return success;
2224 }
2225
2226 /*
2227  *-------------------------------------------------------------------
2228  * loading
2229  *-------------------------------------------------------------------
2230  */
2231
2232 static void image_load_pixbuf_ready(ImageWindow *imd)
2233 {
2234         if (imd->pixbuf || !imd->il) return;
2235
2236         imd->pixbuf = image_loader_get_pixbuf(imd->il);
2237
2238         if (imd->pixbuf) g_object_ref(imd->pixbuf);
2239
2240         image_pixbuf_sync(imd, imd->zoom, TRUE, TRUE);
2241 }
2242
2243 static void image_load_area_cb(ImageLoader *il, guint x, guint y, guint w, guint h, gpointer data)
2244 {
2245         ImageWindow *imd = data;
2246
2247         if (imd->delay_flip &&
2248             imd->pixbuf != image_loader_get_pixbuf(il))
2249                 {
2250                 return;
2251                 }
2252
2253         if (!imd->pixbuf) image_load_pixbuf_ready(imd);
2254
2255         if (imd->scale != 1.0)
2256                 {
2257                 x = (guint) floor((double)x * imd->scale);
2258                 y = (guint) floor((double)y * imd->scale);
2259                 w = (guint) ceil((double)w * imd->scale);
2260                 h = (guint) ceil((double)h * imd->scale);
2261
2262                 if (w == 0) w = 1;
2263                 if (h == 0) h = 1;
2264
2265                 if ((GdkInterpType)zoom_quality != GDK_INTERP_NEAREST)
2266                         {
2267                         /* some scaling types use surrounding pixels to smooth the image,
2268                          * this will expand the new area to cover up for faint black
2269                          * lines caused by previous renders with non-complete image
2270                          */
2271                         y -= 1;
2272                         h += 2;
2273                         }
2274
2275                 }
2276
2277         image_queue(imd, (gint) x, (gint) y, (gint) w, (gint) h, FALSE, TILE_RENDER_AREA, TRUE);
2278 }
2279
2280 static void image_load_done_cb(ImageLoader *il, gpointer data)
2281 {
2282         ImageWindow *imd = data;
2283
2284         if (debug) printf ("image done\n");
2285
2286         if (imd->delay_flip &&
2287             imd->pixbuf != image_loader_get_pixbuf(imd->il))
2288                 {
2289                 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2290                 imd->pixbuf = image_loader_get_pixbuf(imd->il);
2291                 if (imd->pixbuf) g_object_ref(imd->pixbuf);
2292                 image_pixbuf_sync(imd, imd->zoom, FALSE, TRUE);
2293                 }
2294
2295         image_loader_free(imd->il);
2296         imd->il = NULL;
2297
2298         image_post_process(imd, TRUE);
2299
2300         image_read_ahead_start(imd);
2301 }
2302
2303 static void image_load_error_cb(ImageLoader *il, gpointer data)
2304 {
2305         if (debug) printf ("image error\n");
2306
2307         /* even on error handle it like it was done,
2308          * since we have a pixbuf with _something_ */
2309
2310         image_load_done_cb(il, data);
2311 }
2312
2313 #ifdef IMAGE_THROTTLE_LARGER_IMAGES
2314 static void image_load_buffer_throttle(ImageLoader *il)
2315 {
2316         if (!il || il->bytes_total < IMAGE_THROTTLE_THRESHOLD) return;
2317
2318         /* Larger image files usually have larger chunks of data per pixel...
2319          * So increase the buffer read size so that the rendering chunks called
2320          * are also larger.
2321          */
2322
2323         image_loader_set_buffer_size(il, IMAGE_LOAD_BUFFER_COUNT * IMAGE_THROTTLE_FACTOR);
2324 }
2325 #endif
2326
2327 /* this read ahead is located here merely for the callbacks, above */
2328
2329 static gint image_read_ahead_check(ImageWindow *imd)
2330 {
2331         if (!imd->read_ahead_path) return FALSE;
2332         if (imd->il) return FALSE;
2333
2334         if (!imd->image_path || strcmp(imd->read_ahead_path, imd->image_path) != 0)
2335                 {
2336                 image_read_ahead_cancel(imd);
2337                 return FALSE;
2338                 }
2339
2340         if (imd->read_ahead_il)
2341                 {
2342                 imd->il = imd->read_ahead_il;
2343                 imd->read_ahead_il = NULL;
2344
2345                 /* override the old signals */
2346                 image_loader_set_area_ready_func(imd->il, image_load_area_cb, imd);
2347                 image_loader_set_error_func(imd->il, image_load_error_cb, imd);
2348                 image_loader_set_buffer_size(imd->il, IMAGE_LOAD_BUFFER_COUNT);
2349
2350 #ifdef IMAGE_THROTTLE_LARGER_IMAGES
2351                 image_load_buffer_throttle(imd->il);
2352 #endif
2353
2354                 /* do this one directly (probably should add a set func) */
2355                 imd->il->func_done = image_load_done_cb;
2356
2357                 if (!imd->delay_flip)
2358                         {
2359                         if (imd->pixbuf) g_object_unref(imd->pixbuf);
2360                         imd->pixbuf = image_loader_get_pixbuf(imd->il);
2361                         if (imd->pixbuf) g_object_ref(imd->pixbuf);
2362                         }
2363
2364                 image_read_ahead_cancel(imd);
2365                 return TRUE;
2366                 }
2367         else if (imd->read_ahead_pixbuf)
2368                 {
2369                 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2370                 imd->pixbuf = imd->read_ahead_pixbuf;
2371                 imd->read_ahead_pixbuf = NULL;
2372
2373                 image_read_ahead_cancel(imd);
2374
2375                 image_post_process(imd, FALSE);
2376                 return TRUE;
2377                 }
2378
2379         image_read_ahead_cancel(imd);
2380         return FALSE;
2381 }
2382
2383 static gint image_load_begin(ImageWindow *imd, const gchar *path)
2384 {
2385         if (debug) printf ("image begin \n");
2386
2387         if (imd->il) return FALSE;
2388
2389         imd->completed = FALSE;
2390
2391         if (image_post_buffer_get(imd))
2392                 {
2393                 if (debug) printf("from post buffer: %s\n", imd->image_path);
2394
2395                 image_pixbuf_sync(imd, imd->zoom, FALSE, TRUE);
2396                 return TRUE;
2397                 }
2398
2399         if (image_read_ahead_check(imd))
2400                 {
2401                 if (debug) printf("from read ahead buffer: %s\n", imd->image_path);
2402
2403                 if (!imd->delay_flip || !imd->il) image_pixbuf_sync(imd, imd->zoom, FALSE, TRUE);
2404                 return TRUE;
2405                 }
2406
2407         if (!imd->delay_flip && imd->pixbuf)
2408                 {
2409                 g_object_unref(imd->pixbuf);
2410                 imd->pixbuf = NULL;
2411                 }
2412
2413         imd->il = image_loader_new(path);
2414
2415         image_loader_set_area_ready_func(imd->il, image_load_area_cb, imd);
2416         image_loader_set_error_func(imd->il, image_load_error_cb, imd);
2417         image_loader_set_buffer_size(imd->il, IMAGE_LOAD_BUFFER_COUNT);
2418
2419         if (!image_loader_start(imd->il, image_load_done_cb, imd))
2420                 {
2421                 if (debug) printf("image start error\n");
2422
2423                 image_loader_free(imd->il);
2424                 imd->il = NULL;
2425
2426                 image_complete_util(imd, FALSE);
2427
2428                 return FALSE;
2429                 }
2430
2431 #ifdef IMAGE_THROTTLE_LARGER_IMAGES
2432         image_load_buffer_throttle(imd->il);
2433 #endif
2434
2435         if (!imd->delay_flip && !imd->pixbuf) image_load_pixbuf_ready(imd);
2436
2437         return TRUE;
2438 }
2439
2440 static void image_reset(ImageWindow *imd)
2441 {
2442         /* stops anything currently being done */
2443
2444         if (debug) printf("image reset\n");
2445
2446         image_loader_free(imd->il);
2447         imd->il = NULL;
2448
2449         image_queue_clear(imd);
2450         imd->delay_alter_type = ALTER_NONE;
2451 }
2452
2453 /*
2454  *-------------------------------------------------------------------
2455  * image changer
2456  *-------------------------------------------------------------------
2457  */
2458
2459 static void image_change_complete(ImageWindow *imd, gdouble zoom, gint new)
2460 {
2461         gint sync = TRUE;
2462
2463         image_source_tile_unset(imd);
2464
2465         imd->zoom = zoom;       /* store the zoom, needed by the loader */
2466
2467         image_reset(imd);
2468
2469         if (imd->image_path && isfile(imd->image_path))
2470                 {
2471                 if (image_load_begin(imd, imd->image_path))
2472                         {
2473                         imd->unknown = FALSE;
2474                         sync = FALSE;
2475                         }
2476                 else
2477                         {
2478                         if (imd->pixbuf) g_object_unref(imd->pixbuf);
2479                         imd->pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
2480                         imd->unknown = TRUE;
2481                         }
2482                 imd->size = filesize(imd->image_path);
2483                 imd->mtime = filetime(imd->image_path);
2484                 }
2485         else
2486                 {
2487                 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2488                 imd->pixbuf = NULL;
2489
2490                 if (imd->image_path)
2491                         {
2492                         imd->pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
2493                         imd->mtime = filetime(imd->image_path);
2494                         }
2495                 else
2496                         {
2497                         imd->pixbuf = NULL;
2498                         imd->mtime = 0;
2499                         }
2500                 imd->unknown = TRUE;
2501                 imd->size = 0;
2502                 }
2503
2504         if (sync)
2505                 {
2506                 image_pixbuf_sync(imd, zoom, FALSE, new);
2507                 }
2508         else
2509                 {
2510                 image_update_util(imd);
2511                 }
2512 }
2513
2514 static void image_change_real(ImageWindow *imd, const gchar *path,
2515                               CollectionData *cd, CollectInfo *info, gdouble zoom)
2516 {
2517         GdkPixbuf *prev_pixbuf = NULL;
2518         gchar *prev_path = NULL;
2519         gint prev_clear = FALSE;
2520
2521         imd->collection = cd;
2522         imd->collection_info = info;
2523
2524         if (enable_read_ahead && imd->image_path && imd->pixbuf)
2525                 {
2526                 if (imd->il)
2527                         {
2528                         /* current image is not finished */
2529                         prev_clear = TRUE;
2530                         }
2531                 else
2532                         {
2533                         prev_path = g_strdup(imd->image_path);
2534                         prev_pixbuf = imd->pixbuf;
2535                         g_object_ref(prev_pixbuf);
2536                         }
2537                 }
2538
2539         g_free(imd->image_path);
2540         imd->image_path = g_strdup(path);
2541         imd->image_name = filename_from_path(imd->image_path);
2542
2543         image_change_complete(imd, zoom, TRUE);
2544
2545         if (prev_pixbuf)
2546                 {
2547                 image_post_buffer_set(imd, prev_path, prev_pixbuf);
2548                 g_free(prev_path);
2549                 g_object_unref(prev_pixbuf);
2550                 }
2551         else if (prev_clear)
2552                 {
2553                 image_post_buffer_set(imd, NULL, NULL);
2554                 }
2555
2556         image_update_title(imd);
2557         image_new_util(imd);
2558 }
2559
2560 /*
2561  *-------------------------------------------------------------------
2562  * callbacks
2563  *-------------------------------------------------------------------
2564  */
2565
2566 static gint image_expose_cb(GtkWidget *widget, GdkEventExpose *event, gpointer data)
2567 {
2568         gint x, y;
2569
2570         ImageWindow *imd = data;
2571
2572         image_border_draw(imd, event->area.x, event->area.y,
2573                           event->area.width, event->area.height);
2574
2575         /* image */
2576         x = MAX(0, (gint)event->area.x - imd->x_offset + imd->x_scroll);
2577         y = MAX(0, (gint)event->area.y - imd->y_offset + imd->y_scroll);
2578
2579         image_queue(imd, x, y,
2580                     MIN((gint)event->area.width, imd->width - x),
2581                     MIN((gint)event->area.height, imd->height - y),
2582                     FALSE, TILE_RENDER_ALL, FALSE);
2583
2584         return TRUE;
2585 }
2586
2587 static void image_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data)
2588 {
2589         ImageWindow *imd = data;
2590
2591         image_size_sync(imd, allocation->width, allocation->height);
2592 }
2593
2594 /*
2595  *-------------------------------------------------------------------
2596  * focus stuff
2597  *-------------------------------------------------------------------
2598  */
2599
2600 static void image_focus_paint(ImageWindow *imd, gint has_focus, GdkRectangle *area)
2601 {
2602         GtkWidget *widget;
2603
2604         widget = imd->widget;
2605         if (!widget->window) return;
2606
2607         if (has_focus)
2608                 {
2609                 gtk_paint_focus (widget->style, widget->window, GTK_STATE_ACTIVE,
2610                                  area, widget, "image_window",
2611                                  widget->allocation.x, widget->allocation.y,
2612                                  widget->allocation.width - 1, widget->allocation.height - 1);  
2613                 }
2614         else
2615                 {
2616                 gtk_paint_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN,
2617                                   area, widget, "image_window",
2618                                   widget->allocation.x, widget->allocation.y,
2619                                   widget->allocation.width - 1, widget->allocation.height - 1);
2620                 }
2621 }
2622
2623 static gint image_focus_expose(GtkWidget *widget, GdkEventExpose *event, gpointer data)
2624 {
2625         ImageWindow *imd = data;
2626
2627         image_focus_paint(imd, GTK_WIDGET_HAS_FOCUS(widget), &event->area);
2628         return TRUE;
2629 }
2630
2631 static gint image_focus_in_cb(GtkWidget *widget, GdkEventFocus *event, gpointer data)
2632 {
2633         ImageWindow *imd = data;
2634
2635         GTK_WIDGET_SET_FLAGS(imd->widget, GTK_HAS_FOCUS);
2636         image_focus_paint(imd, TRUE, NULL);
2637
2638         return TRUE;
2639 }
2640
2641 static gint image_focus_out_cb(GtkWidget *widget, GdkEventFocus *event, gpointer data)
2642 {
2643         ImageWindow *imd = data;
2644
2645         GTK_WIDGET_UNSET_FLAGS(imd->widget, GTK_HAS_FOCUS);
2646         image_focus_paint(imd, FALSE, NULL);
2647
2648         return TRUE;
2649 }
2650
2651
2652 /*
2653  *-------------------------------------------------------------------
2654  * overlays
2655  *-------------------------------------------------------------------
2656  */
2657
2658 static void image_overlay_draw(ImageWindow *imd, gint x, gint y, gint w, gint h)
2659 {
2660         GList *work;
2661
2662         work = imd->overlay_list;
2663         while (work)
2664                 {
2665                 OverlayData *od;
2666                 gint px, py, pw, ph;
2667                 gint rx, ry, rw, rh;
2668
2669                 od = work->data;
2670                 work = work->next;
2671
2672                 if (!od->visible) continue;
2673
2674                 pw = gdk_pixbuf_get_width(od->pixbuf);
2675                 ph = gdk_pixbuf_get_height(od->pixbuf);
2676                 px = od->x;
2677                 py = od->y;
2678
2679                 if (od->relative)
2680                         {
2681                         if (px < 0) px = imd->window_width - pw + px;
2682                         if (py < 0) py = imd->window_height - ph + py;
2683                         }
2684
2685                 if (util_clip_region(x, y, w, h, px, py, pw, ph, &rx, &ry, &rw, &rh))
2686                         {
2687                         gdk_draw_pixbuf(imd->image->window,
2688                                         imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
2689                                         od->pixbuf,
2690                                         rx - px, ry - py,
2691                                         rx, ry, rw, rh,
2692                                         (GdkRgbDither)dither_quality, rx, ry);
2693                         }
2694                 }
2695 }
2696
2697 static void image_overlay_queue_draw(ImageWindow *imd, OverlayData *od, gint hidden)
2698 {
2699         gint x, y, w, h;
2700         gint old_vis;
2701
2702         w = gdk_pixbuf_get_width(od->pixbuf);
2703         h = gdk_pixbuf_get_height(od->pixbuf);
2704         x = od->x;
2705         y = od->y;
2706
2707         if (od->relative)
2708                 {
2709                 if (x < 0) x = imd->window_width - w + x;
2710                 if (y < 0) y = imd->window_height - h + y;
2711                 }
2712
2713         image_queue(imd, imd->x_scroll - imd->x_offset + x,
2714                          imd->y_scroll - imd->y_offset + y,
2715                          w, h,
2716                          FALSE, TILE_RENDER_ALL, FALSE);
2717
2718         old_vis = od->visible;
2719         if (hidden) od->visible = FALSE;
2720         image_border_draw(imd, x, y, w, h);
2721         od->visible = old_vis;
2722 }
2723
2724 static void image_overlay_queue_all(ImageWindow *imd)
2725 {
2726         GList *work;
2727
2728         work = imd->overlay_list;
2729         while (work)
2730                 {
2731                 OverlayData *od = work->data;
2732                 work = work->next;
2733
2734                 image_overlay_queue_draw(imd, od, FALSE);
2735                 }
2736 }
2737
2738 static OverlayData *image_overlay_find(ImageWindow *imd, gint id)
2739 {
2740         GList *work;
2741
2742         work = imd->overlay_list;
2743         while (work)
2744                 {
2745                 OverlayData *od = work->data;
2746                 work = work->next;
2747
2748                 if (od->id == id) return od;
2749                 }
2750
2751         return NULL;
2752 }
2753
2754 gint image_overlay_add(ImageWindow *imd, GdkPixbuf *pixbuf, gint x, gint y,
2755                        gint relative, gint always)
2756 {
2757         OverlayData *od;
2758         gint id;
2759
2760         if (!imd || !pixbuf) return -1;
2761
2762         id = 1;
2763         while (image_overlay_find(imd, id)) id++;
2764
2765         od = g_new0(OverlayData, 1);
2766         od->id = id;
2767         od->pixbuf = pixbuf;
2768         g_object_ref(G_OBJECT(od->pixbuf));
2769         od->x = x;
2770         od->y = y;
2771         od->relative = relative;
2772         od->visible = TRUE;
2773         od->always = always;
2774
2775         imd->overlay_list = g_list_append(imd->overlay_list, od);
2776
2777         image_overlay_queue_draw(imd, od, FALSE);
2778
2779         return od->id;
2780 }
2781
2782 static void image_overlay_free(ImageWindow *imd, OverlayData *od)
2783 {
2784         imd->overlay_list = g_list_remove(imd->overlay_list, od);
2785
2786         if (od->pixbuf) g_object_unref(G_OBJECT(od->pixbuf));
2787         g_free(od);
2788 }
2789
2790 static void image_overlay_list_clear(ImageWindow *imd)
2791 {
2792         while (imd->overlay_list)
2793                 {
2794                 OverlayData *od;
2795
2796                 od = imd->overlay_list->data;
2797                 image_overlay_free(imd, od);
2798                 }
2799 }
2800
2801 void image_overlay_set(ImageWindow *imd, gint id, GdkPixbuf *pixbuf, gint x, gint y)
2802 {
2803         OverlayData *od;
2804
2805         if (!imd) return;
2806
2807         od = image_overlay_find(imd, id);
2808         if (!od) return;
2809
2810         if (pixbuf)
2811                 {
2812                 image_overlay_queue_draw(imd, od, TRUE);
2813
2814                 g_object_ref(G_OBJECT(pixbuf));
2815                 g_object_unref(G_OBJECT(od->pixbuf));
2816                 od->pixbuf = pixbuf;
2817
2818                 od->x = x;
2819                 od->y = y;
2820
2821                 image_overlay_queue_draw(imd, od, FALSE);
2822                 }
2823         else
2824                 {
2825                 image_overlay_queue_draw(imd, od, TRUE);
2826                 image_overlay_free(imd, od);
2827                 }
2828 }
2829
2830 gint image_overlay_get(ImageWindow *imd, gint id, GdkPixbuf **pixbuf, gint *x, gint *y)
2831 {
2832         OverlayData *od;
2833
2834         if (!imd) return FALSE;
2835
2836         od = image_overlay_find(imd, id);
2837         if (!od) return FALSE;
2838
2839         if (pixbuf) *pixbuf = od->pixbuf;
2840         if (x) *x = od->x;
2841         if (y) *y = od->y;
2842
2843         return TRUE;
2844 }
2845
2846 void image_overlay_remove(ImageWindow *imd, gint id)
2847 {
2848         image_overlay_set(imd, id, NULL, 0, 0);
2849 }
2850
2851 /*
2852  *-------------------------------------------------------------------
2853  * scroller
2854  *-------------------------------------------------------------------
2855  */
2856
2857 #define SCROLLER_UPDATES_PER_SEC 30
2858 #define SCROLLER_DEAD_ZONE 6
2859
2860
2861 static gboolean image_scroller_update_cb(gpointer data)
2862 {
2863         ImageWindow *imd = data;
2864         gint x, y;
2865         gint xinc, yinc;
2866
2867         /* this was a simple scroll by difference between scroller and mouse position,
2868          * but all this math results in a smoother result and accounts for a dead zone.
2869          */
2870
2871         if (abs(imd->scroller_xpos - imd->scroller_x) < SCROLLER_DEAD_ZONE)
2872                 {
2873                 x = 0;
2874                 }
2875         else
2876                 {
2877                 gint shift = SCROLLER_DEAD_ZONE / 2 * SCROLLER_UPDATES_PER_SEC;
2878                 x = (imd->scroller_xpos - imd->scroller_x) / 2 * SCROLLER_UPDATES_PER_SEC;
2879                 x += (x > 0) ? -shift : shift;
2880                 }
2881
2882         if (abs(imd->scroller_ypos - imd->scroller_y) < SCROLLER_DEAD_ZONE)
2883                 {
2884                 y = 0;
2885                 }
2886         else
2887                 {
2888                 gint shift = SCROLLER_DEAD_ZONE / 2 * SCROLLER_UPDATES_PER_SEC;
2889                 y = (imd->scroller_ypos - imd->scroller_y) / 2 * SCROLLER_UPDATES_PER_SEC;
2890                 y += (y > 0) ? -shift : shift;
2891                 }
2892
2893         if (abs(x) < SCROLLER_DEAD_ZONE * SCROLLER_UPDATES_PER_SEC)
2894                 {
2895                 xinc = x;
2896                 }
2897         else
2898                 {
2899                 xinc = imd->scroller_xinc;
2900
2901                 if (x >= 0)
2902                         {
2903                         if (xinc < 0) xinc = 0;
2904                         if (x < xinc) xinc = x;
2905                         if (x > xinc) xinc = MIN(xinc + x / SCROLLER_UPDATES_PER_SEC, x);
2906                         }
2907                 else
2908                         {
2909                         if (xinc > 0) xinc = 0;
2910                         if (x > xinc) xinc = x;
2911                         if (x < xinc) xinc = MAX(xinc + x / SCROLLER_UPDATES_PER_SEC, x);
2912                         }
2913                 }
2914
2915         if (abs(y) < SCROLLER_DEAD_ZONE * SCROLLER_UPDATES_PER_SEC)
2916                 {
2917                 yinc = y;
2918                 }
2919         else
2920                 {
2921                 yinc = imd->scroller_yinc;
2922
2923                 if (y >= 0)
2924                         {
2925                         if (yinc < 0) yinc = 0;
2926                         if (y < yinc) yinc = y;
2927                         if (y > yinc) yinc = MIN(yinc + y / SCROLLER_UPDATES_PER_SEC, y);
2928                         }
2929                 else
2930                         {
2931                         if (yinc > 0) yinc = 0;
2932                         if (y > yinc) yinc = y;
2933                         if (y < yinc) yinc = MAX(yinc + y / SCROLLER_UPDATES_PER_SEC, y);
2934                         }
2935                 }
2936
2937         imd->scroller_xinc = xinc;
2938         imd->scroller_yinc = yinc;
2939
2940         xinc = xinc / SCROLLER_UPDATES_PER_SEC;
2941         yinc = yinc / SCROLLER_UPDATES_PER_SEC;
2942
2943         image_scroll(imd, xinc, yinc);
2944
2945         return TRUE;
2946 }
2947
2948 static void image_scroller_timer_set(ImageWindow *imd, gint start)
2949 {
2950         if (imd->scroller_id != -1)
2951                 {
2952                 g_source_remove(imd->scroller_id);
2953                 imd->scroller_id = -1;
2954                 }
2955
2956         if (start)
2957                 {
2958                 imd->scroller_id = g_timeout_add(1000 / SCROLLER_UPDATES_PER_SEC,
2959                                                    image_scroller_update_cb, imd);
2960                 }
2961 }
2962
2963 static void image_scroller_start(ImageWindow *imd, gint x, gint y)
2964 {
2965         if (imd->scroller_overlay == -1)
2966                 {
2967                 GdkPixbuf *pixbuf;
2968                 gint w, h;
2969
2970                 pixbuf = pixbuf_inline(PIXBUF_INLINE_SCROLLER);
2971                 w = gdk_pixbuf_get_width(pixbuf);
2972                 h = gdk_pixbuf_get_height(pixbuf);
2973
2974                 imd->scroller_overlay = image_overlay_add(imd, pixbuf, x - w / 2, y - h / 2, FALSE, TRUE);
2975                 }
2976
2977         imd->scroller_x = x;
2978         imd->scroller_y = y;
2979         imd->scroller_xpos = x;
2980         imd->scroller_ypos = y;
2981
2982         image_scroller_timer_set(imd, TRUE);
2983 }
2984
2985 static void image_scroller_stop(ImageWindow *imd)
2986 {
2987         if (imd->scroller_id == -1) return;
2988
2989         image_overlay_remove(imd, imd->scroller_overlay);
2990         imd->scroller_overlay = -1;
2991
2992         image_scroller_timer_set(imd, FALSE);
2993 }
2994
2995 /*
2996  *-------------------------------------------------------------------
2997  * mouse stuff
2998  *-------------------------------------------------------------------
2999  */
3000
3001 static gint image_mouse_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3002 {
3003         ImageWindow *imd = data;
3004
3005         if (imd->scroller_id != -1)
3006                 {
3007                 imd->scroller_xpos = bevent->x;
3008                 imd->scroller_ypos = bevent->y;
3009                 }
3010
3011         if (!imd->in_drag || !gdk_pointer_is_grabbed()) return FALSE;
3012
3013         if (imd->drag_moved < IMAGE_DRAG_SCROLL_THRESHHOLD)
3014                 {
3015                 imd->drag_moved++;
3016                 }
3017         else
3018                 {
3019                 widget_set_cursor (imd->image, GDK_FLEUR);
3020                 }
3021
3022         /* do the scroll */
3023         image_scroll_real(imd, imd->drag_last_x - bevent->x, imd->drag_last_y - bevent->y);
3024
3025         imd->drag_last_x = bevent->x;
3026         imd->drag_last_y = bevent->y;
3027
3028         return FALSE;
3029 }
3030
3031 static gint image_mouse_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3032 {
3033         ImageWindow *imd = data;
3034
3035         if (imd->scroller_id != -1) return TRUE;
3036
3037         switch (bevent->button)
3038                 {
3039                 case 1:
3040                         imd->in_drag = TRUE;
3041                         imd->drag_last_x = bevent->x;
3042                         imd->drag_last_y = bevent->y;
3043                         imd->drag_moved = 0;
3044                         gdk_pointer_grab(imd->image->window, FALSE,
3045                                 GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
3046                                 NULL, NULL, bevent->time);
3047                         gtk_grab_add(imd->image);
3048                         break;
3049                 case 2:
3050                         imd->drag_moved = 0;
3051                         break;
3052                 case 3:
3053                         image_button_do(imd, bevent);
3054                         break;
3055                 default:
3056                         break;
3057                 }
3058
3059         gtk_widget_grab_focus(imd->widget);
3060
3061         return FALSE;
3062 }
3063
3064 static gint image_mouse_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3065 {
3066         ImageWindow *imd = data;
3067
3068         if (imd->scroller_id != -1)
3069                 {
3070                 image_scroller_stop(imd);
3071                 return TRUE;
3072                 }
3073
3074         if (gdk_pointer_is_grabbed() && GTK_WIDGET_HAS_GRAB(imd->image))
3075                 {
3076                 gtk_grab_remove(imd->image);
3077                 gdk_pointer_ungrab(bevent->time);
3078                 widget_set_cursor(imd->image, -1);
3079                 }
3080
3081         if (bevent->button == 1 && (bevent->state & GDK_SHIFT_MASK))
3082                 {
3083                 image_scroller_start(imd, bevent->x, bevent->y);
3084                 }
3085         else if (bevent->button == 1 || bevent->button == 2)
3086                 {
3087                 if (imd->drag_moved < IMAGE_DRAG_SCROLL_THRESHHOLD) image_button_do(imd, bevent);
3088                 }
3089
3090         imd->in_drag = FALSE;
3091
3092         return FALSE;
3093 }
3094
3095 static gint image_mouse_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
3096 {
3097         ImageWindow *imd = data;
3098
3099         if (imd->scroller_id != -1)
3100                 {
3101                 imd->scroller_xpos = imd->scroller_x;
3102                 imd->scroller_ypos = imd->scroller_y;
3103                 imd->scroller_xinc = 0;
3104                 imd->scroller_yinc = 0;
3105                 }
3106
3107         return FALSE;
3108 }
3109
3110 static gint image_scroll_cb(GtkWidget *widget, GdkEventScroll *event, gpointer data)
3111 {
3112         ImageWindow *imd = data;
3113
3114         if (imd->func_scroll &&
3115             event && event->type == GDK_SCROLL)
3116                 {
3117                 imd->func_scroll(imd, event->direction, event->time,
3118                                  event->x, event->y, event->state, imd->data_scroll);
3119                 return TRUE;
3120                 }
3121
3122         return FALSE;
3123 }
3124
3125 static void image_mouse_drag_cb(GtkWidget *widget, GdkDragContext *context, gpointer data)
3126 {
3127         ImageWindow *imd = data;
3128
3129         imd->drag_moved = IMAGE_DRAG_SCROLL_THRESHHOLD;
3130 }
3131
3132 /*
3133  *-------------------------------------------------------------------
3134  * drag and drop
3135  *-------------------------------------------------------------------
3136  */
3137
3138 /*
3139  *-------------------------------------------------------------------
3140  * public interface
3141  *-------------------------------------------------------------------
3142  */
3143
3144 void image_attach_window(ImageWindow *imd, GtkWidget *window,
3145                          const gchar *title, const gchar *title_right, gint show_zoom)
3146 {
3147         imd->top_window = window;
3148         g_free(imd->title);
3149         imd->title = g_strdup(title);
3150         g_free(imd->title_right);
3151         imd->title_right = g_strdup(title_right);
3152         imd->title_show_zoom = show_zoom;
3153
3154         image_update_title(imd);
3155 }
3156
3157 void image_set_update_func(ImageWindow *imd,
3158                            void (*func)(ImageWindow *imd, gpointer data),
3159                            gpointer data)
3160 {
3161         imd->func_update = func;
3162         imd->data_update = data;
3163 }
3164
3165 void image_set_complete_func(ImageWindow *imd,
3166                              void (*func)(ImageWindow *, gint preload, gpointer),
3167                              gpointer data)
3168 {
3169         imd->func_complete = func;
3170         imd->data_complete = data;
3171 }
3172
3173 void image_set_new_func(ImageWindow *imd,
3174                         void (*func)(ImageWindow *, gpointer),
3175                         gpointer data)
3176 {
3177         imd->func_new = func;
3178         imd->data_new = data;
3179 }
3180
3181
3182 static void image_button_do(ImageWindow *imd, GdkEventButton *bevent)
3183 {
3184         if (imd->func_button &&
3185             bevent &&
3186             (bevent->type == GDK_BUTTON_PRESS || bevent->type == GDK_BUTTON_RELEASE))
3187                 {
3188                 imd->func_button(imd, bevent->button, bevent->time,
3189                                  bevent->x, bevent->y, bevent->state, imd->data_button);
3190                 }
3191 }
3192
3193 void image_set_button_func(ImageWindow *imd,
3194                            void (*func)(ImageWindow *, gint button, guint32 time, gdouble x, gdouble y, guint state, gpointer),
3195                            gpointer data)
3196 {
3197         imd->func_button = func;
3198         imd->data_button = data;
3199 }
3200
3201 void image_set_scroll_func(ImageWindow *imd,
3202                            void (*func)(ImageWindow *, GdkScrollDirection direction, guint32 time, gdouble x, gdouble y, guint state, gpointer),
3203                            gpointer data)
3204 {
3205         imd->func_scroll = func;
3206         imd->data_scroll = data;
3207 }
3208
3209 void image_set_scroll_notify_func(ImageWindow *imd,
3210                                   void (*func)(ImageWindow *imd, gint x, gint y, gint width, gint height, gpointer data),
3211                                   gpointer data)
3212 {
3213         imd->func_scroll_notify = func;
3214         imd->data_scroll_notify = data;
3215 }
3216
3217 /* path, name */
3218
3219 const gchar *image_get_path(ImageWindow *imd)
3220 {
3221         return imd->image_path;
3222 }
3223
3224 const gchar *image_get_name(ImageWindow *imd)
3225 {
3226         return imd->image_name;
3227 }
3228
3229 /* merely changes path string, does not change the image! */
3230 void image_set_path(ImageWindow *imd, const gchar *newpath)
3231 {
3232         g_free(imd->image_path);
3233         imd->image_path = g_strdup(newpath);
3234         imd->image_name = filename_from_path(imd->image_path);
3235
3236         image_update_title(imd);
3237         image_new_util(imd);
3238 }
3239
3240 /* load a new image */
3241
3242 void image_change_path(ImageWindow *imd, const gchar *path, gdouble zoom)
3243 {
3244         if (imd->image_path == path ||
3245             (path && imd->image_path && !strcmp(path, imd->image_path)) ) return;
3246
3247         image_source_tile_unset(imd);
3248
3249         image_change_real(imd, path, NULL, NULL, zoom);
3250 }
3251
3252 void image_change_pixbuf(ImageWindow *imd, GdkPixbuf *pixbuf, gdouble zoom)
3253 {
3254         image_source_tile_unset(imd);
3255
3256         image_set_pixbuf(imd, pixbuf, zoom, TRUE);
3257         image_new_util(imd);
3258 }
3259
3260 void image_change_from_collection(ImageWindow *imd, CollectionData *cd, CollectInfo *info, gdouble zoom)
3261 {
3262         if (!cd || !info || !g_list_find(cd->list, info)) return;
3263
3264         image_source_tile_unset(imd);
3265
3266         image_change_real(imd, info->path, cd, info, zoom);
3267 }
3268
3269 CollectionData *image_get_collection(ImageWindow *imd, CollectInfo **info)
3270 {
3271         if (collection_to_number(imd->collection) >= 0)
3272                 {
3273                 if (g_list_find(imd->collection->list, imd->collection_info) != NULL)
3274                         {
3275                         if (info) *info = imd->collection_info;
3276                         }
3277                 else
3278                         {
3279                         if (info) *info = NULL;
3280                         }
3281                 return imd->collection;
3282                 }
3283
3284         if (info) *info = NULL;
3285         return NULL;
3286 }
3287
3288 static void image_loader_sync_data(ImageLoader *il, gpointer data)
3289 {
3290         /* change data for the callbacks directly */
3291
3292         il->data_area_ready = data;
3293         il->data_error = data;
3294         il->data_done = data;
3295         il->data_percent = data;
3296 }
3297
3298 /* this is more like a move function
3299  * it moves most data from source to imd, source does keep a ref on the pixbuf
3300  */
3301 void image_change_from_image(ImageWindow *imd, ImageWindow *source)
3302 {
3303         if (imd == source) return;
3304
3305         imd->zoom_min = source->zoom_min;
3306         imd->zoom_max = source->zoom_max;
3307
3308         imd->unknown = source->unknown;
3309
3310         image_set_pixbuf(imd, source->pixbuf, image_zoom_get(source), TRUE);
3311
3312         imd->collection = source->collection;
3313         imd->collection_info = source->collection_info;
3314         imd->size = source->size;
3315         imd->mtime = source->mtime;
3316
3317         image_set_path(imd, image_get_path(source));
3318
3319         image_loader_free(imd->il);
3320         imd->il = NULL;
3321
3322         if (imd->pixbuf && source->il)
3323                 {
3324                 imd->il = source->il;
3325                 source->il = NULL;
3326
3327                 image_loader_sync_data(imd->il, imd);
3328
3329                 imd->delay_alter_type = source->delay_alter_type;
3330                 source->delay_alter_type = ALTER_NONE;
3331                 }
3332
3333         image_loader_free(imd->read_ahead_il);
3334         imd->read_ahead_il = source->read_ahead_il;
3335         source->read_ahead_il = NULL;
3336         if (imd->read_ahead_il) image_loader_sync_data(imd->read_ahead_il, imd);
3337
3338         if (imd->read_ahead_pixbuf) g_object_unref(imd->read_ahead_pixbuf);
3339         imd->read_ahead_pixbuf = source->read_ahead_pixbuf;
3340         source->read_ahead_pixbuf = NULL;
3341
3342         g_free(imd->read_ahead_path);
3343         imd->read_ahead_path = source->read_ahead_path;
3344         source->read_ahead_path = NULL;
3345
3346         if (imd->prev_pixbuf) g_object_unref(imd->prev_pixbuf);
3347         imd->prev_pixbuf = source->prev_pixbuf;
3348         source->prev_pixbuf = NULL;
3349
3350         g_free(imd->prev_path);
3351         imd->prev_path = source->prev_path;
3352         source->prev_path = NULL;
3353
3354         imd->completed = source->completed;
3355
3356         imd->x_scroll = source->x_scroll;
3357         imd->y_scroll = source->y_scroll;
3358
3359         if (imd->source_tiles_enabled)
3360                 {
3361                 image_source_tile_unset(imd);
3362                 }
3363
3364         if (source->source_tiles_enabled)
3365                 {
3366                 imd->source_tiles_enabled = source->source_tiles_enabled;
3367                 imd->source_tiles_cache_size = source->source_tiles_cache_size;
3368                 imd->source_tiles = source->source_tiles;
3369                 imd->source_tile_width = source->source_tile_width;
3370                 imd->source_tile_height = source->source_tile_height;
3371
3372                 source->source_tiles_enabled = FALSE;
3373                 source->source_tiles = NULL;
3374
3375                 imd->func_tile_request = source->func_tile_request;
3376                 imd->func_tile_dispose = source->func_tile_dispose;
3377                 imd->data_tile = source->data_tile;
3378
3379                 source->func_tile_request = NULL;
3380                 source->func_tile_dispose = NULL;
3381                 source->data_tile = NULL;
3382
3383                 imd->image_width = source->image_width;
3384                 imd->image_height = source->image_height;
3385
3386                 if (image_zoom_clamp(imd, source->zoom, TRUE, TRUE))
3387                         {
3388                         image_size_clamp(imd);
3389                         image_scroll_clamp(imd);
3390                         image_tile_sync(imd, imd->width, imd->height, FALSE);
3391                         image_redraw(imd, FALSE);
3392                         }
3393                 return;
3394                 }
3395
3396         image_scroll_clamp(imd);
3397 }
3398
3399 /* manipulation */
3400
3401 void image_area_changed(ImageWindow *imd, gint x, gint y, gint width, gint height)
3402 {
3403         gint sx, sy, sw, sh;
3404
3405         sx = (gint)floor((double)x * imd->scale);
3406         sy = (gint)floor((double)y * imd->scale);
3407         sw = (gint)ceil((double)width * imd->scale);
3408         sh = (gint)ceil((double)height * imd->scale);
3409
3410         if (imd->source_tiles_enabled)
3411                 {
3412                 source_tile_changed(imd, x, y, width, height);
3413                 }
3414
3415         image_queue(imd, sx, sy, sw, sh, FALSE, TILE_RENDER_AREA, TRUE);
3416 }
3417
3418 void image_reload(ImageWindow *imd)
3419 {
3420         if (imd->source_tiles_enabled) return;
3421
3422         image_change_complete(imd, imd->zoom, FALSE);
3423 }
3424
3425 void image_scroll(ImageWindow *imd, gint x, gint y)
3426 {
3427         image_scroll_real(imd, x, y);
3428 }
3429
3430 void image_scroll_to_point(ImageWindow *imd, gint x, gint y)
3431 {
3432         gint px, py;
3433
3434         px = (gdouble)x * imd->scale - imd->x_scroll;
3435         py = (gdouble)y * imd->scale - imd->y_scroll;
3436
3437         image_scroll(imd, px, py);
3438 }
3439
3440 void image_alter(ImageWindow *imd, AlterType type)
3441 {
3442         if (imd->source_tiles_enabled) return;
3443
3444         if (imd->il)
3445                 {
3446                 /* still loading, wait till done */
3447                 imd->delay_alter_type = type;
3448                 return;
3449                 }
3450
3451         image_alter_real(imd, type, TRUE);
3452 }
3453
3454 /* zoom */
3455
3456 static void image_zoom_adjust_real(ImageWindow *imd, gdouble increment,
3457                                    gint center_point, gint x, gint y)
3458 {
3459         gdouble zoom = imd->zoom;
3460
3461         if (increment == 0.0) return; /* avoid possible div by zero, a no-op anyway... */
3462
3463         if (zoom == 0.0)
3464                 {
3465                 if (imd->scale < 1.0)
3466                         {
3467                         zoom = 0.0 - 1.0 / imd->scale;
3468                         }
3469                 else
3470                         {
3471                         zoom = imd->scale;
3472                         }
3473                 }
3474
3475         if (increment < 0.0)
3476                 {
3477                 if (zoom >= 1.0 && zoom + increment < 1.0)
3478                         {
3479                         zoom = zoom + increment - 2.0;
3480                         }
3481                 else
3482                         {
3483                         zoom = zoom + increment;
3484                         }
3485                 }
3486         else
3487                 {
3488                 if (zoom <= -1.0 && zoom + increment > -1.0)
3489                         {
3490                         zoom = zoom + increment + 2.0;
3491                         }
3492                 else
3493                         {
3494                         zoom = zoom + increment;
3495                         }
3496                 }
3497
3498         image_zoom_sync(imd, zoom, FALSE, FALSE, FALSE, center_point, x, y);
3499 }
3500
3501 void image_zoom_adjust(ImageWindow *imd, gdouble increment)
3502 {
3503         image_zoom_adjust_real(imd, increment, FALSE, 0, 0);
3504 }
3505
3506 void image_zoom_adjust_at_point(ImageWindow *imd, gdouble increment, gint x, gint y)
3507 {
3508         image_zoom_adjust_real(imd, increment, TRUE, x, y);
3509 }
3510
3511 void image_zoom_set_limits(ImageWindow *imd, gdouble min, gdouble max)
3512 {
3513         if (min > 1.0 || max < 1.0) return;
3514         if (min < 1.0 && min > -1.0) return;
3515         if (min < -200.0 || max > 200.0) return;
3516
3517         imd->zoom_min = min;
3518         imd->zoom_max = max;
3519 }
3520
3521 void image_zoom_set(ImageWindow *imd, gdouble zoom)
3522 {
3523         image_zoom_sync(imd, zoom, FALSE, FALSE, FALSE, FALSE, 0, 0);
3524 }
3525
3526 void image_zoom_set_fill_geometry(ImageWindow *imd, gint vertical)
3527 {
3528         gdouble zoom;
3529
3530         if (!imd->pixbuf || imd->image_width < 1 || imd->image_height < 1) return;
3531
3532         if (vertical)
3533                 {
3534                 zoom = (gdouble)imd->window_height / imd->image_height;
3535                 }
3536         else
3537                 {
3538                 zoom = (gdouble)imd->window_width / imd->image_width;
3539                 }
3540
3541         if (zoom < 1.0)
3542                 {
3543                 zoom = 0.0 - 1.0 / zoom;
3544                 }
3545
3546         image_zoom_set(imd, zoom);
3547 }
3548
3549 gdouble image_zoom_get(ImageWindow *imd)
3550 {
3551         return imd->zoom;
3552 }
3553
3554 gdouble image_zoom_get_real(ImageWindow *imd)
3555 {
3556         return imd->scale;
3557 }
3558
3559 gchar *image_zoom_get_as_text(ImageWindow *imd)
3560 {
3561         gdouble l = 1.0;
3562         gdouble r = 1.0;
3563         gint pl = 0;
3564         gint pr = 0;
3565         gchar *approx = " ";
3566
3567         if (imd->zoom > 0.0)
3568                 {
3569                 l = imd->zoom;
3570                 }
3571         else if (imd->zoom < 0.0)
3572                 {
3573                 r = 0.0 - imd->zoom;
3574                 }
3575         else if (imd->zoom == 0.0 && imd->scale != 0.0)
3576                 {
3577                 if (imd->scale >= 1.0)
3578                         {
3579                         l = imd->scale;
3580                         }
3581                 else
3582                         {
3583                         r = 1.0 / imd->scale;
3584                         }
3585                 approx = " ~";
3586                 }
3587
3588         if (rint(l) != l) pl = 1;
3589         if (rint(r) != r) pr = 1;
3590
3591         return g_strdup_printf("%.*f :%s%.*f", pl, l, approx, pr, r);
3592 }
3593
3594 gdouble image_zoom_get_default(ImageWindow *imd, gint mode)
3595 {
3596         gdouble zoom;
3597
3598         if (mode == ZOOM_RESET_ORIGINAL)
3599                 {
3600                 zoom = 1.0;
3601                 }
3602         else if (mode == ZOOM_RESET_FIT_WINDOW)
3603                 {
3604                 zoom = 0.0;
3605                 }
3606         else
3607                 {
3608                 if (imd)
3609                         {
3610                         zoom = image_zoom_get(imd);
3611                         }
3612                 else
3613                         {
3614                         zoom = 1.0;
3615                         }
3616                 }
3617
3618         return zoom;
3619 }
3620
3621 /* read ahead */
3622
3623 void image_prebuffer_set(ImageWindow *imd, const gchar *path)
3624 {
3625         if (imd->source_tiles_enabled) return;
3626
3627         if (path)
3628                 {
3629                 image_read_ahead_set(imd, path);
3630                 }
3631         else
3632                 {
3633                 image_read_ahead_cancel(imd);
3634                 }
3635 }
3636
3637 static gint image_auto_refresh_cb(gpointer data)
3638 {
3639         ImageWindow *imd = data;
3640         time_t newtime;
3641         
3642         if (!imd || !imd->pixbuf ||
3643             imd->il || !imd->image_path ||
3644             !update_on_time_change) return TRUE;
3645
3646         newtime = filetime(imd->image_path);
3647         if (newtime > 0 && newtime != imd->mtime)
3648                 {
3649                 imd->mtime = newtime;
3650                 image_reload(imd);
3651                 }
3652
3653         return TRUE;
3654 }
3655
3656 /* image auto refresh on time stamp change, in 1/1000's second, -1 disables */
3657
3658 void image_auto_refresh(ImageWindow *imd, gint interval)
3659 {
3660         if (!imd) return;
3661         if (imd->source_tiles_enabled) return;
3662
3663         if (imd->auto_refresh_id > -1)
3664                 {
3665                 g_source_remove(imd->auto_refresh_id);
3666                 imd->auto_refresh_id = -1;
3667                 imd->auto_refresh_interval = -1;
3668                 }
3669
3670         if (interval < 0) return;
3671
3672         if (interval == 0) interval = IMAGE_AUTO_REFRESH_TIME;
3673
3674         imd->auto_refresh_id = g_timeout_add((guint32)interval, image_auto_refresh_cb, imd);
3675         imd->auto_refresh_interval = interval;
3676 }
3677
3678 /* allow top window to be resized ? */
3679
3680 void image_top_window_set_sync(ImageWindow *imd, gint allow_sync)
3681 {
3682         imd->top_window_sync = allow_sync;
3683 }
3684
3685 /* background colors */
3686
3687 void image_background_set_black(ImageWindow *imd, gint black)
3688 {
3689         GtkStyle *style;
3690
3691         if (!imd) return;
3692
3693         style = gtk_style_copy(gtk_widget_get_style(imd->widget));
3694         g_object_ref(G_OBJECT(style));
3695
3696         if (black)
3697                 {
3698                 style->bg[GTK_STATE_NORMAL] = style->black;
3699                 }
3700
3701         gtk_widget_set_style(imd->image, style);
3702         g_object_unref(G_OBJECT(style));
3703
3704         if (GTK_WIDGET_VISIBLE(imd->widget)) image_border_clear(imd);
3705 }
3706
3707 void image_background_set_color(ImageWindow *imd, GdkColor *color)
3708 {
3709         GtkStyle *style;
3710
3711         if (!imd) return;
3712
3713         style = gtk_style_copy(gtk_widget_get_style(imd->widget));
3714         g_object_ref(G_OBJECT(style));
3715
3716         if (color)
3717                 {
3718                 GdkColor *slot;
3719
3720                 slot = &style->bg[GTK_STATE_NORMAL];
3721
3722                 slot->red = color->red;
3723                 slot->green = color->green;
3724                 slot->blue = color->blue;
3725                 }
3726
3727         gtk_widget_set_style(imd->image, style);
3728         g_object_unref(G_OBJECT(style));
3729
3730         if (GTK_WIDGET_VISIBLE(imd->widget)) image_border_clear(imd);
3731 }
3732
3733 void image_set_delay_flip(ImageWindow *imd, gint delay)
3734 {
3735         if (!imd ||
3736             imd->delay_flip == delay) return;
3737
3738         imd->delay_flip = delay;
3739         if (!imd->delay_flip && imd->il)
3740                 {
3741                 if (imd->pixbuf) g_object_unref(imd->pixbuf);
3742                 imd->pixbuf = NULL;
3743                 image_load_pixbuf_ready(imd);
3744
3745                 image_queue_clear(imd);
3746                 image_queue(imd, 0, 0, imd->width, imd->height, FALSE, TILE_RENDER_AREA, TRUE);
3747                 }
3748 }
3749
3750 /* wallpaper util */
3751
3752 void image_to_root_window(ImageWindow *imd, gint scaled)
3753 {
3754         GdkScreen *screen;
3755         GdkWindow *rootwindow;
3756         GdkPixmap *pixmap;
3757         GdkPixbuf *pb;
3758
3759         if (!imd || !imd->pixbuf) return;
3760
3761         screen = gtk_widget_get_screen(imd->image);
3762         rootwindow = gdk_screen_get_root_window(screen);
3763         if (gdk_drawable_get_visual(rootwindow) != gdk_visual_get_system()) return;
3764
3765         if (scaled)
3766                 {
3767                 pb = gdk_pixbuf_scale_simple(imd->pixbuf, gdk_screen_width(), gdk_screen_height(), (GdkInterpType)zoom_quality);
3768                 }
3769         else
3770                 {
3771                 pb = gdk_pixbuf_scale_simple(imd->pixbuf, imd->width, imd->height, (GdkInterpType)zoom_quality);
3772                 }
3773
3774         gdk_pixbuf_render_pixmap_and_mask (pb, &pixmap, NULL, 128);
3775         gdk_window_set_back_pixmap(rootwindow, pixmap, FALSE);
3776         gdk_window_clear(rootwindow);
3777         g_object_unref(pb);
3778         g_object_unref(pixmap);
3779
3780         gdk_flush();
3781 }
3782
3783
3784 /*
3785  *-------------------------------------------------------------------
3786  * init / destroy
3787  *-------------------------------------------------------------------
3788  */
3789
3790 static void image_free(ImageWindow *imd)
3791 {
3792         image_read_ahead_cancel(imd);
3793         image_post_buffer_set(imd, NULL, NULL);
3794         image_auto_refresh(imd, -1);
3795
3796         g_free(imd->image_path);
3797         g_free(imd->title);
3798         g_free(imd->title_right);
3799
3800         image_reset(imd);
3801         image_tile_sync_count(imd, 0);
3802         if (imd->pixbuf) g_object_unref(imd->pixbuf);
3803
3804         image_scroller_timer_set(imd, FALSE);
3805
3806         image_overlay_list_clear(imd);
3807
3808         source_tile_free_all(imd);
3809
3810         g_free(imd);
3811 }
3812
3813 static void image_destroy_cb(GtkObject *widget, gpointer data)
3814 {
3815         ImageWindow *imd = data;
3816         image_free(imd);
3817 }
3818
3819 ImageWindow *image_new(gint frame)
3820 {
3821         ImageWindow *imd;
3822
3823         imd = g_new0(ImageWindow, 1);
3824
3825         imd->zoom_min = IMAGE_ZOOM_MIN;
3826         imd->zoom_max = IMAGE_ZOOM_MAX;
3827         imd->zoom = 1.0;
3828         imd->scale = 1.0;
3829
3830         imd->draw_idle_id = -1;
3831
3832         imd->tile_width = IMAGE_TILE_SIZE;
3833         imd->tile_height = IMAGE_TILE_SIZE;
3834
3835         imd->top_window = NULL;
3836         imd->title = NULL;
3837         imd->title_right = NULL;
3838         imd->title_show_zoom = FALSE;
3839
3840         imd->unknown = TRUE;
3841
3842         imd->pixbuf = NULL;
3843
3844         imd->has_frame = frame;
3845         imd->top_window_sync = FALSE;
3846
3847         imd->tile_cache = NULL;
3848         imd->tile_cache_size = 0;
3849
3850         imd->delay_alter_type = ALTER_NONE;
3851
3852         imd->read_ahead_il = NULL;
3853         imd->read_ahead_pixbuf = NULL;
3854         imd->read_ahead_path = NULL;
3855
3856         imd->completed = FALSE;
3857
3858         imd->auto_refresh_id = -1;
3859         imd->auto_refresh_interval = -1;
3860
3861         imd->delay_flip = FALSE;
3862
3863         imd->func_update = NULL;
3864         imd->func_complete = NULL;
3865         imd->func_tile_request = NULL;
3866         imd->func_tile_dispose = NULL;
3867
3868         imd->func_button = NULL;
3869         imd->func_scroll = NULL;
3870
3871         imd->scroller_id = -1;
3872         imd->scroller_overlay = -1;
3873
3874         imd->source_tiles_enabled = FALSE;
3875         imd->source_tiles = NULL;
3876
3877         imd->image = gtk_drawing_area_new();
3878         gtk_widget_set_double_buffered(imd->image, FALSE);
3879
3880         if (imd->has_frame)
3881                 {
3882                 imd->widget = gtk_frame_new(NULL);
3883                 gtk_frame_set_shadow_type(GTK_FRAME(imd->widget), GTK_SHADOW_IN);
3884                 gtk_container_add(GTK_CONTAINER(imd->widget), imd->image);
3885                 gtk_widget_show(imd->image);
3886
3887                 GTK_WIDGET_SET_FLAGS(imd->widget, GTK_CAN_FOCUS);
3888                 g_signal_connect(G_OBJECT(imd->widget), "focus_in_event",
3889                                  G_CALLBACK(image_focus_in_cb), imd);
3890                 g_signal_connect(G_OBJECT(imd->widget), "focus_out_event",
3891                                  G_CALLBACK(image_focus_out_cb), imd);
3892
3893                 g_signal_connect_after(G_OBJECT(imd->widget), "expose_event",
3894                                        G_CALLBACK(image_focus_expose), imd);
3895                 }
3896         else
3897                 {
3898                 imd->widget = imd->image;
3899                 }
3900
3901         g_signal_connect(G_OBJECT(imd->image), "motion_notify_event",
3902                          G_CALLBACK(image_mouse_motion_cb), imd);
3903         g_signal_connect(G_OBJECT(imd->image), "button_press_event",
3904                          G_CALLBACK(image_mouse_press_cb), imd);
3905         g_signal_connect(G_OBJECT(imd->image), "button_release_event",
3906                          G_CALLBACK(image_mouse_release_cb), imd);
3907         g_signal_connect(G_OBJECT(imd->image), "leave_notify_event",
3908                          G_CALLBACK(image_mouse_leave_cb), imd);
3909         gtk_widget_set_events(imd->image, GDK_POINTER_MOTION_MASK |
3910                                           GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
3911                                           GDK_LEAVE_NOTIFY_MASK);
3912
3913         g_signal_connect(G_OBJECT(imd->image), "expose_event",
3914                          G_CALLBACK(image_expose_cb), imd);
3915         g_signal_connect_after(G_OBJECT(imd->image), "size_allocate",
3916                          G_CALLBACK(image_size_cb), imd);
3917
3918         g_signal_connect(G_OBJECT(imd->image), "drag_begin",
3919                          G_CALLBACK(image_mouse_drag_cb), imd);
3920         g_signal_connect(G_OBJECT(imd->image), "scroll_event",
3921                          G_CALLBACK(image_scroll_cb), imd);
3922
3923         g_signal_connect(G_OBJECT(imd->widget), "destroy",
3924                          G_CALLBACK(image_destroy_cb), imd);
3925
3926         return imd;
3927 }
3928