7b407ac08469aa39c08b9c1d5d02ad62284fb604
[geeqie.git] / src / pan-view.c
1 /*
2  * GQview
3  * (C) 2005 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 "pan-view.h"
15
16 #include "cache.h"
17 #include "dnd.h"
18 #include "editors.h"
19 #include "filelist.h"
20 #include "fullscreen.h"
21 #include "image.h"
22 #include "image-load.h"
23 #include "img-view.h"
24 #include "info.h"
25 #include "menu.h"
26 #include "pixbuf_util.h"
27 #include "thumb.h"
28 #include "utilops.h"
29 #include "ui_bookmark.h"
30 #include "ui_fileops.h"
31 #include "ui_menu.h"
32 #include "ui_misc.h"
33 #include "ui_tabcomp.h"
34
35 #include <gdk/gdkkeysyms.h> /* for keyboard values */
36 #include <math.h>
37
38
39 #define PAN_WINDOW_DEFAULT_WIDTH 720
40 #define PAN_WINDOW_DEFAULT_HEIGHT 500
41
42 #define PAN_TILE_SIZE 512
43
44 #define PAN_THUMB_SIZE_DOTS 4
45 #define PAN_THUMB_SIZE_NONE 24
46 #define PAN_THUMB_SIZE_SMALL 64
47 #define PAN_THUMB_SIZE_NORMAL 128
48 #define PAN_THUMB_SIZE_LARGE 256
49 #define PAN_THUMB_SIZE pw->thumb_size
50
51 #define PAN_THUMB_GAP_DOTS 2
52 #define PAN_THUMB_GAP_SMALL 14
53 #define PAN_THUMB_GAP_NORMAL 30
54 #define PAN_THUMB_GAP_LARGE 40
55 #define PAN_THUMB_GAP_HUGE 50
56 #define PAN_THUMB_GAP pw->thumb_gap
57
58 #define PAN_SHADOW_OFFSET 6
59 #define PAN_SHADOW_FADE 5
60 #define PAN_SHADOW_COLOR 0, 0, 0
61 #define PAN_SHADOW_ALPHA 64
62
63 #define PAN_OUTLINE_THICKNESS 1
64 #define PAN_OUTLINE_COLOR_1 255, 255, 255
65 #define PAN_OUTLINE_COLOR_2 64, 64, 64
66 #define PAN_OUTLINE_ALPHA 180
67
68 #define PAN_BACKGROUND_COLOR 255, 255, 230
69
70 #define PAN_GRID_SIZE 10
71 #define PAN_GRID_COLOR 0, 0, 0
72 #define PAN_GRID_ALPHA 20
73
74 #define PAN_FOLDER_BOX_COLOR 0, 0, 255
75 #define PAN_FOLDER_BOX_ALPHA 10
76 #define PAN_FOLDER_BOX_BORDER 20
77
78 #define PAN_FOLDER_BOX_OUTLINE_THICKNESS 4
79 #define PAN_FOLDER_BOX_OUTLINE_COLOR 0, 0, 255
80 #define PAN_FOLDER_BOX_OUTLINE_ALPHA 64
81
82 #define PAN_TEXT_BORDER_SIZE 4
83 #define PAN_TEXT_COLOR 0, 0, 0
84
85 #define PAN_POPUP_COLOR 255, 255, 220
86 #define PAN_POPUP_ALPHA 255
87 #define PAN_POPUP_BORDER 1
88 #define PAN_POPUP_BORDER_COLOR 0, 0, 0
89 #define PAN_POPUP_TEXT_COLOR 0, 0, 0
90
91 #define PAN_GROUP_MAX 16
92
93 #define ZOOM_INCREMENT 1.0
94 #define ZOOM_LABEL_WIDTH 64
95
96
97 #define PAN_PREF_GROUP "pan_view_options"
98 #define PAN_PREF_HIDE_WARNING "hide_performance_warning"
99
100
101 typedef enum {
102         LAYOUT_TIMELINE = 0,
103         LAYOUT_FOLDERS_LINEAR,
104         LAYOUT_FOLDERS_FLOWER,
105         LAYOUT_GRID,
106 } LayoutType;
107
108 typedef enum {
109         LAYOUT_SIZE_THUMB_DOTS = 0,
110         LAYOUT_SIZE_THUMB_NONE,
111         LAYOUT_SIZE_THUMB_SMALL,
112         LAYOUT_SIZE_THUMB_NORMAL,
113         LAYOUT_SIZE_THUMB_LARGE,
114         LAYOUT_SIZE_10,
115         LAYOUT_SIZE_25,
116         LAYOUT_SIZE_33,
117         LAYOUT_SIZE_50,
118         LAYOUT_SIZE_100
119 } LayoutSize;
120
121 typedef enum {
122         ITEM_NONE,
123         ITEM_THUMB,
124         ITEM_BOX,
125         ITEM_TRIANGLE,
126         ITEM_TEXT,
127         ITEM_IMAGE
128 } ItemType;
129
130 typedef enum {
131         TEXT_ATTR_NONE = 0,
132         TEXT_ATTR_BOLD = 1 << 0,
133         TEXT_ATTR_HEADING = 1 << 1,
134         TEXT_ATTR_MARKUP = 1 << 2
135 } TextAttrType;
136
137 enum {
138         BORDER_NONE = 0,
139         BORDER_1 = 1 << 0,
140         BORDER_2 = 1 << 1,
141         BORDER_3 = 1 << 2,
142         BORDER_4 = 1 << 3
143 };
144
145 typedef struct _PanItem PanItem;
146 struct _PanItem {
147         ItemType type;
148         gint x;
149         gint y;
150         gint width;
151         gint height;
152         gchar *key;
153
154         FileData *fd;
155
156         GdkPixbuf *pixbuf;
157         gint refcount;
158
159         gchar *text;
160         TextAttrType text_attr;
161
162         guint8 color_r;
163         guint8 color_g;
164         guint8 color_b;
165         guint8 color_a;
166
167         guint8 color2_r;
168         guint8 color2_g;
169         guint8 color2_b;
170         guint8 color2_a;
171         gint border;
172
173         gpointer data;
174
175         gint queued;
176 };
177
178 typedef struct _PanWindow PanWindow;
179 struct _PanWindow
180 {
181         GtkWidget *window;
182         ImageWindow *imd;
183         ImageWindow *imd_normal;
184         FullScreenData *fs;
185
186         GtkWidget *path_entry;
187
188         GtkWidget *label_message;
189         GtkWidget *label_zoom;
190
191         GtkWidget *search_box;
192         GtkWidget *search_entry;
193         GtkWidget *search_label;
194         GtkWidget *search_button;
195         GtkWidget *search_button_arrow;
196
197         GtkWidget *scrollbar_h;
198         GtkWidget *scrollbar_v;
199
200         gint overlay_id;
201
202         gchar *path;
203         LayoutType layout;
204         LayoutSize size;
205         gint thumb_size;
206         gint thumb_gap;
207         gint image_size;
208
209         GList *list;
210
211         GList *cache_list;
212         GList *cache_todo;
213         gint cache_count;
214         gint cache_total;
215         gint cache_tick;
216
217         ImageLoader *il;
218         ThumbLoader *tl;
219         PanItem *queue_pi;
220         GList *queue;
221
222         PanItem *click_pi;
223
224         gint idle_id;
225 };
226
227 typedef struct _PanCacheData PanCacheData;
228 struct _PanCacheData {
229         FileData fd;
230         CacheData *cd;
231 };
232
233
234 static GList *pan_window_list = NULL;
235
236
237 static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend);
238
239 static GtkWidget *pan_popup_menu(PanWindow *pw);
240 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off);
241 static void pan_overlay_toggle(PanWindow *pw);
242
243 static void pan_window_close(PanWindow *pw);
244
245 static void pan_window_dnd_init(PanWindow *pw);
246
247
248 static gint util_clip_region(gint x, gint y, gint w, gint h,
249                              gint clip_x, gint clip_y, gint clip_w, gint clip_h,
250                              gint *rx, gint *ry, gint *rw, gint *rh)
251 {
252         if (clip_x + clip_w <= x ||
253             clip_x >= x + w ||
254             clip_y + clip_h <= y ||
255             clip_y >= y + h)
256                 {
257                 return FALSE;
258                 }
259
260         *rx = MAX(x, clip_x);
261         *rw = MIN((x + w), (clip_x + clip_w)) - *rx;
262
263         *ry = MAX(y, clip_y);
264         *rh = MIN((y + h), (clip_y + clip_h)) - *ry;
265
266         return TRUE;
267 }
268
269 static gint util_clip_region_test(gint x, gint y, gint w, gint h,
270                                   gint clip_x, gint clip_y, gint clip_w, gint clip_h)
271 {
272         gint rx, ry, rw, rh;
273
274         return util_clip_region(x, y, w, h,
275                                 clip_x, clip_y, clip_w, clip_h,
276                                 &rx, &ry, &rw, &rh);
277 }
278
279 typedef enum {
280         DATE_LENGTH_EXACT,
281         DATE_LENGTH_HOUR,
282         DATE_LENGTH_DAY,
283         DATE_LENGTH_WEEK,
284         DATE_LENGTH_MONTH,
285         DATE_LENGTH_YEAR
286 } DateLengthType;
287
288 static gint date_compare(time_t a, time_t b, DateLengthType length)
289 {
290         struct tm ta;
291         struct tm tb;
292
293         if (length == DATE_LENGTH_EXACT) return (a == b);
294
295         if (!localtime_r(&a, &ta) ||
296             !localtime_r(&b, &tb)) return FALSE;
297
298         if (ta.tm_year != tb.tm_year) return FALSE;
299         if (length == DATE_LENGTH_YEAR) return TRUE;
300
301         if (ta.tm_mon != tb.tm_mon) return FALSE;
302         if (length == DATE_LENGTH_MONTH) return TRUE;
303
304         if (length == DATE_LENGTH_WEEK) return (ta.tm_yday / 7 == tb.tm_yday / 7);
305
306         if (ta.tm_mday != tb.tm_mday) return FALSE;
307         if (length == DATE_LENGTH_DAY) return TRUE;
308
309         return (ta.tm_hour == tb.tm_hour);
310 }
311
312 static gchar *date_value_string(time_t d, DateLengthType length)
313 {
314         struct tm td;
315         gchar buf[128];
316         gchar *format = NULL;
317
318         if (!localtime_r(&d, &td)) return g_strdup("");
319
320         switch (length)
321                 {
322                 case DATE_LENGTH_DAY:
323                         return g_strdup_printf("%d", td.tm_mday);
324                         break;
325                 case DATE_LENGTH_WEEK:
326                         format = "%A %e";
327                         break;
328                 case DATE_LENGTH_MONTH:
329                         format = "%B %Y";
330                         break;
331                 case DATE_LENGTH_YEAR:
332                         return g_strdup_printf("%d", td.tm_year + 1900);
333                         break;
334                 case DATE_LENGTH_EXACT:
335                 default:
336                         return g_strdup(text_from_time(d));
337                         break;
338                 }
339
340
341         if (format && strftime(buf, sizeof(buf), format, &td) > 0)
342                 {
343                 gchar *ret = g_locale_to_utf8(buf, -1, NULL, NULL, NULL);
344                 if (ret) return ret;
345                 }
346
347         return g_strdup("");
348 }
349
350 static time_t date_to_time(gint year, gint month, gint day)
351 {
352         struct tm lt;
353
354         lt.tm_sec = 0;
355         lt.tm_min = 0;
356         lt.tm_hour = 0;
357         lt.tm_mday = (day >= 1 && day <= 31) ? day : 1;
358         lt.tm_mon = (month >= 1 && month <= 12) ? month - 1 : 0;
359         lt.tm_year = year - 1900;
360         lt.tm_isdst = 0;
361
362         return mktime(&lt);
363 }
364
365 /*
366  *-----------------------------------------------------------------------------
367  * drawing utils
368  *-----------------------------------------------------------------------------
369  */
370
371 static void triangle_rect_region(gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
372                                  gint *rx, gint *ry, gint *rw, gint *rh)
373 {
374         gint tx, ty, tw, th;
375
376         tx = MIN(x1, x2);
377         tx = MIN(tx, x3);
378         ty = MIN(y1, y2);
379         ty = MIN(ty, y3);
380         tw = MAX(abs(x1 - x2), abs(x2 - x3));
381         tw = MAX(tw, abs(x3 - x1));
382         th = MAX(abs(y1 - y2), abs(y2 - y3));
383         th = MAX(th, abs(y3 - y1));
384
385         *rx = tx;
386         *ry = ty;
387         *rw = tw;
388         *rh = th;
389 }
390
391 static void pixbuf_draw_triangle(GdkPixbuf *pb,
392                                  gint clip_x, gint clip_y, gint clip_w, gint clip_h,
393                                  gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
394                                  guint8 r, guint8 g, guint8 b, guint8 a)
395 {
396         gint p_alpha;
397         gint pw, ph, prs;
398         gint rx, ry, rw, rh;
399         gint tx, ty, tw, th;
400         gint fx1, fy1;
401         gint fx2, fy2;
402         gint fw, fh;
403         guchar *p_pix;
404         guchar *pp;
405         gint p_step;
406         gint i, j;
407
408         if (!pb) return;
409
410         pw = gdk_pixbuf_get_width(pb);
411         ph = gdk_pixbuf_get_height(pb);
412
413         if (!util_clip_region(0, 0, pw, ph,
414                               clip_x, clip_y, clip_w, clip_h,
415                               &rx, &ry, &rw, &rh)) return;
416
417         triangle_rect_region(x1, y1, x2, y2, x3, y3,
418                              &tx, &ty, &tw, &th);
419
420         if (!util_clip_region(rx, ry, rw, rh,
421                               tx, ty, tw, th,
422                               &fx1, &fy1, &fw, &fh)) return;
423         fx2 = fx1 + fw;
424         fy2 = fy1 + fh;
425
426         p_alpha = gdk_pixbuf_get_has_alpha(pb);
427         prs = gdk_pixbuf_get_rowstride(pb);
428         p_pix = gdk_pixbuf_get_pixels(pb);
429
430         p_step = (p_alpha) ? 4 : 3;
431         for (i = fy1; i < fy2; i++)
432                 {
433                 pp = p_pix + i * prs + (fx1 * p_step);
434                 for (j = fx1; j < fx2; j++)
435                         {
436                         gint z1, z2;
437
438                         z1 = (y1 - y2)*(j - x2) + (x2 - x1)*(i - y2);
439                         z2 = (y2 - y3)*(j - x3) + (x3 - x2)*(i - y3);
440                         if ((z1 ^ z2) >= 0)
441                                 {
442                                 z2 = (y3 - y1)*(j - x1) + (x1 - x3)*(i - y1);
443                                 if ((z1 ^ z2) >= 0)
444                                         {
445                                         pp[0] = (r * a + pp[0] * (256-a)) >> 8;
446                                         pp[1] = (g * a + pp[1] * (256-a)) >> 8;
447                                         pp[2] = (b * a + pp[2] * (256-a)) >> 8;
448                                         }
449                                 }
450                         pp += p_step;
451                         }
452                 }
453 }
454
455 static void pixbuf_draw_line(GdkPixbuf *pb,
456                              gint clip_x, gint clip_y, gint clip_w, gint clip_h,
457                              gint x1, gint y1, gint x2, gint y2,
458                              guint8 r, guint8 g, guint8 b, guint8 a)
459 {
460         gint p_alpha;
461         gint pw, ph, prs;
462         gint rx, ry, rw, rh;
463         gint fx1, fy1, fx2, fy2;
464         guchar *p_pix;
465         guchar *pp;
466         gint p_step;
467         gint xd, yd;
468         gint xa, ya;
469         gdouble xstep, ystep;
470         gdouble i, j;
471         gint n, nt;
472         gint x, y;
473
474         if (!pb) return;
475
476         pw = gdk_pixbuf_get_width(pb);
477         ph = gdk_pixbuf_get_height(pb);
478
479         if (!util_clip_region(0, 0, pw, ph,
480                               clip_x, clip_y, clip_w, clip_h,
481                               &rx, &ry, &rw, &rh)) return;
482
483         fx1 = rx;
484         fy1 = ry;
485         fx2 = rx + rw;
486         fy2 = ry + rh;
487
488         xd = x2 - x1;
489         yd = y2 - y1;
490         xa = abs(xd);
491         ya = abs(yd);
492
493         if (xa == 0 && ya == 0) return;
494 #if 0
495         nt = sqrt(xd * xd + yd * yd);
496 #endif
497         nt = (xa > ya) ? xa : ya;
498         xstep = (double)xd / nt;
499         ystep = (double)yd / nt;
500
501         p_alpha = gdk_pixbuf_get_has_alpha(pb);
502         prs = gdk_pixbuf_get_rowstride(pb);
503         p_pix = gdk_pixbuf_get_pixels(pb);
504
505         p_step = (p_alpha) ? 4 : 3;
506
507         i = (double)y1;
508         j = (double)x1;
509         for (n = 0; n < nt; n++)
510                 {
511                 x = (gint)(j + 0.5);
512                 y = (gint)(i + 0.5);
513
514                 if (x >= fx1 && x < fx2 &&
515                     y >= fy1 && y < fy2)
516                         {
517                         pp = p_pix + y * prs + x * p_step;
518                         *pp = (r * a + *pp * (256-a)) >> 8;
519                         pp++;
520                         *pp = (g * a + *pp * (256-a)) >> 8;
521                         pp++;
522                         *pp = (b * a + *pp * (256-a)) >> 8;
523                         }
524                 i += ystep;
525                 j += xstep;
526                 }
527 }
528
529 static void pixbuf_draw_fade_linear(guchar *p_pix, gint prs, gint p_alpha,
530                                     gint s, gint vertical, gint border,
531                                     gint x1, gint y1, gint x2, gint y2,
532                                     guint8 r, guint8 g, guint8 b, guint8 a)
533 {
534         guchar *pp;
535         gint p_step;
536         guint8 n = a;
537         gint i, j;
538
539         p_step = (p_alpha) ? 4 : 3;
540         for (j = y1; j < y2; j++)
541                 {
542                 pp = p_pix + j * prs + x1 * p_step;
543                 if (!vertical) n = a - a * abs(j - s) / border;
544                 for (i = x1; i < x2; i++)
545                         {
546                         if (vertical) n = a - a * abs(i - s) / border;
547                         *pp = (r * n + *pp * (256-n)) >> 8;
548                         pp++;
549                         *pp = (g * n + *pp * (256-n)) >> 8;
550                         pp++;
551                         *pp = (b * n + *pp * (256-n)) >> 8;
552                         pp++;
553                         if (p_alpha) pp++;
554                         }
555                 }
556 }
557
558 static void pixbuf_draw_fade_radius(guchar *p_pix, gint prs, gint p_alpha,
559                                     gint sx, gint sy, gint border,
560                                     gint x1, gint y1, gint x2, gint y2,
561                                     guint8 r, guint8 g, guint8 b, guint8 a)
562 {
563         guchar *pp;
564         gint p_step;
565         gint i, j;
566
567         p_step = (p_alpha) ? 4 : 3;
568         for (j = y1; j < y2; j++)
569                 {
570                 pp = p_pix + j * prs + x1 * p_step;
571                 for (i = x1; i < x2; i++)
572                         {
573                         guint8 n;
574                         gint r;
575
576                         r = MIN(border, (gint)sqrt((i-sx)*(i-sx) + (j-sy)*(j-sy)));
577                         n = a - a * r / border;
578                         *pp = (r * n + *pp * (256-n)) >> 8;
579                         pp++;
580                         *pp = (g * n + *pp * (256-n)) >> 8;
581                         pp++;
582                         *pp = (b * n + *pp * (256-n)) >> 8;
583                         pp++;
584                         if (p_alpha) pp++;
585                         }
586                 }
587 }
588
589 static void pixbuf_draw_shadow(GdkPixbuf *pb,
590                                gint clip_x, gint clip_y, gint clip_w, gint clip_h,
591                                gint x, gint y, gint w, gint h, gint border,
592                                guint8 r, guint8 g, guint8 b, guint8 a)
593 {
594         gint p_alpha;
595         gint pw, ph, prs;
596         gint rx, ry, rw, rh;
597         gint fx, fy, fw, fh;
598         guchar *p_pix;
599
600         if (!pb) return;
601
602         pw = gdk_pixbuf_get_width(pb);
603         ph = gdk_pixbuf_get_height(pb);
604
605         if (!util_clip_region(0, 0, pw, ph,
606                               clip_x, clip_y, clip_w, clip_h,
607                               &rx, &ry, &rw, &rh)) return;
608
609         p_alpha = gdk_pixbuf_get_has_alpha(pb);
610         prs = gdk_pixbuf_get_rowstride(pb);
611         p_pix = gdk_pixbuf_get_pixels(pb);
612
613         if (util_clip_region(x + border, y + border, w - border * 2, h - border * 2,
614                              rx, ry, rw, rh,
615                              &fx, &fy, &fw, &fh))
616                 {
617                 pixbuf_draw_rect_fill(pb, fx, fy, fw, fh, r, g, b, a);
618                 }
619
620         if (border < 1) return;
621
622         if (util_clip_region(x, y + border, border, h - border * 2,
623                              rx, ry, rw, rh,
624                              &fx, &fy, &fw, &fh))
625                 {
626                 pixbuf_draw_fade_linear(p_pix, prs, p_alpha,
627                                         x + border, TRUE, border,
628                                         fx, fy, fx + fw, fy + fh,
629                                         r, g, b, a);
630                 }
631         if (util_clip_region(x + w - border, y + border, border, h - border * 2,
632                              rx, ry, rw, rh,
633                              &fx, &fy, &fw, &fh))
634                 {
635                 pixbuf_draw_fade_linear(p_pix, prs, p_alpha,
636                                         x + w - border, TRUE, border,
637                                         fx, fy, fx + fw, fy + fh,
638                                         r, g, b, a);
639                 }
640         if (util_clip_region(x + border, y, w - border * 2, border,
641                              rx, ry, rw, rh,
642                              &fx, &fy, &fw, &fh))
643                 {
644                 pixbuf_draw_fade_linear(p_pix, prs, p_alpha,
645                                         y + border, FALSE, border,
646                                         fx, fy, fx + fw, fy + fh,
647                                         r, g, b, a);
648                 }
649         if (util_clip_region(x + border, y + h - border, w - border * 2, border,
650                              rx, ry, rw, rh,
651                              &fx, &fy, &fw, &fh))
652                 {
653                 pixbuf_draw_fade_linear(p_pix, prs, p_alpha,
654                                         y + h - border, FALSE, border,
655                                         fx, fy, fx + fw, fy + fh,
656                                         r, g, b, a);
657                 }
658         if (util_clip_region(x, y, border, border,
659                              rx, ry, rw, rh,
660                              &fx, &fy, &fw, &fh))
661                 {
662                 pixbuf_draw_fade_radius(p_pix, prs, p_alpha,
663                                         x + border, y + border, border,
664                                         fx, fy, fx + fw, fy + fh,
665                                         r, g, b, a);
666                 }
667         if (util_clip_region(x + w - border, y, border, border,
668                              rx, ry, rw, rh,
669                              &fx, &fy, &fw, &fh))
670                 {
671                 pixbuf_draw_fade_radius(p_pix, prs, p_alpha,
672                                         x + w - border, y + border, border,
673                                         fx, fy, fx + fw, fy + fh,
674                                         r, g, b, a);
675                 }
676         if (util_clip_region(x, y + h - border, border, border,
677                              rx, ry, rw, rh,
678                              &fx, &fy, &fw, &fh))
679                 {
680                 pixbuf_draw_fade_radius(p_pix, prs, p_alpha,
681                                         x + border, y + h - border, border,
682                                         fx, fy, fx + fw, fy + fh,
683                                         r, g, b, a);
684                 }
685         if (util_clip_region(x + w - border, y + h - border, border, border,
686                              rx, ry, rw, rh,
687                              &fx, &fy, &fw, &fh))
688                 {
689                 pixbuf_draw_fade_radius(p_pix, prs, p_alpha,
690                                         x + w - border, y + h - border, border,
691                                         fx, fy, fx + fw, fy + fh,
692                                         r, g, b, a);
693                 }
694 }
695                                 
696
697 /*
698  *-----------------------------------------------------------------------------
699  * cache
700  *-----------------------------------------------------------------------------
701  */
702
703 static void pan_cache_free(PanWindow *pw)
704 {
705         GList *work;
706
707         work = pw->cache_list;
708         while (work)
709                 {
710                 PanCacheData *pc;
711
712                 pc = work->data;
713                 work = work->next;
714
715                 cache_sim_data_free(pc->cd);
716                 file_data_free((FileData *)pc);
717                 }
718
719         g_list_free(pw->cache_list);
720         pw->cache_list = NULL;
721
722         filelist_free(pw->cache_todo);
723         pw->cache_todo = NULL;
724
725         pw->cache_count = 0;
726         pw->cache_total = 0;
727         pw->cache_tick = 0;
728 }
729
730 static void pan_cache_fill(PanWindow *pw, const gchar *path)
731 {
732         GList *list;
733
734         pan_cache_free(pw);
735
736         list = pan_window_layout_list(path, SORT_NAME, TRUE);
737         pw->cache_todo = g_list_reverse(list);
738
739         pw->cache_total = g_list_length(pw->cache_todo);
740 }
741
742 static gint pan_cache_step(PanWindow *pw)
743 {
744         FileData *fd;
745         PanCacheData *pc;
746         CacheData *cd = NULL;
747
748         if (!pw->cache_todo) return FALSE;
749
750         fd = pw->cache_todo->data;
751         pw->cache_todo = g_list_remove(pw->cache_todo, fd);
752
753         if (enable_thumb_caching)
754                 {
755                 gchar *found;
756
757                 found = cache_find_location(CACHE_TYPE_SIM, fd->path);
758                 if (found && filetime(found) == fd->date)
759                         {
760                         cd = cache_sim_data_load(found);
761                         }
762                 g_free(found);
763                 }
764
765         if (!cd) cd = cache_sim_data_new();
766
767         if (!cd->dimensions)
768                 {
769                 cd->dimensions = image_load_dimensions(fd->path, &cd->width, &cd->height);
770                 if (enable_thumb_caching &&
771                     cd->dimensions)
772                         {
773                         gchar *base;
774                         mode_t mode = 0755;
775
776                         base = cache_get_location(CACHE_TYPE_SIM, fd->path, FALSE, &mode);
777                         if (cache_ensure_dir_exists(base, mode))
778                                 {
779                                 g_free(cd->path);
780                                 cd->path = cache_get_location(CACHE_TYPE_SIM, fd->path, TRUE, NULL);
781                                 if (cache_sim_data_save(cd))
782                                         {
783                                         filetime_set(cd->path, filetime(fd->path));
784                                         }
785                                 }
786                         g_free(base);
787                         }
788
789                 pw->cache_tick = 9;
790                 }
791
792         pc = g_new0(PanCacheData, 1);
793         memcpy(pc, fd, sizeof(FileData));
794         g_free(fd);
795
796         pc->cd = cd;
797
798         pw->cache_list = g_list_prepend(pw->cache_list, pc);
799
800         return TRUE;
801 }
802
803
804 /*
805  *-----------------------------------------------------------------------------
806  * item objects
807  *-----------------------------------------------------------------------------
808  */
809
810 static void pan_item_free(PanItem *pi)
811 {
812         if (!pi) return;
813
814         if (pi->pixbuf) g_object_unref(pi->pixbuf);
815         if (pi->fd) file_data_free(pi->fd);
816         g_free(pi->text);
817         g_free(pi->key);
818         g_free(pi->data);
819
820         g_free(pi);
821 }
822
823 static void pan_window_items_free(PanWindow *pw)
824 {
825         GList *work;
826
827         work = pw->list;
828         while (work)
829                 {
830                 PanItem *pi = work->data;
831                 work = work->next;
832
833                 pan_item_free(pi);
834                 }
835
836         g_list_free(pw->list);
837         pw->list = NULL;
838
839         g_list_free(pw->queue);
840         pw->queue = NULL;
841         pw->queue_pi = NULL;
842
843         image_loader_free(pw->il);
844         pw->il = NULL;
845
846         thumb_loader_free(pw->tl);
847         pw->tl = NULL;
848
849         pw->click_pi = NULL;
850 }
851
852 static PanItem *pan_item_new_thumb(PanWindow *pw, FileData *fd, gint x, gint y)
853 {
854         PanItem *pi;
855
856         pi = g_new0(PanItem, 1);
857         pi->type = ITEM_THUMB;
858         pi->fd = fd;
859         pi->x = x;
860         pi->y = y;
861         pi->width = PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2;
862         pi->height = PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2;
863
864         pi->pixbuf = NULL;
865
866         pi->queued = FALSE;
867
868         pw->list = g_list_prepend(pw->list, pi);
869
870         return pi;
871 }
872
873 static PanItem *pan_item_new_box(PanWindow *pw, FileData *fd, gint x, gint y, gint width, gint height,
874                                  gint border_size,
875                                  guint8 base_r, guint8 base_g, guint8 base_b, guint8 base_a,
876                                  guint8 bord_r, guint8 bord_g, guint8 bord_b, guint8 bord_a)
877 {
878         PanItem *pi;
879
880         pi = g_new0(PanItem, 1);
881         pi->type = ITEM_BOX;
882         pi->fd = fd;
883         pi->x = x;
884         pi->y = y;
885         pi->width = width;
886         pi->height = height;
887
888         pi->color_r = base_r;
889         pi->color_g = base_g;
890         pi->color_b = base_b;
891         pi->color_a = base_a;
892
893         pi->color2_r = bord_r;
894         pi->color2_g = bord_g;
895         pi->color2_b = bord_b;
896         pi->color2_a = bord_a;
897         pi->border = border_size;
898
899         pw->list = g_list_prepend(pw->list, pi);
900
901         return pi;
902 }
903
904 static void pan_item_box_shadow(PanItem *pi, gint offset, gint fade)
905 {
906         gint *shadow;
907
908         if (!pi || pi->type != ITEM_BOX) return;
909
910         shadow = pi->data;
911         if (shadow)
912                 {
913                 pi->width -= shadow[0];
914                 pi->height -= shadow[0];
915                 }
916
917         shadow = g_new0(gint, 2);
918         shadow[0] = offset;
919         shadow[1] = fade;
920
921         pi->width += offset;
922         pi->height += offset;
923
924         g_free(pi->data);
925         pi->data = shadow;
926 }
927
928 static PanItem *pan_item_new_tri(PanWindow *pw, FileData *fd, gint x, gint y, gint width, gint height,
929                                  gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
930                                  guint8 r, guint8 g, guint8 b, guint8 a)
931 {
932         PanItem *pi;
933         gint *coord;
934
935         pi = g_new0(PanItem, 1);
936         pi->type = ITEM_TRIANGLE;
937         pi->x = x;
938         pi->y = y;
939         pi->width = width;
940         pi->height = height;
941
942         pi->color_r = r;
943         pi->color_g = g;
944         pi->color_b = b;
945         pi->color_a = a;
946
947         coord = g_new0(gint, 6);
948         coord[0] = x1;
949         coord[1] = y1;
950         coord[2] = x2;
951         coord[3] = y2;
952         coord[4] = x3;
953         coord[5] = y3;
954
955         pi->data = coord;
956
957         pi->border = BORDER_NONE;
958
959         pw->list = g_list_prepend(pw->list, pi);
960
961         return pi;
962 }
963
964 static void pan_item_tri_border(PanItem *pi, gint borders,
965                                 guint8 r, guint8 g, guint8 b, guint8 a)
966 {
967         if (!pi || pi->type != ITEM_TRIANGLE) return;
968
969         pi->border = borders;
970
971         pi->color2_r = r;
972         pi->color2_g = g;
973         pi->color2_b = b;
974         pi->color2_a = a;
975 }
976
977 static PangoLayout *pan_item_text_layout(PanItem *pi, GtkWidget *widget)
978 {
979         PangoLayout *layout;
980
981         layout = gtk_widget_create_pango_layout(widget, NULL);
982
983         if (pi->text_attr & TEXT_ATTR_MARKUP)
984                 {
985                 pango_layout_set_markup(layout, pi->text, -1);
986                 return layout;
987                 }
988
989         if (pi->text_attr & TEXT_ATTR_BOLD ||
990             pi->text_attr & TEXT_ATTR_HEADING)
991                 {
992                 PangoAttrList *pal;
993                 PangoAttribute *pa;
994                 
995                 pal = pango_attr_list_new();
996                 if (pi->text_attr & TEXT_ATTR_BOLD)
997                         {
998                         pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
999                         pa->start_index = 0;
1000                         pa->end_index = G_MAXINT;
1001                         pango_attr_list_insert(pal, pa);
1002                         }
1003                 if (pi->text_attr & TEXT_ATTR_HEADING)
1004                         {
1005                         pa = pango_attr_scale_new(PANGO_SCALE_LARGE);
1006                         pa->start_index = 0;
1007                         pa->end_index = G_MAXINT;
1008                         pango_attr_list_insert(pal, pa);
1009                         }
1010                 pango_layout_set_attributes(layout, pal);
1011                 pango_attr_list_unref(pal);
1012                 }
1013
1014         pango_layout_set_text(layout, pi->text, -1);
1015         return layout;
1016 }
1017
1018 static void pan_item_text_compute_size(PanItem *pi, GtkWidget *widget)
1019 {
1020         PangoLayout *layout;
1021
1022         if (!pi || !pi->text || !widget) return;
1023
1024         layout = pan_item_text_layout(pi, widget);
1025         pango_layout_get_pixel_size(layout, &pi->width, &pi->height);
1026         g_object_unref(G_OBJECT(layout));
1027
1028         pi->width += PAN_TEXT_BORDER_SIZE * 2;
1029         pi->height += PAN_TEXT_BORDER_SIZE * 2;
1030 }
1031
1032 static PanItem *pan_item_new_text(PanWindow *pw, gint x, gint y, const gchar *text, TextAttrType attr,
1033                                   guint8 r, guint8 g, guint8 b, guint8 a)
1034 {
1035         PanItem *pi;
1036
1037         pi = g_new0(PanItem, 1);
1038         pi->type = ITEM_TEXT;
1039         pi->x = x;
1040         pi->y = y;
1041         pi->text = g_strdup(text);
1042         pi->text_attr = attr;
1043
1044         pi->color_r = r;
1045         pi->color_g = g;
1046         pi->color_b = b;
1047         pi->color_a = a;
1048
1049         pan_item_text_compute_size(pi, pw->imd->widget);
1050
1051         pw->list = g_list_prepend(pw->list, pi);
1052
1053         return pi;
1054 }
1055
1056 static void pan_item_set_key(PanItem *pi, const gchar *key)
1057 {
1058         gchar *tmp;
1059
1060         if (!pi) return;
1061
1062         tmp = pi->key;
1063         pi->key = g_strdup(key);
1064         g_free(tmp);
1065 }
1066
1067 static void pan_item_added(PanWindow *pw, PanItem *pi)
1068 {
1069         if (!pi) return;
1070         image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
1071 }
1072
1073 static void pan_item_remove(PanWindow *pw, PanItem *pi)
1074 {
1075         if (!pi) return;
1076
1077         if (pw->click_pi == pi) pw->click_pi = NULL;
1078
1079         pw->list = g_list_remove(pw->list, pi);
1080         image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
1081         pan_item_free(pi);
1082 }
1083
1084 static void pan_item_size_by_item(PanItem *pi, PanItem *child, gint border)
1085 {
1086         if (!pi || !child) return;
1087
1088         if (pi->x + pi->width < child->x + child->width + border)
1089                 pi->width = child->x + child->width + border - pi->x;
1090
1091         if (pi->y + pi->height < child->y + child->height + border)
1092                 pi->height = child->y + child->height + border - pi->y;
1093 }
1094
1095 static void pan_item_size_coordinates(PanItem *pi, gint border, gint *w, gint *h)
1096 {
1097         if (!pi) return;
1098
1099         if (*w < pi->x + pi->width + border) *w = pi->x + pi->width + border;
1100         if (*h < pi->y + pi->height + border) *h = pi->y + pi->height + border;
1101 }
1102
1103 static void pan_item_image_find_size(PanWindow *pw, PanItem *pi, gint w, gint h)
1104 {
1105         GList *work;
1106
1107         pi->width = w;
1108         pi->height = h;
1109
1110         if (!pi->fd) return;
1111
1112         work = pw->cache_list;
1113         while (work)
1114                 {
1115                 PanCacheData *pc;
1116                 gchar *path;
1117
1118                 pc = work->data;
1119                 work = work->next;
1120
1121                 path = ((FileData *)pc)->path;
1122
1123                 if (pc->cd && pc->cd->dimensions &&
1124                     path && strcmp(path, pi->fd->path) == 0)
1125                         {
1126                         pi->width = MAX(1, pc->cd->width * pw->image_size / 100);
1127                         pi->height = MAX(1, pc->cd->height * pw->image_size / 100);
1128
1129                         pw->cache_list = g_list_remove(pw->cache_list, pc);
1130                         cache_sim_data_free(pc->cd);
1131                         file_data_free((FileData *)pc);
1132                         return;
1133                         }
1134                 }
1135 }
1136
1137 static PanItem *pan_item_new_image(PanWindow *pw, FileData *fd, gint x, gint y, gint w, gint h)
1138 {
1139         PanItem *pi;
1140
1141         pi = g_new0(PanItem, 1);
1142         pi->type = ITEM_IMAGE;
1143         pi->fd = fd;
1144         pi->x = x;
1145         pi->y = y;
1146
1147         pan_item_image_find_size(pw, pi, w, h);
1148
1149         pw->list = g_list_prepend(pw->list, pi);
1150
1151         return pi;
1152 }
1153
1154 static PanItem *pan_item_find_by_key(PanWindow *pw, ItemType type, const gchar *key)
1155 {
1156         GList *work;
1157
1158         if (!key) return NULL;
1159
1160         work = g_list_last(pw->list);
1161         while (work)
1162                 {
1163                 PanItem *pi;
1164
1165                 pi = work->data;
1166                 if ((pi->type == type || type == ITEM_NONE) &&
1167                      pi->key && strcmp(pi->key, key) == 0)
1168                         {
1169                         return pi;
1170                         }
1171                 work = work->prev;
1172                 }
1173
1174         return NULL;
1175 }
1176
1177 /* when ignore_case and partial are TRUE, path should be converted to lower case */
1178 static GList *pan_item_find_by_path(PanWindow *pw, ItemType type, const gchar *path,
1179                                     gint ignore_case, gint partial)
1180 {
1181         GList *list = NULL;
1182         GList *work;
1183
1184         if (!path) return NULL;
1185         if (partial && path[0] == '/') return NULL;
1186
1187         work = g_list_last(pw->list);
1188         while (work)
1189                 {
1190                 PanItem *pi;
1191
1192                 pi = work->data;
1193                 if ((pi->type == type || type == ITEM_NONE) && pi->fd)
1194                         {
1195                         gint match = FALSE;
1196
1197                         if (path[0] == '/')
1198                                 {
1199                                 if (pi->fd->path && strcmp(path, pi->fd->path) == 0) match = TRUE;
1200                                 }
1201                         else if (pi->fd->name)
1202                                 {
1203                                 if (partial)
1204                                         {
1205                                         if (ignore_case)
1206                                                 {
1207                                                 gchar *haystack;
1208
1209                                                 haystack = g_utf8_strdown(pi->fd->name, -1);
1210                                                 match = (strstr(haystack, path) != NULL);
1211                                                 g_free(haystack);
1212                                                 }
1213                                         else
1214                                                 {
1215                                                 if (strstr(pi->fd->name, path)) match = TRUE;
1216                                                 }
1217                                         }
1218                                 else if (ignore_case)
1219                                         {
1220                                         if (strcasecmp(path, pi->fd->name) == 0) match = TRUE;
1221                                         }
1222                                 else
1223                                         {
1224                                         if (strcmp(path, pi->fd->name) == 0) match = TRUE;
1225                                         }
1226                                 }
1227
1228                         if (match) list = g_list_prepend(list, pi);
1229                         }
1230                 work = work->prev;
1231                 }
1232
1233         return g_list_reverse(list);
1234 }
1235
1236 static PanItem *pan_item_find_by_coord(PanWindow *pw, ItemType type, gint x, gint y)
1237 {
1238         GList *work;
1239
1240         if (x < 0 || x >= pw->imd->image_width ||
1241             y < 0 || y >= pw->imd->image_height) return  NULL;
1242
1243         work = pw->list;
1244         while (work)
1245                 {
1246                 PanItem *pi;
1247
1248                 pi = work->data;
1249                 if ((pi->type == type || type == ITEM_NONE) &&
1250                      x >= pi->x && x < pi->x + pi->width &&
1251                      y >= pi->y && y < pi->y + pi->height)
1252                         {
1253                         return pi;
1254                         }
1255                 work = work->next;
1256                 }
1257
1258         return NULL;
1259 }
1260
1261 /*
1262  *-----------------------------------------------------------------------------
1263  * layout generation
1264  *-----------------------------------------------------------------------------
1265  */
1266
1267 static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend)
1268 {
1269         GList *flist = NULL;
1270         GList *dlist = NULL;
1271         GList *result;
1272         GList *folders;
1273
1274         filelist_read(path, &flist, &dlist);
1275         if (sort != SORT_NONE)
1276                 {
1277                 flist = filelist_sort(flist, sort, ascend);
1278                 dlist = filelist_sort(dlist, sort, ascend);
1279                 }
1280
1281         result = flist;
1282         folders = dlist;
1283         while (folders)
1284                 {
1285                 FileData *fd;
1286
1287                 fd = folders->data;
1288                 folders = g_list_remove(folders, fd);
1289
1290                 if (filelist_read(fd->path, &flist, &dlist))
1291                         {
1292                         if (sort != SORT_NONE)
1293                                 {
1294                                 flist = filelist_sort(flist, sort, ascend);
1295                                 dlist = filelist_sort(dlist, sort, ascend);
1296                                 }
1297
1298                         result = g_list_concat(result, flist);
1299                         folders = g_list_concat(dlist, folders);
1300                         }
1301
1302                 file_data_free(fd);
1303                 }
1304
1305         return result;
1306 }
1307
1308 static void pan_window_layout_compute_grid(PanWindow *pw, const gchar *path, gint *width, gint *height)
1309 {
1310         GList *list;
1311         GList *work;
1312         gint x, y;
1313         gint grid_size;
1314         gint next_y;
1315
1316         list = pan_window_layout_list(path, SORT_NAME, TRUE);
1317
1318         grid_size = (gint)sqrt((double)g_list_length(list));
1319         if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1320                 {
1321                 grid_size = grid_size * (512 + PAN_THUMB_GAP) * pw->image_size / 100;
1322                 }
1323         else
1324                 {
1325                 grid_size = grid_size * (PAN_THUMB_SIZE + PAN_THUMB_GAP);
1326                 }
1327
1328         next_y = 0;
1329
1330         *width = PAN_FOLDER_BOX_BORDER * 2;
1331         *height = PAN_FOLDER_BOX_BORDER * 2;
1332
1333         x = PAN_THUMB_GAP;
1334         y = PAN_THUMB_GAP;
1335         work = list;
1336         while (work)
1337                 {
1338                 FileData *fd;
1339                 PanItem *pi;
1340
1341                 fd = work->data;
1342                 work = work->next;
1343
1344                 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1345                         {
1346                         pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1347
1348                         x += pi->width + PAN_THUMB_GAP;
1349                         if (y + pi->height + PAN_THUMB_GAP > next_y) next_y = y + pi->height + PAN_THUMB_GAP;
1350                         if (x > grid_size)
1351                                 {
1352                                 x = PAN_THUMB_GAP;
1353                                 y = next_y;
1354                                 }
1355                         }
1356                 else
1357                         {
1358                         pi = pan_item_new_thumb(pw, fd, x, y);
1359
1360                         x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1361                         if (x > grid_size)
1362                                 {
1363                                 x = PAN_THUMB_GAP;
1364                                 y += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1365                                 }
1366                         }
1367                 pan_item_size_coordinates(pi, PAN_THUMB_GAP, width, height);
1368                 }
1369
1370         g_list_free(list);
1371 }
1372
1373 static void pan_window_Layout_compute_folders_flower_size(PanWindow *pw, gint *width, gint *height)
1374 {
1375         GList *work;
1376         gint x1, y1, x2, y2;
1377
1378         x1 = 0;
1379         y1 = 0;
1380         x2 = 0;
1381         y2 = 0;
1382
1383         work = pw->list;
1384         while (work)
1385                 {
1386                 PanItem *pi;
1387
1388                 pi = work->data;
1389                 work = work->next;
1390
1391                 if (x1 > pi->x) x1 = pi->x;
1392                 if (y1 > pi->y) y1 = pi->y;
1393                 if (x2 < pi->x + pi->width) x2 = pi->x + pi->width;
1394                 if (y2 < pi->y + pi->height) y2 = pi->y + pi->height;
1395                 }
1396
1397         x1 -= PAN_FOLDER_BOX_BORDER;
1398         y1 -= PAN_FOLDER_BOX_BORDER;
1399         x2 += PAN_FOLDER_BOX_BORDER;
1400         y2 += PAN_FOLDER_BOX_BORDER;
1401
1402         work = pw->list;
1403         while (work)
1404                 {
1405                 PanItem *pi;
1406
1407                 pi = work->data;
1408                 work = work->next;
1409
1410                 pi->x -= x1;
1411                 pi->y -= y1;
1412
1413                 if (pi->type == ITEM_TRIANGLE && pi->data)
1414                         {
1415                         gint *coord;
1416
1417                         coord = pi->data;
1418                         coord[0] -= x1;
1419                         coord[1] -= y1;
1420                         coord[2] -= x1;
1421                         coord[3] -= y1;
1422                         coord[4] -= x1;
1423                         coord[5] -= y1;
1424                         }
1425                 }
1426
1427         if (width) *width = x2 - x1;
1428         if (height) *height = y2 - y1;
1429 }
1430
1431 typedef struct _FlowerGroup FlowerGroup;
1432 struct _FlowerGroup {
1433         GList *items;
1434         GList *children;
1435         gint x;
1436         gint y;
1437         gint width;
1438         gint height;
1439
1440         gdouble angle;
1441         gint circumference;
1442         gint diameter;
1443 };
1444
1445 static void pan_window_layout_compute_folder_flower_move(FlowerGroup *group, gint x, gint y)
1446 {
1447         GList *work;
1448
1449         work = group->items;
1450         while (work)
1451                 {
1452                 PanItem *pi;
1453
1454                 pi = work->data;
1455                 work = work->next;
1456
1457                 pi->x += x;
1458                 pi->y += y;
1459                 }
1460
1461         group->x += x;
1462         group->y += y;
1463 }
1464
1465 #define PI 3.14159
1466
1467 static void pan_window_layout_compute_folder_flower_position(FlowerGroup *group, FlowerGroup *parent,
1468                                                              gint *result_x, gint *result_y)
1469 {
1470         gint x, y;
1471         gint radius;
1472         gdouble a;
1473
1474         radius = parent->circumference / (2*PI);
1475         radius = MAX(radius, parent->diameter / 2 + group->diameter / 2);
1476
1477         a = 2*PI * group->diameter / parent->circumference;
1478
1479         x = (gint)((double)radius * cos(parent->angle + a / 2));
1480         y = (gint)((double)radius * sin(parent->angle + a / 2));
1481
1482         parent->angle += a;
1483
1484         x += parent->x;
1485         y += parent->y;
1486
1487         x += parent->width / 2;
1488         y += parent->height / 2;
1489
1490         x -= group->width / 2;
1491         y -= group->height / 2;
1492
1493         *result_x = x;
1494         *result_y = y;
1495 }
1496
1497 static void pan_window_layout_compute_folder_flower_build(PanWindow *pw, FlowerGroup *group, FlowerGroup *parent)
1498 {
1499         GList *work;
1500         gint x, y;
1501
1502         if (!group) return;
1503
1504         if (parent && parent->children)
1505                 {
1506                 pan_window_layout_compute_folder_flower_position(group, parent, &x, &y);
1507                 }
1508         else
1509                 {
1510                 x = 0;
1511                 y = 0;
1512                 }
1513
1514         pan_window_layout_compute_folder_flower_move(group, x, y);
1515
1516         if (parent)
1517                 {
1518                 PanItem *pi;
1519                 gint px, py, gx, gy;
1520                 gint x1, y1, x2, y2;
1521
1522                 px = parent->x + parent->width / 2;
1523                 py = parent->y + parent->height / 2;
1524
1525                 gx = group->x + group->width / 2;
1526                 gy = group->y + group->height / 2;
1527
1528                 x1 = MIN(px, gx);
1529                 y1 = MIN(py, gy);
1530
1531                 x2 = MAX(px, gx + 5);
1532                 y2 = MAX(py, gy + 5);
1533
1534                 pi = pan_item_new_tri(pw, NULL, x1, y1, x2 - x1, y2 - y1,
1535                                       px, py, gx, gy, gx + 5, gy + 5,
1536                                       255, 40, 40, 128);
1537                 pan_item_tri_border(pi, BORDER_1 | BORDER_3,
1538                                     255, 0, 0, 128);
1539                 }
1540
1541         pw->list = g_list_concat(group->items, pw->list);
1542         group->items = NULL;
1543
1544         group->circumference = 0;
1545         work = group->children;
1546         while (work)
1547                 {
1548                 FlowerGroup *child;
1549
1550                 child = work->data;
1551                 work = work->next;
1552
1553                 group->circumference += child->diameter;
1554                 }
1555
1556         work = g_list_last(group->children);
1557         while (work)
1558                 {
1559                 FlowerGroup *child;
1560
1561                 child = work->data;
1562                 work = work->prev;
1563
1564                 pan_window_layout_compute_folder_flower_build(pw, child, group);
1565                 }
1566
1567         g_list_free(group->children);
1568         g_free(group);
1569 }
1570
1571 static FlowerGroup *pan_window_layout_compute_folders_flower_path(PanWindow *pw, const gchar *path,
1572                                                                   gint x, gint y)
1573 {
1574         FlowerGroup *group;
1575         GList *f;
1576         GList *d;
1577         GList *work;
1578         PanItem *pi_box;
1579         gint x_start;
1580         gint y_height;
1581         gint grid_size;
1582         gint grid_count;
1583
1584         if (!filelist_read(path, &f, &d)) return NULL;
1585         if (!f && !d) return NULL;
1586
1587         f = filelist_sort(f, SORT_NAME, TRUE);
1588         d = filelist_sort(d, SORT_NAME, TRUE);
1589
1590         pi_box = pan_item_new_text(pw, x, y, path, TEXT_ATTR_NONE,
1591                                    PAN_TEXT_COLOR, 255);
1592
1593         y += pi_box->height;
1594
1595         pi_box = pan_item_new_box(pw, file_data_new_simple(path),
1596                                   x, y,
1597                                   PAN_FOLDER_BOX_BORDER * 2, PAN_FOLDER_BOX_BORDER * 2,
1598                                   PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1599                                   PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1600                                   PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1601
1602         x += PAN_FOLDER_BOX_BORDER;
1603         y += PAN_FOLDER_BOX_BORDER;
1604
1605         grid_size = (gint)(sqrt(g_list_length(f)) + 0.9);
1606         grid_count = 0;
1607         x_start = x;
1608         y_height = y;
1609
1610         work = f;
1611         while (work)
1612                 {
1613                 FileData *fd;
1614                 PanItem *pi;
1615
1616                 fd = work->data;
1617                 work = work->next;
1618
1619                 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1620                         {
1621                         pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1622                         x += pi->width + PAN_THUMB_GAP;
1623                         if (pi->height > y_height) y_height = pi->height;
1624                         }
1625                 else
1626                         {
1627                         pi = pan_item_new_thumb(pw, fd, x, y);
1628                         x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1629                         y_height = PAN_THUMB_SIZE;
1630                         }
1631
1632                 grid_count++;
1633                 if (grid_count >= grid_size)
1634                         {
1635                         grid_count = 0;
1636                         x = x_start;
1637                         y += y_height + PAN_THUMB_GAP;
1638                         y_height = 0;
1639                         }
1640
1641                 pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER);
1642                 }
1643
1644         g_list_free(f);
1645
1646         group = g_new0(FlowerGroup, 1);
1647         group->items = pw->list;
1648         pw->list = NULL;
1649
1650         group->width = pi_box->width;
1651         group->height = pi_box->y + pi_box->height;
1652         group->diameter = (int)sqrt(group->width * group->width + group->height * group->height);
1653
1654         group->children = NULL;
1655
1656         work = d;
1657         while (work)
1658                 {
1659                 FileData *fd;
1660                 FlowerGroup *child;
1661
1662                 fd = work->data;
1663                 work = work->next;
1664
1665                 child = pan_window_layout_compute_folders_flower_path(pw, fd->path, 0, 0);
1666                 if (child) group->children = g_list_prepend(group->children, child);
1667                 }
1668
1669         filelist_free(d);
1670
1671         return group;
1672 }
1673
1674 static void pan_window_layout_compute_folders_flower(PanWindow *pw, const gchar *path,
1675                                                      gint *width, gint *height,
1676                                                      gint *scroll_x, gint *scroll_y)
1677 {
1678         FlowerGroup *group;
1679         GList *list;
1680
1681         group = pan_window_layout_compute_folders_flower_path(pw, path, 0, 0);
1682         pan_window_layout_compute_folder_flower_build(pw, group, NULL);
1683
1684         pan_window_Layout_compute_folders_flower_size(pw, width, height);
1685
1686         list = pan_item_find_by_path(pw, ITEM_BOX, path, FALSE, FALSE);
1687         if (list)
1688                 {
1689                 PanItem *pi = list->data;
1690                 *scroll_x = pi->x + pi->width / 2;
1691                 *scroll_y = pi->y + pi->height / 2;
1692                 }
1693         g_list_free(list);
1694 }
1695
1696 static void pan_window_layout_compute_folders_linear_path(PanWindow *pw, const gchar *path,
1697                                                           gint *x, gint *y, gint *level,
1698                                                           PanItem *parent,
1699                                                           gint *width, gint *height)
1700 {
1701         GList *f;
1702         GList *d;
1703         GList *work;
1704         PanItem *pi_box;
1705         gint y_height = 0;
1706
1707         if (!filelist_read(path, &f, &d)) return;
1708         if (!f && !d) return;
1709
1710         f = filelist_sort(f, SORT_NAME, TRUE);
1711         d = filelist_sort(d, SORT_NAME, TRUE);
1712
1713         *x = PAN_FOLDER_BOX_BORDER + ((*level) * MAX(PAN_FOLDER_BOX_BORDER, PAN_THUMB_GAP));
1714
1715         pi_box = pan_item_new_text(pw, *x, *y, path, TEXT_ATTR_NONE,
1716                                    PAN_TEXT_COLOR, 255);
1717
1718         *y += pi_box->height;
1719
1720         pi_box = pan_item_new_box(pw, file_data_new_simple(path),
1721                                   *x, *y,
1722                                   PAN_FOLDER_BOX_BORDER, PAN_FOLDER_BOX_BORDER,
1723                                   PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1724                                   PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1725                                   PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1726
1727         *x += PAN_FOLDER_BOX_BORDER;
1728         *y += PAN_FOLDER_BOX_BORDER;
1729
1730         work = f;
1731         while (work)
1732                 {
1733                 FileData *fd;
1734                 PanItem *pi;
1735
1736                 fd = work->data;
1737                 work = work->next;
1738
1739                 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1740                         {
1741                         pi = pan_item_new_image(pw, fd, *x, *y, 10, 10);
1742                         *x += pi->width + PAN_THUMB_GAP;
1743                         if (pi->height > y_height) y_height = pi->height;
1744                         }
1745                 else
1746                         {
1747                         pi = pan_item_new_thumb(pw, fd, *x, *y);
1748                         *x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1749                         y_height = PAN_THUMB_SIZE;
1750                         }
1751
1752                 pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER);
1753                 }
1754
1755         if (f) *y = pi_box->y + pi_box->height;
1756
1757         g_list_free(f);
1758
1759         work = d;
1760         while (work)
1761                 {
1762                 FileData *fd;
1763
1764                 fd = work->data;
1765                 work = work->next;
1766
1767                 *level = *level + 1;
1768                 pan_window_layout_compute_folders_linear_path(pw, fd->path, x, y, level,
1769                                                               pi_box, width, height);
1770                 *level = *level - 1;
1771                 }
1772
1773         filelist_free(d);
1774
1775         pan_item_size_by_item(parent, pi_box, PAN_FOLDER_BOX_BORDER);
1776
1777         if (*y < pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER)
1778                 *y = pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER;
1779
1780         pan_item_size_coordinates(pi_box, PAN_FOLDER_BOX_BORDER, width, height);
1781 }
1782
1783 static void pan_window_layout_compute_folders_linear(PanWindow *pw, const gchar *path, gint *width, gint *height)
1784 {
1785         gint x, y;
1786         gint level;
1787         gint w, h;
1788
1789         level = 0;
1790         x = PAN_FOLDER_BOX_BORDER;
1791         y = PAN_FOLDER_BOX_BORDER;
1792         w = PAN_FOLDER_BOX_BORDER * 2;
1793         h = PAN_FOLDER_BOX_BORDER * 2;
1794
1795         pan_window_layout_compute_folders_linear_path(pw, path, &x, &y, &level, NULL, &w, &h);
1796
1797         if (width) *width = w;
1798         if (height) *height = h;
1799 }
1800
1801 static void pan_window_layout_compute_timeline(PanWindow *pw, const gchar *path, gint *width, gint *height)
1802 {
1803         GList *list;
1804         GList *work;
1805         gint x, y;
1806         time_t tc;
1807         gint total;
1808         gint count;
1809         PanItem *pi_month = NULL;
1810         PanItem *pi_day = NULL;
1811         gint month_start;
1812         gint day_start;
1813         gint x_width;
1814         gint y_height;
1815
1816         pw->cache_list = filelist_sort(pw->cache_list, SORT_TIME, TRUE);
1817
1818         list = pan_window_layout_list(path, SORT_NONE, TRUE);
1819         list = filelist_sort(list, SORT_TIME, TRUE);
1820
1821         *width = PAN_FOLDER_BOX_BORDER * 2;
1822         *height = PAN_FOLDER_BOX_BORDER * 2;
1823
1824         x = 0;
1825         y = 0;
1826         month_start = y;
1827         day_start = month_start;
1828         x_width = 0;
1829         y_height = 0;
1830         tc = 0;
1831         total = 0;
1832         count = 0;
1833         work = list;
1834         while (work)
1835                 {
1836                 FileData *fd;
1837                 PanItem *pi;
1838
1839                 fd = work->data;
1840                 work = work->next;
1841
1842                 if (!date_compare(fd->date, tc, DATE_LENGTH_DAY))
1843                         {
1844                         GList *needle;
1845                         gchar *buf;
1846
1847                         if (!date_compare(fd->date, tc, DATE_LENGTH_MONTH))
1848                                 {
1849                                 pi_day = NULL;
1850
1851                                 if (pi_month)
1852                                         {
1853                                         x = pi_month->x + pi_month->width + PAN_FOLDER_BOX_BORDER;
1854                                         }
1855                                 else
1856                                         {
1857                                         x = PAN_FOLDER_BOX_BORDER;
1858                                         }
1859
1860                                 y = PAN_FOLDER_BOX_BORDER;
1861
1862                                 buf = date_value_string(fd->date, DATE_LENGTH_MONTH);
1863                                 pi = pan_item_new_text(pw, x, y, buf,
1864                                                        TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
1865                                                        PAN_TEXT_COLOR, 255);
1866                                 y += pi->height;
1867
1868                                 pi_month = pan_item_new_box(pw, file_data_new_simple(fd->path),
1869                                                             x, y, 0, 0,
1870                                                             PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1871                                                             PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1872                                                             PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1873
1874                                 x += PAN_FOLDER_BOX_BORDER;
1875                                 y += PAN_FOLDER_BOX_BORDER;
1876                                 month_start = y;
1877                                 }
1878
1879                         if (pi_day) x = pi_day->x + pi_day->width + PAN_FOLDER_BOX_BORDER;
1880
1881                         tc = fd->date;
1882                         total = 1;
1883                         count = 0;
1884
1885                         needle = work;
1886                         while (needle)
1887                                 {
1888                                 FileData *nfd;
1889
1890                                 nfd = needle->data;
1891                                 if (date_compare(nfd->date, tc, DATE_LENGTH_DAY))
1892                                         {
1893                                         needle = needle->next;
1894                                         total++;
1895                                         }
1896                                 else
1897                                         {
1898                                         needle = NULL;
1899                                         }
1900                                 }
1901
1902                         buf = date_value_string(fd->date, DATE_LENGTH_WEEK);
1903                         pi = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_NONE,
1904                                                PAN_TEXT_COLOR, 255);
1905                         g_free(buf);
1906
1907                         y += pi->height;
1908
1909                         pi_day = pan_item_new_box(pw, file_data_new_simple(fd->path), x, y, 0, 0,
1910                                                   PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1911                                                   PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1912                                                   PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1913
1914                         x += PAN_FOLDER_BOX_BORDER;
1915                         y += PAN_FOLDER_BOX_BORDER;
1916                         day_start = y;
1917                         }
1918
1919                 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1920                         {
1921                         pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1922                         if (pi->width > x_width) x_width = pi->width;
1923                         y_height = pi->height;
1924                         }
1925                 else
1926                         {
1927                         pi = pan_item_new_thumb(pw, fd, x, y);
1928                         x_width = PAN_THUMB_SIZE;
1929                         y_height = PAN_THUMB_SIZE;
1930                         }
1931
1932                 pan_item_size_by_item(pi_day, pi, PAN_FOLDER_BOX_BORDER);
1933                 pan_item_size_by_item(pi_month, pi_day, PAN_FOLDER_BOX_BORDER);
1934
1935                 total--;
1936                 count++;
1937
1938                 if (total > 0 && count < PAN_GROUP_MAX)
1939                         {
1940                         y += y_height + PAN_THUMB_GAP;
1941                         }
1942                 else
1943                         {
1944                         x += x_width + PAN_THUMB_GAP;
1945                         x_width = 0;
1946                         count = 0;
1947
1948                         if (total > 0)
1949                                 y = day_start;
1950                         else
1951                                 y = month_start;
1952                         }
1953
1954                 pan_item_size_coordinates(pi_month, PAN_FOLDER_BOX_BORDER, width, height);
1955                 }
1956
1957         g_list_free(list);
1958 }
1959
1960 static void pan_window_layout_compute(PanWindow *pw, const gchar *path,
1961                                       gint *width, gint *height,
1962                                       gint *scroll_x, gint *scroll_y)
1963 {
1964         pan_window_items_free(pw);
1965
1966         switch (pw->size)
1967                 {
1968                 case LAYOUT_SIZE_THUMB_DOTS:
1969                         pw->thumb_size = PAN_THUMB_SIZE_DOTS;
1970                         pw->thumb_gap = PAN_THUMB_GAP_DOTS;
1971                         break;
1972                 case LAYOUT_SIZE_THUMB_NONE:
1973                         pw->thumb_size = PAN_THUMB_SIZE_NONE;
1974                         pw->thumb_gap = PAN_THUMB_GAP_SMALL;
1975                         break;
1976                 case LAYOUT_SIZE_THUMB_SMALL:
1977                         pw->thumb_size = PAN_THUMB_SIZE_SMALL;
1978                         pw->thumb_gap = PAN_THUMB_GAP_SMALL;
1979                         break;
1980                 case LAYOUT_SIZE_THUMB_NORMAL:
1981                 default:
1982                         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
1983                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
1984                         break;
1985                 case LAYOUT_SIZE_THUMB_LARGE:
1986                         pw->thumb_size = PAN_THUMB_SIZE_LARGE;
1987                         pw->thumb_gap = PAN_THUMB_GAP_LARGE;
1988                         break;
1989                 case LAYOUT_SIZE_10:
1990                         pw->image_size = 10;
1991                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
1992                         break;
1993                 case LAYOUT_SIZE_25:
1994                         pw->image_size = 25;
1995                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
1996                         break;
1997                 case LAYOUT_SIZE_33:
1998                         pw->image_size = 33;
1999                         pw->thumb_gap = PAN_THUMB_GAP_LARGE;
2000                         break;
2001                 case LAYOUT_SIZE_50:
2002                         pw->image_size = 50;
2003                         pw->thumb_gap = PAN_THUMB_GAP_HUGE;
2004                         break;
2005                 case LAYOUT_SIZE_100:
2006                         pw->image_size = 100;
2007                         pw->thumb_gap = PAN_THUMB_GAP_HUGE;
2008                         break;
2009                 }
2010
2011         *width = 0;
2012         *height = 0;
2013         *scroll_x = 0;
2014         *scroll_y = 0;
2015
2016         switch (pw->layout)
2017                 {
2018                 case LAYOUT_GRID:
2019                 default:
2020                         pan_window_layout_compute_grid(pw, path, width, height);
2021                         break;
2022                 case LAYOUT_FOLDERS_LINEAR:
2023                         pan_window_layout_compute_folders_linear(pw, path, width, height);
2024                         break;
2025                 case LAYOUT_FOLDERS_FLOWER:
2026                         pan_window_layout_compute_folders_flower(pw, path, width, height, scroll_x, scroll_y);
2027                         break;
2028                 case LAYOUT_TIMELINE:
2029                         pan_window_layout_compute_timeline(pw, path, width, height);
2030                         break;
2031                 }
2032
2033         pan_cache_free(pw);
2034
2035         printf("computed %d objects\n", g_list_length(pw->list));
2036 }
2037
2038 static GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height)
2039 {
2040         GList *list = NULL;
2041         GList *work;
2042
2043         work = pw->list;
2044         while (work)
2045                 {
2046                 PanItem *pi;
2047
2048                 pi = work->data;
2049                 work = work->next;
2050
2051                 if (util_clip_region_test(x, y, width, height,
2052                                           pi->x, pi->y, pi->width, pi->height))
2053                         {
2054                         list = g_list_prepend(list, pi);
2055                         }
2056                 }
2057
2058         return list;
2059 }
2060
2061
2062
2063 /*
2064  *-----------------------------------------------------------------------------
2065  * tile generation
2066  *-----------------------------------------------------------------------------
2067  */
2068
2069 static gint pan_layout_queue_step(PanWindow *pw);
2070
2071
2072 static void pan_layout_queue_thumb_done_cb(ThumbLoader *tl, gpointer data)
2073 {
2074         PanWindow *pw = data;
2075
2076         if (pw->queue_pi)
2077                 {
2078                 PanItem *pi;
2079                 gint rc;
2080
2081                 pi = pw->queue_pi;
2082                 pw->queue_pi = NULL;
2083
2084                 pi->queued = FALSE;
2085
2086                 if (pi->pixbuf) g_object_unref(pi->pixbuf);
2087                 pi->pixbuf = thumb_loader_get_pixbuf(tl, TRUE);
2088
2089                 rc = pi->refcount;
2090                 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
2091                 pi->refcount = rc;
2092                 }
2093
2094         thumb_loader_free(pw->tl);
2095         pw->tl = NULL;
2096
2097         while (pan_layout_queue_step(pw));
2098 }
2099
2100 static void pan_layout_queue_image_done_cb(ImageLoader *il, gpointer data)
2101 {
2102         PanWindow *pw = data;
2103
2104         if (pw->queue_pi)
2105                 {
2106                 PanItem *pi;
2107                 gint rc;
2108
2109                 pi = pw->queue_pi;
2110                 pw->queue_pi = NULL;
2111
2112                 pi->queued = FALSE;
2113
2114                 if (pi->pixbuf) g_object_unref(pi->pixbuf);
2115                 pi->pixbuf = image_loader_get_pixbuf(pw->il);
2116                 if (pi->pixbuf) g_object_ref(pi->pixbuf);
2117
2118                 if (pi->pixbuf && pw->size != LAYOUT_SIZE_100 &&
2119                     (gdk_pixbuf_get_width(pi->pixbuf) > pi->width ||
2120                      gdk_pixbuf_get_height(pi->pixbuf) > pi->height))
2121                         {
2122                         GdkPixbuf *tmp;
2123
2124                         tmp = pi->pixbuf;
2125                         pi->pixbuf = gdk_pixbuf_scale_simple(tmp, pi->width, pi->height,
2126                                                              (GdkInterpType)zoom_quality);
2127                         g_object_unref(tmp);
2128                         }
2129
2130                 rc = pi->refcount;
2131                 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
2132                 pi->refcount = rc;
2133                 }
2134
2135         image_loader_free(pw->il);
2136         pw->il = NULL;
2137
2138         while (pan_layout_queue_step(pw));
2139 }
2140
2141 #if 0
2142 static void pan_layout_queue_image_area_cb(ImageLoader *il, guint x, guint y,
2143                                            guint width, guint height, gpointer data)
2144 {
2145         PanWindow *pw = data;
2146
2147         if (pw->queue_pi)
2148                 {
2149                 PanItem *pi;
2150                 gint rc;
2151
2152                 pi = pw->queue_pi;
2153
2154                 if (!pi->pixbuf)
2155                         {
2156                         pi->pixbuf = image_loader_get_pixbuf(pw->il);
2157                         if (pi->pixbuf) g_object_ref(pi->pixbuf);
2158                         }
2159
2160                 rc = pi->refcount;
2161                 image_area_changed(pw->imd, pi->x + x, pi->y + y, width, height);
2162                 pi->refcount = rc;
2163                 }
2164 }
2165 #endif
2166
2167 static gint pan_layout_queue_step(PanWindow *pw)
2168 {
2169         PanItem *pi;
2170
2171         if (!pw->queue) return FALSE;
2172
2173         pi = pw->queue->data;
2174         pw->queue = g_list_remove(pw->queue, pi);
2175         pw->queue_pi = pi;
2176
2177         image_loader_free(pw->il);
2178         pw->il = NULL;
2179         thumb_loader_free(pw->tl);
2180         pw->tl = NULL;
2181
2182         if (pi->type == ITEM_IMAGE)
2183                 {
2184                 pw->il = image_loader_new(pi->fd->path);
2185
2186                 if (pw->size != LAYOUT_SIZE_100)
2187                         {
2188                         image_loader_set_requested_size(pw->il, pi->width, pi->height);
2189                         }
2190
2191 #if 0
2192                 image_loader_set_area_ready_func(pw->il, pan_layout_queue_image_area_cb, pw);
2193 #endif
2194                 image_loader_set_error_func(pw->il, pan_layout_queue_image_done_cb, pw);
2195
2196                 if (image_loader_start(pw->il, pan_layout_queue_image_done_cb, pw)) return FALSE;
2197
2198                 image_loader_free(pw->il);
2199                 pw->il = NULL;
2200                 }
2201         else if (pi->type == ITEM_THUMB)
2202                 {
2203                 pw->tl = thumb_loader_new(PAN_THUMB_SIZE, PAN_THUMB_SIZE);
2204
2205                 if (!pw->tl->standard_loader)
2206                         {
2207                         /* The classic loader will recreate a thumbnail any time we
2208                          * request a different size than what exists. This view will
2209                          * almost never use the user configured sizes so disable cache.
2210                          */
2211                         thumb_loader_set_cache(pw->tl, FALSE, FALSE, FALSE);
2212                         }
2213
2214                 thumb_loader_set_callbacks(pw->tl,
2215                                            pan_layout_queue_thumb_done_cb,
2216                                            pan_layout_queue_thumb_done_cb,
2217                                            NULL, pw);
2218
2219                 if (thumb_loader_start(pw->tl, pi->fd->path)) return FALSE;
2220
2221                 thumb_loader_free(pw->tl);
2222                 pw->tl = NULL;
2223                 }
2224
2225         pw->queue_pi->queued = FALSE;
2226         pw->queue_pi = NULL;
2227         return TRUE;
2228 }
2229
2230 static void pan_layout_queue(PanWindow *pw, PanItem *pi)
2231 {
2232         if (!pi || pi->queued || pi->pixbuf) return;
2233         if (pw->size <= LAYOUT_SIZE_THUMB_NONE) return;
2234
2235         pi->queued = TRUE;
2236         pw->queue = g_list_prepend(pw->queue, pi);
2237
2238         if (!pw->tl && !pw->il) while(pan_layout_queue_step(pw));
2239 }
2240
2241 static gint pan_window_request_tile_cb(ImageWindow *imd, gint x, gint y, gint width, gint height,
2242                                        GdkPixbuf *pixbuf, gpointer data)
2243 {
2244         PanWindow *pw = data;
2245         GList *list;
2246         GList *work;
2247         gint i;
2248
2249         pixbuf_draw_rect_fill(pixbuf,
2250                          0, 0, width, height,
2251                          PAN_BACKGROUND_COLOR, 255);
2252
2253         for (i = (x / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < x + width; i += PAN_GRID_SIZE)
2254                 {
2255                 gint rx, ry, rw, rh;
2256
2257                 if (util_clip_region(x, y, width, height,
2258                                      i, y, 1, height,
2259                                      &rx, &ry, &rw, &rh))
2260                         {
2261                         pixbuf_draw_rect_fill(pixbuf,
2262                                               rx - x, ry - y, rw, rh,
2263                                               PAN_GRID_COLOR, PAN_GRID_ALPHA);
2264                         }
2265                 }
2266         for (i = (y / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < y + height; i += PAN_GRID_SIZE)
2267                 {
2268                 gint rx, ry, rw, rh;
2269
2270                 if (util_clip_region(x, y, width, height,
2271                                      x, i, width, 1,
2272                                      &rx, &ry, &rw, &rh))
2273                         {
2274                         pixbuf_draw_rect_fill(pixbuf,
2275                                               rx - x, ry - y, rw, rh,
2276                                               PAN_GRID_COLOR, PAN_GRID_ALPHA);
2277                         }
2278                 }
2279
2280         list = pan_layout_intersect(pw, x, y, width, height);
2281         work = list;
2282         while (work)
2283                 {
2284                 PanItem *pi;
2285                 gint tx, ty, tw, th;
2286                 gint rx, ry, rw, rh;
2287
2288                 pi = work->data;
2289                 work = work->next;
2290
2291                 pi->refcount++;
2292
2293                 if (pi->type == ITEM_THUMB && pi->pixbuf)
2294                         {
2295                         tw = gdk_pixbuf_get_width(pi->pixbuf);
2296                         th = gdk_pixbuf_get_height(pi->pixbuf);
2297
2298                         tx = pi->x + (pi->width - tw) / 2;
2299                         ty = pi->y + (pi->height - th) / 2;
2300
2301                         if (gdk_pixbuf_get_has_alpha(pi->pixbuf))
2302                                 {
2303                                 if (util_clip_region(x, y, width, height,
2304                                                      tx + PAN_SHADOW_OFFSET, ty + PAN_SHADOW_OFFSET, tw, th,
2305                                                      &rx, &ry, &rw, &rh))
2306                                         {
2307                                         pixbuf_draw_shadow(pixbuf,
2308                                                            rx - x, ry - y, rw, rh,
2309                                                            tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2310                                                            PAN_SHADOW_FADE,
2311                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2312                                         }
2313                                 }
2314                         else
2315                                 {
2316                                 if (util_clip_region(x, y, width, height,
2317                                                      tx + tw, ty + PAN_SHADOW_OFFSET,
2318                                                      PAN_SHADOW_OFFSET, th - PAN_SHADOW_OFFSET,
2319                                                      &rx, &ry, &rw, &rh))
2320                                         {
2321                                         pixbuf_draw_shadow(pixbuf,
2322                                                            rx - x, ry - y, rw, rh,
2323                                                            tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2324                                                            PAN_SHADOW_FADE,
2325                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2326                                         }
2327                                 if (util_clip_region(x, y, width, height,
2328                                                      tx + PAN_SHADOW_OFFSET, ty + th, tw, PAN_SHADOW_OFFSET,
2329                                                      &rx, &ry, &rw, &rh))
2330                                         {
2331                                         pixbuf_draw_shadow(pixbuf,
2332                                                            rx - x, ry - y, rw, rh,
2333                                                            tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2334                                                            PAN_SHADOW_FADE,
2335                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2336                                         }
2337                                 }
2338
2339                         if (util_clip_region(x, y, width, height,
2340                                              tx, ty, tw, th,
2341                                              &rx, &ry, &rw, &rh))
2342                                 {
2343                                 gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh,
2344                                                      (double) tx - x,
2345                                                      (double) ty - y,
2346                                                      1.0, 1.0, GDK_INTERP_NEAREST,
2347                                                      255);
2348                                 }
2349
2350                         if (util_clip_region(x, y, width, height,
2351                                              tx, ty, tw, PAN_OUTLINE_THICKNESS,
2352                                              &rx, &ry, &rw, &rh))
2353                                 {
2354                                 pixbuf_draw_rect_fill(pixbuf,
2355                                                       rx - x, ry - y, rw, rh,
2356                                                       PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA);
2357                                 }
2358                         if (util_clip_region(x, y, width, height,
2359                                              tx, ty, PAN_OUTLINE_THICKNESS, th,
2360                                              &rx, &ry, &rw, &rh))
2361                                 {
2362                                 pixbuf_draw_rect_fill(pixbuf,
2363                                                       rx - x, ry - y, rw, rh,
2364                                                       PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA);
2365                                 }
2366                         if (util_clip_region(x, y, width, height,
2367                                              tx + tw - PAN_OUTLINE_THICKNESS, ty +  PAN_OUTLINE_THICKNESS,
2368                                              PAN_OUTLINE_THICKNESS, th - PAN_OUTLINE_THICKNESS,
2369                                              &rx, &ry, &rw, &rh))
2370                                 {
2371                                 pixbuf_draw_rect_fill(pixbuf,
2372                                                       rx - x, ry - y, rw, rh,
2373                                                       PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA);
2374                                 }
2375                         if (util_clip_region(x, y, width, height,
2376                                              tx +  PAN_OUTLINE_THICKNESS, ty + th - PAN_OUTLINE_THICKNESS,
2377                                              tw - PAN_OUTLINE_THICKNESS * 2, PAN_OUTLINE_THICKNESS,
2378                                              &rx, &ry, &rw, &rh))
2379                                 {
2380                                 pixbuf_draw_rect_fill(pixbuf,
2381                                                       rx - x, ry - y, rw, rh,
2382                                                       PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA);
2383                                 }
2384                         }
2385                 else if (pi->type == ITEM_THUMB)
2386                         {
2387                         tw = pi->width - PAN_SHADOW_OFFSET * 2;
2388                         th = pi->height - PAN_SHADOW_OFFSET * 2;
2389                         tx = pi->x + PAN_SHADOW_OFFSET;
2390                         ty = pi->y + PAN_SHADOW_OFFSET;
2391
2392                         if (util_clip_region(x, y, width, height,
2393                                              tx, ty, tw, th,
2394                                              &rx, &ry, &rw, &rh))
2395                                 {
2396                                 gint d;
2397
2398                                 d = (pw->size <= LAYOUT_SIZE_THUMB_NONE) ? 2 : 8;
2399                                 pixbuf_draw_rect_fill(pixbuf,
2400                                                       rx - x, ry - y, rw, rh,
2401                                                       PAN_SHADOW_COLOR,
2402                                                       PAN_SHADOW_ALPHA / d);
2403                                 }
2404
2405                         pan_layout_queue(pw, pi);
2406                         }
2407                 else if (pi->type == ITEM_IMAGE)
2408                         {
2409                         if (util_clip_region(x, y, width, height,
2410                                              pi->x, pi->y, pi->width, pi->height,
2411                                              &rx, &ry, &rw, &rh))
2412                                 {
2413                                 if (pi->pixbuf)
2414                                         {
2415                                         gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh,
2416                                                              (double) pi->x - x,
2417                                                              (double) pi->y - y,
2418                                                              1.0, 1.0, GDK_INTERP_NEAREST,
2419                                                              255);
2420                                         }
2421                                 else
2422                                         {
2423                                         pixbuf_draw_rect_fill(pixbuf,
2424                                                               rx - x, ry - y, rw, rh,
2425                                                               PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA / 2);
2426                                         pan_layout_queue(pw, pi);
2427                                         }
2428                                 }
2429                         }
2430                 else if (pi->type == ITEM_BOX)
2431                         {
2432                         gint bw, bh;
2433                         gint *shadow;
2434
2435                         bw = pi->width;
2436                         bh = pi->height;
2437
2438                         shadow = pi->data;
2439                         if (shadow)
2440                                 {
2441                                 bw -= shadow[0];
2442                                 bh -= shadow[0];
2443
2444                                 if (pi->color_a > 254)
2445                                         {
2446                                         pixbuf_draw_shadow(pixbuf, pi->x - x + bw, pi->y - y + shadow[0],
2447                                                            shadow[0], bh - shadow[0],
2448                                                            pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2449                                                            shadow[1],
2450                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2451                                         pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + bh,
2452                                                            bw, shadow[0],
2453                                                            pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2454                                                            shadow[1],
2455                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2456                                         }
2457                                 else
2458                                         {
2459                                         gint a;
2460                                         a = pi->color_a * PAN_SHADOW_ALPHA >> 8;
2461                                         pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + shadow[0],
2462                                                            bw, bh,
2463                                                            pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2464                                                            shadow[1],
2465                                                            PAN_SHADOW_COLOR, a);
2466                                         }
2467                                 }
2468
2469                         if (util_clip_region(x, y, width, height,
2470                                              pi->x, pi->y, bw, bh,
2471                                              &rx, &ry, &rw, &rh))
2472                                 {
2473                                 pixbuf_draw_rect_fill(pixbuf,
2474                                                       rx - x, ry - y, rw, rh,
2475                                                       pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2476                                 }
2477                         if (util_clip_region(x, y, width, height,
2478                                              pi->x, pi->y, bw, pi->border,
2479                                              &rx, &ry, &rw, &rh))
2480                                 {
2481                                 pixbuf_draw_rect_fill(pixbuf,
2482                                                       rx - x, ry - y, rw, rh,
2483                                                       pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2484                                 }
2485                         if (util_clip_region(x, y, width, height,
2486                                              pi->x, pi->y + pi->border, pi->border, bh - pi->border * 2,
2487                                              &rx, &ry, &rw, &rh))
2488                                 {
2489                                 pixbuf_draw_rect_fill(pixbuf,
2490                                                       rx - x, ry - y, rw, rh,
2491                                                       pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2492                                 }
2493                         if (util_clip_region(x, y, width, height,
2494                                              pi->x + bw - pi->border, pi->y + pi->border,
2495                                              pi->border, bh - pi->border * 2,
2496                                              &rx, &ry, &rw, &rh))
2497                                 {
2498                                 pixbuf_draw_rect_fill(pixbuf,
2499                                                       rx - x, ry - y, rw, rh,
2500                                                       pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2501                                 }
2502                         if (util_clip_region(x, y, width, height,
2503                                              pi->x, pi->y + bh - pi->border,
2504                                              bw,  pi->border,
2505                                              &rx, &ry, &rw, &rh))
2506                                 {
2507                                 pixbuf_draw_rect_fill(pixbuf,
2508                                                       rx - x, ry - y, rw, rh,
2509                                                       pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2510                                 }
2511                         }
2512                 else if (pi->type == ITEM_TRIANGLE)
2513                         {
2514                         if (util_clip_region(x, y, width, height,
2515                                              pi->x, pi->y, pi->width, pi->height,
2516                                              &rx, &ry, &rw, &rh) && pi->data)
2517                                 {
2518                                 gint *coord = pi->data;
2519                                 pixbuf_draw_triangle(pixbuf,
2520                                                      rx - x, ry - y, rw, rh,
2521                                                      coord[0] - x, coord[1] - y,
2522                                                      coord[2] - x, coord[3] - y,
2523                                                      coord[4] - x, coord[5] - y,
2524                                                      pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2525
2526                                 if (pi->border & BORDER_1)
2527                                         {
2528                                         pixbuf_draw_line(pixbuf,
2529                                                          rx - x, ry - y, rw, rh,
2530                                                          coord[0] - x, coord[1] - y,
2531                                                          coord[2] - x, coord[3] - y,
2532                                                          pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2533                                         }
2534                                 if (pi->border & BORDER_2)
2535                                         {
2536                                         pixbuf_draw_line(pixbuf,
2537                                                          rx - x, ry - y, rw, rh,
2538                                                          coord[2] - x, coord[3] - y,
2539                                                          coord[4] - x, coord[5] - y,
2540                                                          pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2541                                         }
2542                                 if (pi->border & BORDER_3)
2543                                         {
2544                                         pixbuf_draw_line(pixbuf,
2545                                                          rx - x, ry - y, rw, rh,
2546                                                          coord[4] - x, coord[5] - y,
2547                                                          coord[0] - x, coord[1] - y,
2548                                                          pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2549                                         }
2550                                 }
2551                         }
2552                 else if (pi->type == ITEM_TEXT && pi->text)
2553                         {
2554                         PangoLayout *layout;
2555
2556                         layout = pan_item_text_layout(pi, imd->image);
2557                         pixbuf_draw_layout(pixbuf, layout, imd->image,
2558                                            pi->x - x + PAN_TEXT_BORDER_SIZE, pi->y - y + PAN_TEXT_BORDER_SIZE,
2559                                            pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2560                         g_object_unref(G_OBJECT(layout));
2561                         }
2562                 }
2563         g_list_free(list);
2564
2565         if (0)
2566                 {
2567                 static gint count = 0;
2568                 PangoLayout *layout;
2569                 gint lx, ly;
2570                 gint lw, lh;
2571                 GdkPixmap *pixmap;
2572                 gchar *buf;
2573
2574                 layout = gtk_widget_create_pango_layout(imd->image, NULL);
2575
2576                 buf = g_strdup_printf("%d,%d\n(#%d)", x, y,
2577                                       (x / imd->source_tile_width) +
2578                                       (y / imd->source_tile_height * (imd->image_width/imd->source_tile_width + 1)));
2579                 pango_layout_set_text(layout, buf, -1);
2580                 g_free(buf);
2581
2582                 pango_layout_get_pixel_size(layout, &lw, &lh);
2583
2584                 pixmap = gdk_pixmap_new(imd->widget->window, lw, lh, -1);
2585                 gdk_draw_rectangle(pixmap, imd->widget->style->black_gc, TRUE, 0, 0, lw, lh);
2586                 gdk_draw_layout(pixmap, imd->widget->style->white_gc, 0, 0, layout);
2587                 g_object_unref(G_OBJECT(layout));
2588
2589                 lx = MAX(0, width / 2 - lw / 2);
2590                 ly = MAX(0, height / 2 - lh / 2);
2591                 lw = MIN(lw, width - lx);
2592                 lh = MIN(lh, height - ly);
2593                 gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_drawable_get_colormap(imd->image->window),
2594                                              0, 0, lx, ly, lw, lh);
2595                 g_object_unref(pixmap);
2596
2597                 count++;
2598                 }
2599
2600         return TRUE;
2601 }
2602
2603 static void pan_window_dispose_tile_cb(ImageWindow *imd, gint x, gint y, gint width, gint height,
2604                                        GdkPixbuf *pixbuf, gpointer data)
2605 {
2606         PanWindow *pw = data;
2607         GList *list;
2608         GList *work;
2609
2610         list = pan_layout_intersect(pw, x, y, width, height);
2611         work = list;
2612         while (work)
2613                 {
2614                 PanItem *pi;
2615
2616                 pi = work->data;
2617                 work = work->next;
2618
2619                 if (pi->refcount > 0)
2620                         {
2621                         pi->refcount--;
2622
2623                         if ((pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE) &&
2624                             pi->refcount == 0)
2625                                 {
2626                                 if (pi->queued)
2627                                         {
2628                                         pw->queue = g_list_remove(pw->queue, pi);
2629                                         pi->queued = FALSE;
2630                                         }
2631                                 if (pw->queue_pi == pi) pw->queue_pi = NULL;
2632                                 if (pi->pixbuf)
2633                                         {
2634                                         g_object_unref(pi->pixbuf);
2635                                         pi->pixbuf = NULL;
2636                                         }
2637                                 }
2638                         }
2639                 }
2640
2641         g_list_free(list);
2642 }
2643
2644
2645 /*
2646  *-----------------------------------------------------------------------------
2647  * misc
2648  *-----------------------------------------------------------------------------
2649  */ 
2650
2651 static void pan_window_message(PanWindow *pw, const gchar *text)
2652 {
2653         GList *work;
2654         gint count = 0;
2655         gint64 size = 0;
2656         gchar *ss;
2657         gchar *buf;
2658
2659         if (text)
2660                 {
2661                 gtk_label_set_text(GTK_LABEL(pw->label_message), text);
2662                 return;
2663                 }
2664
2665         work = pw->list;
2666         while (work)
2667                 {
2668                 PanItem *pi;
2669
2670                 pi = work->data;
2671                 work = work->next;
2672
2673                 if (pi->fd &&
2674                     (pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE))
2675                         {
2676                         size += pi->fd->size;
2677                         count++;
2678                         }
2679                 }
2680
2681         ss = text_from_size_abrev(size);
2682         buf = g_strdup_printf(_("%d images, %s"), count, ss);
2683         g_free(ss);
2684         gtk_label_set_text(GTK_LABEL(pw->label_message), buf);
2685         g_free(buf);
2686 }
2687
2688 static ImageWindow *pan_window_active_image(PanWindow *pw)
2689 {
2690         if (pw->fs) return pw->fs->imd;
2691
2692         return pw->imd;
2693 }
2694
2695 static void pan_window_zoom_limit(PanWindow *pw)
2696 {
2697         gdouble min;
2698
2699         switch (pw->size)
2700                 {
2701                 case LAYOUT_SIZE_THUMB_DOTS:
2702                 case LAYOUT_SIZE_THUMB_NONE:
2703                 case LAYOUT_SIZE_THUMB_SMALL:
2704                 case LAYOUT_SIZE_THUMB_NORMAL:
2705 #if 0
2706                         /* easily requires > 512mb ram when window size > 1024x768 and zoom is <= -8 */
2707                         min = -16.0;
2708                         break;
2709 #endif
2710                 case LAYOUT_SIZE_THUMB_LARGE:
2711                         min = -6.0;
2712                         break;
2713                 case LAYOUT_SIZE_10:
2714                 case LAYOUT_SIZE_25:
2715                         min = -4.0;
2716                         break;
2717                 case LAYOUT_SIZE_33:
2718                 case LAYOUT_SIZE_50:
2719                 case LAYOUT_SIZE_100:
2720                 default:
2721                         min = -2.0;
2722                         break;
2723                 }
2724
2725         image_zoom_set_limits(pw->imd, min, 32.0);
2726 }
2727
2728 static gint pan_window_layout_update_idle_cb(gpointer data)
2729 {
2730         PanWindow *pw = data;
2731         gint width;
2732         gint height;
2733         gint scroll_x;
2734         gint scroll_y;
2735
2736         if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
2737                 {
2738                 if (!pw->cache_list && !pw->cache_todo)
2739                         {
2740                         pan_cache_fill(pw, pw->path);
2741                         if (pw->cache_todo)
2742                                 {
2743                                 pan_window_message(pw, _("Reading dimensions..."));
2744                                 return TRUE;
2745                                 }
2746                         }
2747                 if (pan_cache_step(pw))
2748                         {
2749                         pw->cache_count++;
2750                         pw->cache_tick++;
2751                         if (pw->cache_count == pw->cache_total)
2752                                 {
2753                                 pan_window_message(pw, _("Sorting images..."));
2754                                 }
2755                         else if (pw->cache_tick > 9)
2756                                 {
2757                                 gchar *buf;
2758
2759                                 buf = g_strdup_printf("%s %d", _("Reading dimensions..."),
2760                                                       pw->cache_total - pw->cache_count);
2761                                 pan_window_message(pw, buf);
2762                                 g_free(buf);
2763
2764                                 pw->cache_tick = 0;
2765                                 }
2766
2767                         return TRUE;
2768                         }
2769                 }
2770
2771         pan_window_layout_compute(pw, pw->path, &width, &height, &scroll_x, &scroll_y);
2772
2773         pan_window_zoom_limit(pw);
2774
2775         if (width > 0 && height > 0)
2776                 {
2777                 gdouble align;
2778
2779                 image_set_image_as_tiles(pw->imd, width, height,
2780                                          PAN_TILE_SIZE, PAN_TILE_SIZE, 8,
2781                                          pan_window_request_tile_cb,
2782                                          pan_window_dispose_tile_cb, pw, 1.0);
2783
2784                 if (scroll_x == 0 && scroll_y == 0)
2785                         {
2786                         align = 0.0;
2787                         }
2788                 else
2789                         {
2790                         align = 0.5;
2791                         }
2792                 image_scroll_to_point(pw->imd, scroll_x, scroll_y, align, align);
2793                 }
2794
2795         pan_window_message(pw, NULL);
2796
2797         pw->idle_id = -1;
2798
2799         return FALSE;
2800 }
2801
2802 static void pan_window_layout_update_idle(PanWindow *pw)
2803 {
2804         if (pw->idle_id == -1)
2805                 {
2806                 pan_window_message(pw, _("Sorting images..."));
2807                 pw->idle_id = g_idle_add(pan_window_layout_update_idle_cb, pw);
2808                 }
2809 }
2810
2811 /*
2812  *-----------------------------------------------------------------------------
2813  * pan window keyboard
2814  *-----------------------------------------------------------------------------
2815  */
2816
2817 static const gchar *pan_menu_click_path(PanWindow *pw)
2818 {
2819         if (pw->click_pi && pw->click_pi->fd) return pw->click_pi->fd->path;
2820         return NULL;
2821 }
2822
2823 static void pan_window_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
2824 {
2825         PanWindow *pw = data;
2826         ImageWindow *imd;
2827
2828         imd = pan_window_active_image(pw);
2829         gdk_window_get_origin(imd->image->window, x, y);
2830         popup_menu_position_clamp(menu, x, y, 0);
2831 }
2832
2833 static gint pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
2834 {
2835         PanWindow *pw = data;
2836         ImageWindow *imd;
2837         const gchar *path;
2838         gint stop_signal = FALSE;
2839         GtkWidget *menu;
2840         gint x = 0;
2841         gint y = 0;
2842         gint focused;
2843
2844         focused = (pw->fs || GTK_WIDGET_HAS_FOCUS(pw->imd->widget));
2845
2846         imd = pan_window_active_image(pw);
2847         path = pan_menu_click_path(pw);
2848
2849         if (focused)
2850                 {
2851                 switch (event->keyval)
2852                         {
2853                         case GDK_Left: case GDK_KP_Left:
2854                                 x -= 1;
2855                                 stop_signal = TRUE;
2856                                 break;
2857                         case GDK_Right: case GDK_KP_Right:
2858                                 x += 1;
2859                                 stop_signal = TRUE;
2860                                 break;
2861                         case GDK_Up: case GDK_KP_Up:
2862                                 y -= 1;
2863                                 stop_signal = TRUE;
2864                                 break;
2865                         case GDK_Down: case GDK_KP_Down:
2866                                 y += 1;
2867                                 stop_signal = TRUE;
2868                                 break;
2869                         case GDK_Page_Up: case GDK_KP_Page_Up:
2870                                 image_scroll(imd, 0, 0-imd->vis_height / 2);
2871                                 break;
2872                         case GDK_Page_Down: case GDK_KP_Page_Down:
2873                                 image_scroll(imd, 0, imd->vis_height / 2);
2874                                 break;
2875                         case GDK_Home: case GDK_KP_Home:
2876                                 image_scroll(imd, 0-imd->vis_width / 2, 0);
2877                                 break;
2878                         case GDK_End: case GDK_KP_End:
2879                                 image_scroll(imd, imd->vis_width / 2, 0);
2880                                 break;
2881                         }
2882                 }
2883
2884         if (focused && !(event->state & GDK_CONTROL_MASK) )
2885             switch (event->keyval)
2886                 {
2887                 case '+': case '=': case GDK_KP_Add:
2888                         image_zoom_adjust(imd, ZOOM_INCREMENT);
2889                         break;
2890                 case '-': case GDK_KP_Subtract:
2891                         image_zoom_adjust(imd, -ZOOM_INCREMENT);
2892                         break;
2893                 case 'Z': case 'z': case GDK_KP_Divide: case '1':
2894                         image_zoom_set(imd, 1.0);
2895                         break;
2896                 case '2':
2897                         image_zoom_set(imd, 2.0);
2898                         break;
2899                 case '3':
2900                         image_zoom_set(imd, 3.0);
2901                         break;
2902                 case '4':
2903                         image_zoom_set(imd, 4.0);
2904                         break;
2905                 case '7':
2906                         image_zoom_set(imd, -4.0);
2907                         break;
2908                 case '8':
2909                         image_zoom_set(imd, -3.0);
2910                         break;
2911                 case '9':
2912                         image_zoom_set(imd, -2.0);
2913                         break;
2914                 case 'F': case 'f':
2915                 case 'V': case 'v':
2916                         pan_fullscreen_toggle(pw, FALSE);
2917                         stop_signal = TRUE;
2918                         break;
2919                 case 'I': case 'i':
2920                         pan_overlay_toggle(pw);
2921                         break;
2922                 case GDK_Delete: case GDK_KP_Delete:
2923                         break;
2924                 case '/':
2925                         if (!pw->fs)
2926                                 {
2927                                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), TRUE);
2928                                 stop_signal = TRUE;
2929                                 }
2930                         break;
2931                 case GDK_Escape:
2932                         if (pw->fs)
2933                                 {
2934                                 pan_fullscreen_toggle(pw, TRUE);
2935                                 stop_signal = TRUE;
2936                                 }
2937                         else if (GTK_WIDGET_VISIBLE(pw->search_entry))
2938                                 {
2939                                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
2940                                 stop_signal = TRUE;
2941                                 }
2942                         break;
2943                 case GDK_Menu:
2944                 case GDK_F10:
2945                         menu = pan_popup_menu(pw);
2946                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME);
2947                         stop_signal = TRUE;
2948                         break;
2949                 }
2950
2951         if (event->state & GDK_CONTROL_MASK)
2952                 {
2953                 gint n = -1;
2954                 switch (event->keyval)
2955                         {
2956                         case '1':
2957                                 n = 0;
2958                                 break;
2959                         case '2':
2960                                 n = 1;
2961                                 break;
2962                         case '3':
2963                                 n = 2;
2964                                 break;
2965                         case '4':
2966                                 n = 3;
2967                                 break;
2968                         case '5':
2969                                 n = 4;
2970                                 break;
2971                         case '6':
2972                                 n = 5;
2973                                 break;
2974                         case '7':
2975                                 n = 6;
2976                                 break;
2977                         case '8':
2978                                 n = 7;
2979                                 break;
2980                         case '9':
2981                                 n = 8;
2982                                 break;
2983                         case '0':
2984                                 n = 9;
2985                                 break;
2986                         case 'C': case 'c':
2987                                 if (path) file_util_copy(path, NULL, NULL, imd->widget);
2988                                 break;
2989                         case 'M': case 'm':
2990                                 if (path) file_util_move(path, NULL, NULL, imd->widget);
2991                                 break;
2992                         case 'R': case 'r':
2993                                 if (path) file_util_rename(path, NULL, imd->widget);
2994                                 break;
2995                         case 'D': case 'd':
2996                                 if (path) file_util_delete(path, NULL, imd->widget);
2997                                 break;
2998                         case 'P': case 'p':
2999                                 if (path) info_window_new(path, NULL);
3000                                 break;
3001                         case 'W': case 'w':
3002                                 pan_window_close(pw);
3003                                 break;
3004                         }
3005                 if (n != -1 && path)
3006                         {
3007                         pan_fullscreen_toggle(pw, TRUE);
3008                         start_editor_from_file(n, path);
3009                         stop_signal = TRUE;
3010                         }
3011                 }
3012         else if (event->state & GDK_SHIFT_MASK)
3013                 {
3014                 x *= 3;
3015                 y *= 3;
3016                 }
3017         else if (!focused)
3018                 {
3019                 switch (event->keyval)
3020                         {
3021                         case GDK_Escape:
3022                                 if (pw->fs)
3023                                         {
3024                                         pan_fullscreen_toggle(pw, TRUE);
3025                                         stop_signal = TRUE;
3026                                         }
3027                                 else if (GTK_WIDGET_HAS_FOCUS(pw->search_entry))
3028                                         {
3029                                         gtk_widget_grab_focus(pw->imd->widget);
3030                                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
3031                                         stop_signal = TRUE;
3032                                         }
3033                         break;
3034                         default:
3035                                 break;
3036                         }
3037                 }
3038
3039         if (x != 0 || y!= 0)
3040                 {
3041                 keyboard_scroll_calc(&x, &y, event);
3042                 image_scroll(imd, x, y);
3043                 }
3044
3045         return stop_signal;
3046 }
3047
3048 /*
3049  *-----------------------------------------------------------------------------
3050  * info popup
3051  *-----------------------------------------------------------------------------
3052  */
3053
3054 static void pan_info_update(PanWindow *pw, PanItem *pi)
3055 {
3056         PanItem *pbox;
3057         PanItem *plabel;
3058         PanItem *p;
3059         gchar *buf;
3060         gint x1, y1, x2, y2, x3, y3;
3061         gint x, y, w, h;
3062
3063         if (pw->click_pi == pi) return;
3064         if (pi && !pi->fd) pi = NULL;
3065
3066         while ((p = pan_item_find_by_key(pw, ITEM_NONE, "info"))) pan_item_remove(pw, p);
3067         pw->click_pi = pi;
3068
3069         if (!pi) return;
3070
3071         printf("info set to %s\n", pi->fd->path);
3072
3073         pbox = pan_item_new_box(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10,
3074                              PAN_POPUP_BORDER,
3075                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
3076                              PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3077         pan_item_set_key(pbox, "info");
3078
3079         if (pi->type == ITEM_THUMB && pi->pixbuf)
3080                 {
3081                 w = gdk_pixbuf_get_width(pi->pixbuf);
3082                 h = gdk_pixbuf_get_height(pi->pixbuf);
3083
3084                 x1 = pi->x + pi->width - (pi->width - w) / 2 - 8;
3085                 y1 = pi->y + (pi->height - h) / 2 + 8;
3086                 }
3087         else
3088                 {
3089                 x1 = pi->x + pi->width - 8;
3090                 y1 = pi->y + 8;
3091                 }
3092
3093         x2 = pbox->x + 1;
3094         y2 = pbox->y + 36;
3095         x3 = pbox->x + 1;
3096         y3 = pbox->y + 12;
3097         triangle_rect_region(x1, y1, x2, y2, x3, y3,
3098                              &x, &y, &w, &h);
3099
3100         p = pan_item_new_tri(pw, NULL, x, y, w, h,
3101                              x1, y1, x2, y2, x3, y3,
3102                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA);
3103         pan_item_tri_border(p, BORDER_1 | BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3104         pan_item_set_key(p, "info");
3105         pan_item_added(pw, p);
3106
3107         plabel = pan_item_new_text(pw, pbox->x, pbox->y,
3108                                    _("Filename:"), TEXT_ATTR_BOLD,
3109                                    PAN_POPUP_TEXT_COLOR, 255);
3110         pan_item_set_key(plabel, "info");
3111         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3112                               pi->fd->name, TEXT_ATTR_NONE,
3113                               PAN_POPUP_TEXT_COLOR, 255);
3114         pan_item_set_key(p, "info");
3115         pan_item_size_by_item(pbox, p, 0);
3116
3117         plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3118                                    _("Date:"), TEXT_ATTR_BOLD,
3119                                    PAN_POPUP_TEXT_COLOR, 255);
3120         pan_item_set_key(plabel, "info");
3121         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3122                               text_from_time(pi->fd->date), TEXT_ATTR_NONE,
3123                               PAN_POPUP_TEXT_COLOR, 255);
3124         pan_item_set_key(p, "info");
3125         pan_item_size_by_item(pbox, p, 0);
3126
3127         plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3128                                    _("Size:"), TEXT_ATTR_BOLD,
3129                                    PAN_POPUP_TEXT_COLOR, 255);
3130         pan_item_set_key(plabel, "info");
3131         buf = text_from_size(pi->fd->size);
3132         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3133                               buf, TEXT_ATTR_NONE,
3134                               PAN_POPUP_TEXT_COLOR, 255);
3135         g_free(buf);
3136         pan_item_set_key(p, "info");
3137         pan_item_size_by_item(pbox, p, 0);
3138
3139         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
3140         pan_item_added(pw, pbox);
3141 }
3142
3143
3144 /*
3145  *-----------------------------------------------------------------------------
3146  * search
3147  *-----------------------------------------------------------------------------
3148  */
3149
3150 static void pan_search_status(PanWindow *pw, const gchar *text)
3151 {
3152         gtk_label_set_text(GTK_LABEL(pw->search_label), (text) ? text : "");
3153 }
3154
3155 static gint pan_search_by_path(PanWindow *pw, const gchar *path)
3156 {
3157         PanItem *pi;
3158         GList *list;
3159         GList *found;
3160         ItemType type;
3161         gchar *buf;
3162
3163         type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3164
3165         list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
3166         if (!list) return FALSE;
3167
3168         found = g_list_find(list, pw->click_pi);
3169         if (found && found->next)
3170                 {
3171                 found = found->next;
3172                 pi = found->data;
3173                 }
3174         else
3175                 {
3176                 pi = list->data;
3177                 }
3178
3179         pan_info_update(pw, pi);
3180         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3181
3182         buf = g_strdup_printf("%s ( %d / %d )",
3183                               (path[0] == '/') ? _("path found") : _("filename found"),
3184                               g_list_index(list, pi) + 1,
3185                               g_list_length(list));
3186         pan_search_status(pw, buf);
3187         g_free(buf);
3188
3189         g_list_free(list);
3190
3191         return TRUE;
3192 }
3193
3194 static gint pan_search_by_partial(PanWindow *pw, const gchar *text)
3195 {
3196         PanItem *pi;
3197         GList *list;
3198         GList *found;
3199         ItemType type;
3200         gchar *buf;
3201
3202         type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3203
3204         list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
3205         if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
3206         if (!list)
3207                 {
3208                 gchar *needle;
3209
3210                 needle = g_utf8_strdown(text, -1);
3211                 list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
3212                 g_free(needle);
3213                 }
3214         if (!list) return FALSE;
3215
3216         found = g_list_find(list, pw->click_pi);
3217         if (found && found->next)
3218                 {
3219                 found = found->next;
3220                 pi = found->data;
3221                 }
3222         else
3223                 {
3224                 pi = list->data;
3225                 }
3226
3227         pan_info_update(pw, pi);
3228         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3229
3230         buf = g_strdup_printf("%s ( %d / %d )",
3231                               _("partial match"),
3232                               g_list_index(list, pi) + 1,
3233                               g_list_length(list));
3234         pan_search_status(pw, buf);
3235         g_free(buf);
3236
3237         g_list_free(list);
3238
3239         return TRUE;
3240 }
3241
3242 static gint valid_date_separator(gchar c)
3243 {
3244         return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
3245 }
3246
3247 static GList *pan_search_by_date_val(PanWindow *pw, ItemType type, gint year, gint month, gint day)
3248 {
3249         GList *list = NULL;
3250         GList *work;
3251
3252         work = g_list_last(pw->list);
3253         while (work)
3254                 {
3255                 PanItem *pi;
3256
3257                 pi = work->data;
3258                 work = work->prev;
3259
3260                 if (pi->fd && (pi->type == type || type == ITEM_NONE))
3261                         {
3262                         struct tm *tl;
3263
3264                         tl = localtime(&pi->fd->date);
3265                         if (tl)
3266                                 {
3267                                 gint match;
3268
3269                                 match = (tl->tm_year == year - 1900);
3270                                 if (match && month >= 0) match = (tl->tm_mon == month - 1);
3271                                 if (match && day > 0) match = (tl->tm_mday == day);
3272
3273                                 if (match) list = g_list_prepend(list, pi);
3274                                 }
3275                         }
3276                 }
3277
3278         return g_list_reverse(list);
3279 }
3280
3281 static gint pan_search_by_date(PanWindow *pw, const gchar *text)
3282 {
3283         PanItem *pi = NULL;
3284         GList *list;
3285         GList *found;
3286         gint year;
3287         gint month = -1;
3288         gint day = -1;
3289         gchar *ptr;
3290         gchar *mptr;
3291         struct tm *lt;
3292         time_t t;
3293         gchar *message;
3294         gchar *buf;
3295         gchar *buf_count;
3296         ItemType type;
3297
3298         if (!text) return FALSE;
3299
3300         ptr = (gchar *)text;
3301         while (*ptr != '\0')
3302                 {
3303                 if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
3304                 ptr++;
3305                 }
3306
3307         t = time(NULL);
3308         if (t == -1) return FALSE;
3309         lt = localtime(&t);
3310         if (!lt) return FALSE;
3311
3312         if (valid_date_separator(*text))
3313                 {
3314                 year = -1;
3315                 mptr = (gchar *)text;
3316                 }
3317         else
3318                 {
3319                 year = (gint)strtol(text, &mptr, 10);
3320                 if (mptr == text) return FALSE;
3321                 }
3322
3323         if (*mptr != '\0' && valid_date_separator(*mptr))
3324                 {
3325                 gchar *dptr;
3326
3327                 mptr++;
3328                 month = strtol(mptr, &dptr, 10);
3329                 if (dptr == mptr)
3330                         {
3331                         if (valid_date_separator(*dptr))
3332                                 {
3333                                 month = lt->tm_mon + 1;
3334                                 dptr++;
3335                                 }
3336                         else
3337                                 {
3338                                 month = -1;
3339                                 }
3340                         }
3341                 if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
3342                         {
3343                         gchar *eptr;
3344                         dptr++;
3345                         day = strtol(dptr, &eptr, 10);
3346                         if (dptr == eptr)
3347                                 {
3348                                 day = lt->tm_mday;
3349                                 }
3350                         }
3351                 }
3352
3353         if (year == -1)
3354                 {
3355                 year = lt->tm_year + 1900;
3356                 }
3357         else if (year < 100)
3358                 {
3359                 if (year > 70)
3360                         year+= 1900;
3361                 else
3362                         year+= 2000;
3363                 }
3364
3365         if (year < 1970 ||
3366             month < -1 || month == 0 || month > 12 ||
3367             day < -1 || day == 0 || day > 31) return FALSE;
3368
3369         t = date_to_time(year, month, day);
3370         if (t < 0) return FALSE;
3371
3372         type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3373
3374         list = pan_search_by_date_val(pw, type, year, month, day);
3375         if (list)
3376                 {
3377                 found = g_list_find(list, pw->click_pi);
3378                 if (found && found->next)
3379                         {
3380                         found = found->next;
3381                         pi = found->data;
3382                         }
3383                 else
3384                         {
3385                         pi = list->data;
3386                         }
3387                 }
3388
3389         if (pi)
3390                 {
3391                 pan_info_update(pw, pi);
3392                 image_scroll_to_point(pw->imd,
3393                                       pi->x - PAN_FOLDER_BOX_BORDER * 5 / 2,
3394                                       pi->y, 0.0, 0.5);
3395                 }
3396
3397         if (month > 0)
3398                 {
3399                 buf = date_value_string(t, DATE_LENGTH_MONTH);
3400                 if (day > 0)
3401                         {
3402                         gchar *tmp;
3403                         tmp = buf;
3404                         buf = g_strdup_printf("%d %s", day, tmp);
3405                         g_free(tmp);
3406                         }
3407                 }
3408         else
3409                 {
3410                 buf = date_value_string(t, DATE_LENGTH_YEAR);
3411                 }
3412
3413         if (pi)
3414                 {
3415                 buf_count = g_strdup_printf("( %d / %d )",
3416                                             g_list_index(list, pi) + 1,
3417                                             g_list_length(list));
3418                 }
3419         else
3420                 {
3421                 buf_count = g_strdup_printf("(%s)", _("no match"));
3422                 }
3423
3424         message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
3425         g_free(buf);
3426         g_free(buf_count);
3427         pan_search_status(pw, message);
3428         g_free(message);
3429
3430         g_list_free(list);
3431
3432         return TRUE;
3433 }
3434
3435 static void pan_search_activate_cb(const gchar *text, gpointer data)
3436 {
3437         PanWindow *pw = data;
3438
3439         if (!text) return;
3440
3441         tab_completion_append_to_history(pw->search_entry, text);
3442
3443         if (pan_search_by_path(pw, text)) return;
3444
3445         if (pw->layout == LAYOUT_TIMELINE && pan_search_by_date(pw, text)) return;
3446
3447         if (pan_search_by_partial(pw, text)) return;
3448
3449         pan_search_status(pw, _("no match"));
3450 }
3451
3452 static void pan_search_toggle_cb(GtkWidget *button, gpointer data)
3453 {
3454         PanWindow *pw = data;
3455         gint visible;
3456
3457         visible = GTK_WIDGET_VISIBLE(pw->search_box);
3458         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
3459
3460         if (visible)
3461                 {
3462                 gtk_widget_hide(pw->search_box);
3463                 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
3464                 }
3465         else
3466                 {
3467                 gtk_widget_show(pw->search_box);
3468                 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
3469                 gtk_widget_grab_focus(pw->search_entry);
3470                 }
3471 }
3472
3473
3474 /*
3475  *-----------------------------------------------------------------------------
3476  * view window main routines
3477  *-----------------------------------------------------------------------------
3478  */ 
3479
3480 static void button_cb(ImageWindow *imd, gint button, guint32 time,
3481                       gdouble x, gdouble y, guint state, gpointer data)
3482 {
3483         PanWindow *pw = data;
3484         PanItem *pi = NULL;
3485         GtkWidget *menu;
3486
3487         if (pw->imd->scale)
3488                 {
3489                 pi = pan_item_find_by_coord(pw, (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB,
3490                             (gint)((double)(pw->imd->x_scroll + x - pw->imd->x_offset) / pw->imd->scale),
3491                             (gint)((double)(pw->imd->y_scroll + y - pw->imd->y_offset) / pw->imd->scale));
3492                 }
3493
3494         switch (button)
3495                 {
3496                 case 1:
3497                         pan_info_update(pw, pi);
3498                         break;
3499                 case 2:
3500                         break;
3501                 case 3:
3502                         pan_info_update(pw, pi);
3503                         menu = pan_popup_menu(pw);
3504                         gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, time);
3505                         break;
3506                 default:
3507                         break;
3508                 }
3509 }
3510
3511 static void scroll_cb(ImageWindow *imd, GdkScrollDirection direction, guint32 time,
3512                       gdouble x, gdouble y, guint state, gpointer data)
3513 {
3514 #if 0
3515         PanWindow *pw = data;
3516 #endif
3517
3518         if (state & GDK_CONTROL_MASK)
3519                 {
3520                 switch (direction)
3521                         {
3522                         case GDK_SCROLL_UP:
3523                                 image_zoom_adjust_at_point(imd, ZOOM_INCREMENT, x, y);
3524                                 break;
3525                         case GDK_SCROLL_DOWN:
3526                                 image_zoom_adjust_at_point(imd, -ZOOM_INCREMENT, x, y);
3527                                 break;
3528                         default:
3529                                 break;
3530                         }
3531                 }
3532         else if ( (state & GDK_SHIFT_MASK) != (mousewheel_scrolls))
3533                 {
3534                 switch (direction)
3535                         {
3536                         case GDK_SCROLL_UP:
3537                                 image_scroll(imd, 0, -MOUSEWHEEL_SCROLL_SIZE);
3538                                 break;
3539                         case GDK_SCROLL_DOWN:
3540                                 image_scroll(imd, 0, MOUSEWHEEL_SCROLL_SIZE);
3541                                 break;
3542                         case GDK_SCROLL_LEFT:
3543                                 image_scroll(imd, -MOUSEWHEEL_SCROLL_SIZE, 0);
3544                                 break;
3545                         case GDK_SCROLL_RIGHT:
3546                                 image_scroll(imd, MOUSEWHEEL_SCROLL_SIZE, 0);
3547                                 break;
3548                         default:
3549                                 break;
3550                         }
3551                 }
3552         else
3553                 {
3554                 switch (direction)
3555                         {
3556                         case GDK_SCROLL_UP:
3557                                 break;
3558                         case GDK_SCROLL_DOWN:
3559                                 break;
3560                         default:
3561                                 break;
3562                         }
3563                 }
3564 }
3565
3566 static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd)
3567 {
3568         image_set_button_func(imd, button_cb, pw);
3569         image_set_scroll_func(imd, scroll_cb, pw);
3570 }
3571
3572 static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data)
3573 {
3574         PanWindow *pw = data;
3575
3576         pw->fs = NULL;
3577 }
3578
3579 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off)
3580 {
3581         if (force_off && !pw->fs) return;
3582
3583         if (pw->fs)
3584                 {
3585                 fullscreen_stop(pw->fs);
3586                 pw->imd = pw->imd_normal;
3587                 }
3588         else
3589                 {
3590                 pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw);
3591
3592                 pan_image_set_buttons(pw, pw->fs->imd);
3593                 g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event",
3594                                  G_CALLBACK(pan_window_key_press_cb), pw);
3595
3596                 pw->imd = pw->fs->imd;
3597                 }
3598 }
3599
3600 static void pan_overlay_toggle(PanWindow *pw)
3601 {
3602         ImageWindow *imd;
3603
3604         imd = pan_window_active_image(pw);
3605 #if 0
3606         if (pw->overlay_id == -1)
3607                 {
3608                 pw->overlay_id = image_overlay_info_enable(imd);
3609                 }
3610         else
3611                 {
3612                 image_overlay_info_disable(imd, pw->overlay_id);
3613                 pw->overlay_id = -1;
3614                 }
3615 #endif
3616 }
3617
3618 static void pan_window_image_update_cb(ImageWindow *imd, gpointer data)
3619 {
3620         PanWindow *pw = data;
3621         gchar *text;
3622
3623         text = image_zoom_get_as_text(imd);
3624         gtk_label_set_text(GTK_LABEL(pw->label_zoom), text);
3625         g_free(text);
3626 }
3627
3628 static void pan_window_image_scroll_notify_cb(ImageWindow *imd, gint x, gint y,
3629                                               gint width, gint height, gpointer data)
3630 {
3631         PanWindow *pw = data;
3632         GtkAdjustment *adj;
3633
3634         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h));
3635         adj->page_size = (gdouble)imd->vis_width / imd->scale;
3636         adj->page_increment = adj->page_size / 2.0;
3637         adj->step_increment = 48.0 / imd->scale;
3638         adj->lower = 0.0;
3639         adj->upper = MAX((gdouble)width + adj->page_size, 1.0);
3640         adj->value = (gdouble)x;
3641
3642         pref_signal_block_data(pw->scrollbar_h, pw);
3643         gtk_adjustment_changed(adj);
3644         pref_signal_unblock_data(pw->scrollbar_h, pw);
3645
3646         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v));
3647         adj->page_size = (gdouble)imd->vis_height / imd->scale;
3648         adj->page_increment = adj->page_size / 2.0;
3649         adj->step_increment = 48.0 / imd->scale;
3650         adj->lower = 0.0;
3651         adj->upper = MAX((gdouble)height + adj->page_size, 1.0);
3652         adj->value = (gdouble)y;
3653
3654         pref_signal_block_data(pw->scrollbar_v, pw);
3655         gtk_adjustment_changed(adj);
3656         pref_signal_unblock_data(pw->scrollbar_v, pw);
3657
3658 //      printf("scrolled to %d,%d @ %d x %d\n", x, y, width, height);
3659 }
3660
3661 static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data)
3662 {
3663         PanWindow *pw = data;
3664         gint x;
3665
3666         if (!pw->imd->scale) return;
3667
3668         x = (gint)gtk_range_get_value(range);
3669
3670         image_scroll_to_point(pw->imd, x, (gint)((gdouble)pw->imd->y_scroll / pw->imd->scale), 0.0, 0.0);
3671 }
3672
3673 static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data)
3674 {
3675         PanWindow *pw = data;
3676         gint y;
3677
3678         if (!pw->imd->scale) return;
3679
3680         y = (gint)gtk_range_get_value(range);
3681
3682         image_scroll_to_point(pw->imd, (gint)((gdouble)pw->imd->x_scroll / pw->imd->scale), y, 0.0, 0.0);
3683 }
3684
3685 static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data)
3686 {
3687         PanWindow *pw = data;
3688
3689         pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
3690         pan_window_layout_update_idle(pw);
3691 }
3692
3693 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
3694 {
3695         PanWindow *pw = data;
3696
3697         pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
3698         pan_window_layout_update_idle(pw);
3699 }
3700
3701 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
3702 {
3703         PanWindow *pw = data;
3704         gchar *path;
3705
3706         path = remove_trailing_slash(new_text);
3707         parse_out_relatives(path);
3708
3709         if (!isdir(path))
3710                 {
3711                 warning_dialog(_("Folder not found"),
3712                                _("The entered path is not a folder"),
3713                                GTK_STOCK_DIALOG_WARNING, pw->path_entry);
3714                 return;
3715                 }
3716
3717         tab_completion_append_to_history(pw->path_entry, path);
3718
3719         g_free(pw->path);
3720         pw->path = g_strdup(path);
3721
3722         pan_window_layout_update_idle(pw);
3723 }
3724
3725 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
3726 {
3727         PanWindow *pw = data;
3728         gchar *text;
3729
3730         if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return;
3731
3732         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
3733         pan_window_entry_activate_cb(text, pw);
3734         g_free(text);
3735 }
3736
3737 static void pan_window_close(PanWindow *pw)
3738 {
3739         pan_window_list = g_list_remove(pan_window_list, pw);
3740
3741         if (pw->idle_id != -1)
3742                 {
3743                 g_source_remove(pw->idle_id);
3744                 }
3745
3746         pan_fullscreen_toggle(pw, TRUE);
3747         gtk_widget_destroy(pw->window);
3748
3749         pan_window_items_free(pw);
3750
3751         g_free(pw->path);
3752
3753         g_free(pw);
3754 }
3755
3756 static gint pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
3757 {
3758         PanWindow *pw = data;
3759
3760         pan_window_close(pw);
3761         return TRUE;
3762 }
3763
3764 static void pan_window_new_real(const gchar *path)
3765 {
3766         PanWindow *pw;
3767         GtkWidget *vbox;
3768         GtkWidget *box;
3769         GtkWidget *combo;
3770         GtkWidget *hbox;
3771         GtkWidget *frame;
3772         GtkWidget *table;
3773         GdkGeometry geometry;
3774
3775         pw = g_new0(PanWindow, 1);
3776
3777         pw->path = g_strdup(path);
3778         pw->layout = LAYOUT_TIMELINE;
3779         pw->size = LAYOUT_SIZE_THUMB_NORMAL;
3780         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
3781         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
3782         pw->list = NULL;
3783
3784         pw->fs = NULL;
3785         pw->overlay_id = -1;
3786         pw->idle_id = -1;
3787
3788         pw->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3789
3790         geometry.min_width = 8;
3791         geometry.min_height = 8;
3792         gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE);
3793
3794         gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE);
3795         gtk_window_set_title (GTK_WINDOW(pw->window), "Pan View - GQview");
3796         gtk_window_set_wmclass(GTK_WINDOW(pw->window), "view", "GQview");
3797         gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0);
3798
3799         window_set_icon(pw->window, NULL, NULL);
3800
3801         vbox = gtk_vbox_new(FALSE, 0);
3802         gtk_container_add(GTK_CONTAINER(pw->window), vbox);
3803         gtk_widget_show(vbox);
3804
3805         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
3806
3807         pref_spacer(box, 0);
3808         pref_label_new(box, _("Location:"));
3809         combo = tab_completion_new_with_history(&pw->path_entry, path, "pan_view", -1,
3810                                                 pan_window_entry_activate_cb, pw);
3811         g_signal_connect(G_OBJECT(pw->path_entry->parent), "changed",
3812                          G_CALLBACK(pan_window_entry_change_cb), pw);
3813         gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
3814         gtk_widget_show(combo);
3815
3816         combo = gtk_combo_box_new_text();
3817         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Timeline"));
3818         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders"));
3819         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders (flower)"));
3820         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Grid"));
3821
3822         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout);
3823         g_signal_connect(G_OBJECT(combo), "changed",
3824                          G_CALLBACK(pan_window_layout_change_cb), pw);
3825         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
3826         gtk_widget_show(combo);
3827
3828         combo = gtk_combo_box_new_text();
3829         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Dots"));
3830         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("No Images"));
3831         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Small Thumbnails"));
3832         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Normal Thumbnails"));
3833         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Large Thumbnails"));
3834         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:10 (10%)"));
3835         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:4 (25%)"));
3836         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:3 (33%)"));
3837         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:2 (50%)"));
3838         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:1 (100%)"));
3839
3840         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size);
3841         g_signal_connect(G_OBJECT(combo), "changed",
3842                          G_CALLBACK(pan_window_layout_size_cb), pw);
3843         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
3844         gtk_widget_show(combo);
3845
3846         table = pref_table_new(vbox, 2, 2, FALSE, TRUE);
3847         gtk_table_set_row_spacings(GTK_TABLE(table), 2);
3848         gtk_table_set_col_spacings(GTK_TABLE(table), 2);
3849
3850         pw->imd = image_new(TRUE);
3851         pw->imd_normal = pw->imd;
3852
3853         if (black_window_background) image_background_set_black(pw->imd, TRUE);
3854         image_set_update_func(pw->imd, pan_window_image_update_cb, pw);
3855
3856         image_set_scroll_notify_func(pw->imd, pan_window_image_scroll_notify_cb, pw);
3857
3858 #if 0
3859         gtk_box_pack_start(GTK_BOX(vbox), pw->imd->widget, TRUE, TRUE, 0);
3860 #endif
3861         gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1,
3862                          GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
3863         gtk_widget_show(pw->imd->widget);
3864
3865         pan_window_dnd_init(pw);
3866
3867         pan_image_set_buttons(pw, pw->imd);
3868
3869         pw->scrollbar_h = gtk_hscrollbar_new(NULL);
3870         g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed",
3871                          G_CALLBACK(pan_window_scrollbar_h_value_cb), pw);
3872         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2,
3873                          GTK_FILL | GTK_EXPAND, 0, 0, 0);
3874         gtk_widget_show(pw->scrollbar_h);
3875
3876         pw->scrollbar_v = gtk_vscrollbar_new(NULL);
3877         g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed",
3878                          G_CALLBACK(pan_window_scrollbar_v_value_cb), pw);
3879         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1,
3880                          0, GTK_FILL | GTK_EXPAND, 0, 0);
3881         gtk_widget_show(pw->scrollbar_v);
3882
3883         /* find bar */
3884
3885         pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
3886         gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2);
3887
3888         pref_spacer(pw->search_box, 0);
3889         pref_label_new(pw->search_box, _("Find:"));
3890
3891         hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
3892         gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0);
3893         gtk_widget_show(hbox);
3894
3895         combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1,
3896                                                 pan_search_activate_cb, pw);
3897         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
3898         gtk_widget_show(combo);
3899
3900         pw->search_label = gtk_label_new("");
3901         gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0);
3902         gtk_widget_show(pw->search_label);
3903
3904         /* status bar */
3905
3906         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
3907
3908         frame = gtk_frame_new(NULL);
3909         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
3910         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
3911         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
3912         gtk_widget_show(frame);
3913
3914         hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
3915         gtk_container_add(GTK_CONTAINER(frame), hbox);
3916         gtk_widget_show(hbox);
3917
3918         pref_spacer(hbox, 0);
3919         pw->label_message = pref_label_new(hbox, "");
3920
3921         frame = gtk_frame_new(NULL);
3922         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
3923         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
3924         gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0);
3925         gtk_widget_show(frame);
3926
3927         pw->label_zoom = gtk_label_new("");
3928         gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
3929         gtk_widget_show(pw->label_zoom);
3930
3931         pw->search_button = gtk_toggle_button_new();
3932         gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE);
3933         gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE);
3934         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
3935         gtk_container_add(GTK_CONTAINER(pw->search_button), hbox);
3936         gtk_widget_show(hbox);
3937         pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
3938         gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0);
3939         gtk_widget_show(pw->search_button_arrow);
3940         pref_label_new(hbox, _("Find"));
3941
3942         gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0);
3943         gtk_widget_show(pw->search_button);
3944         g_signal_connect(G_OBJECT(pw->search_button), "clicked",
3945                          G_CALLBACK(pan_search_toggle_cb), pw);
3946
3947         g_signal_connect(G_OBJECT(pw->window), "delete_event",
3948                          G_CALLBACK(pan_window_delete_cb), pw);
3949         g_signal_connect(G_OBJECT(pw->window), "key_press_event",
3950                          G_CALLBACK(pan_window_key_press_cb), pw);
3951
3952         gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
3953
3954         pan_window_layout_update_idle(pw);
3955
3956         gtk_widget_grab_focus(pw->imd->widget);
3957         gtk_widget_show(pw->window);
3958
3959         pan_window_list = g_list_append(pan_window_list, pw);
3960 }
3961
3962 /*
3963  *-----------------------------------------------------------------------------
3964  * peformance warnings
3965  *-----------------------------------------------------------------------------
3966  */
3967
3968 static void pan_warning_ok_cb(GenericDialog *gd, gpointer data)
3969 {
3970         gchar *path = data;
3971
3972         generic_dialog_close(gd);
3973
3974         pan_window_new_real(path);
3975         g_free(path);
3976 }
3977
3978 static void pan_warning_hide_cb(GtkWidget *button, gpointer data)
3979 {
3980         gint hide_dlg;
3981
3982         hide_dlg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
3983         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, hide_dlg);
3984 }
3985
3986 static gint pan_warning(const gchar *path)
3987 {
3988         GenericDialog *gd;
3989         GtkWidget *box;
3990         GtkWidget *group;
3991         GtkWidget *button;
3992         GtkWidget *ct_button;
3993         gint hide_dlg;
3994
3995         if (enable_thumb_caching &&
3996             thumbnail_spec_standard) return FALSE;
3997
3998         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, &hide_dlg)) hide_dlg = FALSE;
3999         if (hide_dlg) return FALSE;
4000
4001         gd = generic_dialog_new(_("Pan View Performance"), "GQview", "pan_view_warning", NULL, FALSE,
4002                                 NULL, NULL);
4003         gd->data = g_strdup(path);
4004         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
4005                                   pan_warning_ok_cb, TRUE);
4006
4007         box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO,
4008                                          _("Pan view performance may be poor."),
4009                                          _("To improve performance of thumbnails in the pan view the"
4010                                            " following options can be enabled. Note that both options"
4011                                            " must be enabled to notice a change in performance."));
4012
4013         group = pref_box_new(box, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
4014         pref_spacer(group, PREF_PAD_INDENT);
4015         group = pref_box_new(group, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
4016
4017         ct_button = pref_checkbox_new_int(group, _("Cache thumbnails"),
4018                                           enable_thumb_caching, &enable_thumb_caching);
4019         button = pref_checkbox_new_int(group, _("Use shared thumbnail cache"),
4020                                        thumbnail_spec_standard, &thumbnail_spec_standard);
4021         pref_checkbox_link_sensitivity(ct_button, button);
4022
4023         pref_line(box, 0);
4024
4025         pref_checkbox_new(box, _("Do not show this dialog again"), hide_dlg,
4026                           G_CALLBACK(pan_warning_hide_cb), NULL);
4027
4028         gtk_widget_show(gd->dialog);
4029
4030         return TRUE;
4031 }
4032
4033
4034 /*
4035  *-----------------------------------------------------------------------------
4036  * public
4037  *-----------------------------------------------------------------------------
4038  */
4039
4040 void pan_window_new(const gchar *path)
4041 {
4042         if (pan_warning(path)) return;
4043
4044         pan_window_new_real(path);
4045 }
4046
4047 /*
4048  *-----------------------------------------------------------------------------
4049  * menus
4050  *-----------------------------------------------------------------------------
4051  */
4052
4053 static void pan_new_window_cb(GtkWidget *widget, gpointer data)
4054 {
4055         PanWindow *pw = data;
4056         const gchar *path;
4057
4058         path = pan_menu_click_path(pw);
4059         if (path)
4060                 {
4061                 pan_fullscreen_toggle(pw, TRUE);
4062                 view_window_new(path);
4063                 }
4064 }
4065
4066 static void pan_edit_cb(GtkWidget *widget, gpointer data)
4067 {
4068         PanWindow *pw;
4069         const gchar *path;
4070         gint n;
4071
4072         pw = submenu_item_get_data(widget);
4073         n = GPOINTER_TO_INT(data);
4074         if (!pw) return;
4075
4076         path = pan_menu_click_path(pw);
4077         if (path)
4078                 {
4079                 pan_fullscreen_toggle(pw, TRUE);
4080                 start_editor_from_file(n, path);
4081                 }
4082 }
4083
4084 static void pan_info_cb(GtkWidget *widget, gpointer data)
4085 {
4086         PanWindow *pw = data;
4087         const gchar *path;
4088
4089         path = pan_menu_click_path(pw);
4090         if (path) info_window_new(path, NULL);
4091 }
4092
4093 static void pan_zoom_in_cb(GtkWidget *widget, gpointer data)
4094 {
4095         PanWindow *pw = data;
4096
4097         image_zoom_adjust(pan_window_active_image(pw), ZOOM_INCREMENT);
4098 }
4099
4100 static void pan_zoom_out_cb(GtkWidget *widget, gpointer data)
4101 {
4102         PanWindow *pw = data;
4103
4104         image_zoom_adjust(pan_window_active_image(pw), -ZOOM_INCREMENT);
4105 }
4106
4107 static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data)
4108 {
4109         PanWindow *pw = data;
4110
4111         image_zoom_set(pan_window_active_image(pw), 1.0);
4112 }
4113
4114 static void pan_copy_cb(GtkWidget *widget, gpointer data)
4115 {
4116         PanWindow *pw = data;
4117         const gchar *path;
4118
4119         path = pan_menu_click_path(pw);
4120         if (path) file_util_copy(path, NULL, NULL, pw->imd->widget);
4121 }
4122
4123 static void pan_move_cb(GtkWidget *widget, gpointer data)
4124 {
4125         PanWindow *pw = data;
4126         const gchar *path;
4127
4128         path = pan_menu_click_path(pw);
4129         if (path) file_util_move(path, NULL, NULL, pw->imd->widget);
4130 }
4131
4132 static void pan_rename_cb(GtkWidget *widget, gpointer data)
4133 {
4134         PanWindow *pw = data;
4135         const gchar *path;
4136
4137         path = pan_menu_click_path(pw);
4138         if (path) file_util_rename(path, NULL, pw->imd->widget);
4139 }
4140
4141 static void pan_delete_cb(GtkWidget *widget, gpointer data)
4142 {
4143         PanWindow *pw = data;
4144         const gchar *path;
4145
4146         path = pan_menu_click_path(pw);
4147         if (path) file_util_delete(path, NULL, pw->imd->widget);
4148 }
4149
4150 static void pan_fullscreen_cb(GtkWidget *widget, gpointer data)
4151 {
4152         PanWindow *pw = data;
4153
4154         pan_fullscreen_toggle(pw, FALSE);
4155 }
4156
4157 static void pan_close_cb(GtkWidget *widget, gpointer data)
4158 {
4159         PanWindow *pw = data;
4160
4161         pan_window_close(pw);
4162 }
4163
4164 static GtkWidget *pan_popup_menu(PanWindow *pw)
4165 {
4166         GtkWidget *menu;
4167         GtkWidget *item;
4168         gint active;
4169
4170         active = (pw->click_pi != NULL);
4171
4172         menu = popup_menu_short_lived();
4173
4174         menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
4175                             G_CALLBACK(pan_zoom_in_cb), pw);
4176         menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
4177                             G_CALLBACK(pan_zoom_out_cb), pw);
4178         menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100,
4179                             G_CALLBACK(pan_zoom_1_1_cb), pw);
4180         menu_item_add_divider(menu);
4181
4182         submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw);
4183         gtk_widget_set_sensitive(item, active);
4184
4185         menu_item_add_stock_sensitive(menu, _("_Properties"), GTK_STOCK_PROPERTIES, active,
4186                                       G_CALLBACK(pan_info_cb), pw);
4187
4188         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
4189                                       G_CALLBACK(pan_new_window_cb), pw);
4190
4191         menu_item_add_divider(menu);
4192         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
4193                                       G_CALLBACK(pan_copy_cb), pw);
4194         menu_item_add_sensitive(menu, _("_Move..."), active,
4195                                 G_CALLBACK(pan_move_cb), pw);
4196         menu_item_add_sensitive(menu, _("_Rename..."), active,
4197                                 G_CALLBACK(pan_rename_cb), pw);
4198         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
4199                                       G_CALLBACK(pan_delete_cb), pw);
4200
4201         menu_item_add_divider(menu);
4202
4203         if (pw->fs)
4204                 {
4205                 menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4206                 }
4207         else
4208                 {
4209                 menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4210                 }
4211
4212         menu_item_add_divider(menu);
4213         menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw);
4214
4215         return menu;
4216 }
4217
4218 /*
4219  *-----------------------------------------------------------------------------
4220  * drag and drop
4221  *-----------------------------------------------------------------------------
4222  */
4223
4224 static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context,
4225                                     gint x, gint y,
4226                                     GtkSelectionData *selection_data, guint info,
4227                                     guint time, gpointer data)
4228 {
4229         PanWindow *pw = data;
4230         ImageWindow *imd;
4231
4232         if (gtk_drag_get_source_widget(context) == pw->imd->image) return;
4233
4234         imd = pw->imd;
4235
4236         if (info == TARGET_URI_LIST)
4237                 {
4238                 GList *list;
4239
4240                 list = uri_list_from_text(selection_data->data, TRUE);
4241                 if (list && isdir((gchar *)list->data))
4242                         {
4243                         gchar *path = list->data;
4244
4245                         g_free(pw->path);
4246                         pw->path = g_strdup(path);
4247
4248                         pan_window_layout_update_idle(pw);
4249                         }
4250
4251                 path_list_free(list);
4252                 }
4253 }
4254
4255 static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context,
4256                                     GtkSelectionData *selection_data, guint info,
4257                                     guint time, gpointer data)
4258 {
4259         PanWindow *pw = data;
4260         const gchar *path;
4261
4262         path = pan_menu_click_path(pw);
4263         if (path)
4264                 {
4265                 gchar *text = NULL;
4266                 gint len;
4267                 gint plain_text;
4268                 GList *list;
4269
4270                 switch (info)
4271                         {
4272                         case TARGET_URI_LIST:
4273                                 plain_text = FALSE;
4274                                 break;
4275                         case TARGET_TEXT_PLAIN:
4276                         default:
4277                                 plain_text = TRUE;
4278                                 break;
4279                         }
4280                 list = g_list_append(NULL, (gchar *)path);
4281                 text = uri_text_from_list(list, &len, plain_text);
4282                 g_list_free(list);
4283                 if (text)
4284                         {
4285                         gtk_selection_data_set (selection_data, selection_data->target,
4286                                                 8, text, len);
4287                         g_free(text);
4288                         }
4289                 }
4290         else
4291                 {
4292                 gtk_selection_data_set (selection_data, selection_data->target,
4293                                         8, NULL, 0);
4294                 }
4295 }
4296
4297 static void pan_window_dnd_init(PanWindow *pw)
4298 {
4299         ImageWindow *imd;
4300
4301         imd = pw->imd;
4302
4303         gtk_drag_source_set(imd->image, GDK_BUTTON2_MASK,
4304                             dnd_file_drag_types, dnd_file_drag_types_count,
4305                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4306         g_signal_connect(G_OBJECT(imd->image), "drag_data_get",
4307                          G_CALLBACK(pan_window_set_dnd_data), pw);
4308
4309         gtk_drag_dest_set(imd->image,
4310                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
4311                           dnd_file_drop_types, dnd_file_drop_types_count,
4312                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4313         g_signal_connect(G_OBJECT(imd->image), "drag_data_received",
4314                          G_CALLBACK(pan_window_get_dnd_data), pw);
4315 }
4316
4317 /*
4318  *-----------------------------------------------------------------------------
4319  * maintenance (for rename, move, remove)
4320  *-----------------------------------------------------------------------------
4321  */
4322