##### Note: GQview CVS on sourceforge is not always up to date, please use #####
[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 PanItem *pan_item_find_by_path(PanWindow *pw, ItemType type, const gchar *path,
1179                                       gint ignore_case, gint partial)
1180 {
1181         GList *work;
1182
1183         if (!path) return NULL;
1184         if (partial && path[0] == '/') return NULL;
1185
1186         work = g_list_last(pw->list);
1187         while (work)
1188                 {
1189                 PanItem *pi;
1190
1191                 pi = work->data;
1192                 if ((pi->type == type || type == ITEM_NONE) && pi->fd)
1193                         {
1194                         if (path[0] == '/')
1195                                 {
1196                                 if (pi->fd->path && strcmp(path, pi->fd->path) == 0) return pi;
1197                                 }
1198                         else if (pi->fd->name)
1199                                 {
1200                                 if (partial)
1201                                         {
1202                                         if (ignore_case)
1203                                                 {
1204                                                 gchar *haystack;
1205                                                 gint match;
1206
1207                                                 haystack = g_utf8_strdown(pi->fd->name, -1);
1208                                                 match = (strstr(haystack, path) != NULL);
1209                                                 g_free(haystack);
1210                                                 if (match) return pi;
1211                                                 }
1212                                         else
1213                                                 {
1214                                                 if (strstr(pi->fd->name, path)) return pi;
1215                                                 }
1216                                         }
1217                                 else if (ignore_case)
1218                                         {
1219                                         if (strcasecmp(path, pi->fd->name) == 0) return pi;
1220                                         }
1221                                 else
1222                                         {
1223                                         if (strcmp(path, pi->fd->name) == 0) return pi;
1224                                         }
1225                                 }
1226                         }
1227                 work = work->prev;
1228                 }
1229
1230         return NULL;
1231 }
1232
1233 static PanItem *pan_item_find_by_coord(PanWindow *pw, ItemType type, gint x, gint y)
1234 {
1235         GList *work;
1236
1237         if (x < 0 || x >= pw->imd->image_width ||
1238             y < 0 || y >= pw->imd->image_height) return  NULL;
1239
1240         work = pw->list;
1241         while (work)
1242                 {
1243                 PanItem *pi;
1244
1245                 pi = work->data;
1246                 if ((pi->type == type || type == ITEM_NONE) &&
1247                      x >= pi->x && x < pi->x + pi->width &&
1248                      y >= pi->y && y < pi->y + pi->height)
1249                         {
1250                         return pi;
1251                         }
1252                 work = work->next;
1253                 }
1254
1255         return NULL;
1256 }
1257
1258 /*
1259  *-----------------------------------------------------------------------------
1260  * layout generation
1261  *-----------------------------------------------------------------------------
1262  */
1263
1264 static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend)
1265 {
1266         GList *flist = NULL;
1267         GList *dlist = NULL;
1268         GList *result;
1269         GList *folders;
1270
1271         filelist_read(path, &flist, &dlist);
1272         if (sort != SORT_NONE)
1273                 {
1274                 flist = filelist_sort(flist, sort, ascend);
1275                 dlist = filelist_sort(dlist, sort, ascend);
1276                 }
1277
1278         result = flist;
1279         folders = dlist;
1280         while (folders)
1281                 {
1282                 FileData *fd;
1283
1284                 fd = folders->data;
1285                 folders = g_list_remove(folders, fd);
1286
1287                 if (filelist_read(fd->path, &flist, &dlist))
1288                         {
1289                         if (sort != SORT_NONE)
1290                                 {
1291                                 flist = filelist_sort(flist, sort, ascend);
1292                                 dlist = filelist_sort(dlist, sort, ascend);
1293                                 }
1294
1295                         result = g_list_concat(result, flist);
1296                         folders = g_list_concat(dlist, folders);
1297                         }
1298
1299                 file_data_free(fd);
1300                 }
1301
1302         return result;
1303 }
1304
1305 static void pan_window_layout_compute_grid(PanWindow *pw, const gchar *path, gint *width, gint *height)
1306 {
1307         GList *list;
1308         GList *work;
1309         gint x, y;
1310         gint grid_size;
1311         gint next_y;
1312
1313         list = pan_window_layout_list(path, SORT_NAME, TRUE);
1314
1315         grid_size = (gint)sqrt((double)g_list_length(list));
1316         if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1317                 {
1318                 grid_size = grid_size * (512 + PAN_THUMB_GAP) * pw->image_size / 100;
1319                 }
1320         else
1321                 {
1322                 grid_size = grid_size * (PAN_THUMB_SIZE + PAN_THUMB_GAP);
1323                 }
1324
1325         next_y = 0;
1326
1327         *width = PAN_FOLDER_BOX_BORDER * 2;
1328         *height = PAN_FOLDER_BOX_BORDER * 2;
1329
1330         x = PAN_THUMB_GAP;
1331         y = PAN_THUMB_GAP;
1332         work = list;
1333         while (work)
1334                 {
1335                 FileData *fd;
1336                 PanItem *pi;
1337
1338                 fd = work->data;
1339                 work = work->next;
1340
1341                 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1342                         {
1343                         pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1344
1345                         x += pi->width + PAN_THUMB_GAP;
1346                         if (y + pi->height + PAN_THUMB_GAP > next_y) next_y = y + pi->height + PAN_THUMB_GAP;
1347                         if (x > grid_size)
1348                                 {
1349                                 x = PAN_THUMB_GAP;
1350                                 y = next_y;
1351                                 }
1352                         }
1353                 else
1354                         {
1355                         pi = pan_item_new_thumb(pw, fd, x, y);
1356
1357                         x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1358                         if (x > grid_size)
1359                                 {
1360                                 x = PAN_THUMB_GAP;
1361                                 y += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1362                                 }
1363                         }
1364                 pan_item_size_coordinates(pi, PAN_THUMB_GAP, width, height);
1365                 }
1366
1367         g_list_free(list);
1368 }
1369
1370 static void pan_window_Layout_compute_folders_flower_size(PanWindow *pw, gint *width, gint *height)
1371 {
1372         GList *work;
1373         gint x1, y1, x2, y2;
1374
1375         x1 = 0;
1376         y1 = 0;
1377         x2 = 0;
1378         y2 = 0;
1379
1380         work = pw->list;
1381         while (work)
1382                 {
1383                 PanItem *pi;
1384
1385                 pi = work->data;
1386                 work = work->next;
1387
1388                 if (x1 > pi->x) x1 = pi->x;
1389                 if (y1 > pi->y) y1 = pi->y;
1390                 if (x2 < pi->x + pi->width) x2 = pi->x + pi->width;
1391                 if (y2 < pi->y + pi->height) y2 = pi->y + pi->height;
1392                 }
1393
1394         x1 -= PAN_FOLDER_BOX_BORDER;
1395         y1 -= PAN_FOLDER_BOX_BORDER;
1396         x2 += PAN_FOLDER_BOX_BORDER;
1397         y2 += PAN_FOLDER_BOX_BORDER;
1398
1399         work = pw->list;
1400         while (work)
1401                 {
1402                 PanItem *pi;
1403
1404                 pi = work->data;
1405                 work = work->next;
1406
1407                 pi->x -= x1;
1408                 pi->y -= y1;
1409
1410                 if (pi->type == ITEM_TRIANGLE && pi->data)
1411                         {
1412                         gint *coord;
1413
1414                         coord = pi->data;
1415                         coord[0] -= x1;
1416                         coord[1] -= y1;
1417                         coord[2] -= x1;
1418                         coord[3] -= y1;
1419                         coord[4] -= x1;
1420                         coord[5] -= y1;
1421                         }
1422                 }
1423
1424         if (width) *width = x2 - x1;
1425         if (height) *height = y2 - y1;
1426 }
1427
1428 typedef struct _FlowerGroup FlowerGroup;
1429 struct _FlowerGroup {
1430         GList *items;
1431         GList *children;
1432         gint x;
1433         gint y;
1434         gint width;
1435         gint height;
1436
1437         gdouble angle;
1438         gint circumference;
1439         gint diameter;
1440 };
1441
1442 static void pan_window_layout_compute_folder_flower_move(FlowerGroup *group, gint x, gint y)
1443 {
1444         GList *work;
1445
1446         work = group->items;
1447         while (work)
1448                 {
1449                 PanItem *pi;
1450
1451                 pi = work->data;
1452                 work = work->next;
1453
1454                 pi->x += x;
1455                 pi->y += y;
1456                 }
1457
1458         group->x += x;
1459         group->y += y;
1460 }
1461
1462 #define PI 3.14159
1463
1464 static void pan_window_layout_compute_folder_flower_position(FlowerGroup *group, FlowerGroup *parent,
1465                                                              gint *result_x, gint *result_y)
1466 {
1467         gint x, y;
1468         gint radius;
1469         gdouble a;
1470
1471         radius = parent->circumference / (2*PI);
1472         radius = MAX(radius, parent->diameter / 2 + group->diameter / 2);
1473
1474         a = 2*PI * group->diameter / parent->circumference;
1475
1476         x = (gint)((double)radius * cos(parent->angle + a / 2));
1477         y = (gint)((double)radius * sin(parent->angle + a / 2));
1478
1479         parent->angle += a;
1480
1481         x += parent->x;
1482         y += parent->y;
1483
1484         x += parent->width / 2;
1485         y += parent->height / 2;
1486
1487         x -= group->width / 2;
1488         y -= group->height / 2;
1489
1490         *result_x = x;
1491         *result_y = y;
1492 }
1493
1494 static void pan_window_layout_compute_folder_flower_build(PanWindow *pw, FlowerGroup *group, FlowerGroup *parent)
1495 {
1496         GList *work;
1497         gint x, y;
1498
1499         if (!group) return;
1500
1501         if (parent && parent->children)
1502                 {
1503                 pan_window_layout_compute_folder_flower_position(group, parent, &x, &y);
1504                 }
1505         else
1506                 {
1507                 x = 0;
1508                 y = 0;
1509                 }
1510
1511         pan_window_layout_compute_folder_flower_move(group, x, y);
1512
1513         if (parent)
1514                 {
1515                 PanItem *pi;
1516                 gint px, py, gx, gy;
1517                 gint x1, y1, x2, y2;
1518
1519                 px = parent->x + parent->width / 2;
1520                 py = parent->y + parent->height / 2;
1521
1522                 gx = group->x + group->width / 2;
1523                 gy = group->y + group->height / 2;
1524
1525                 x1 = MIN(px, gx);
1526                 y1 = MIN(py, gy);
1527
1528                 x2 = MAX(px, gx + 5);
1529                 y2 = MAX(py, gy + 5);
1530
1531                 pi = pan_item_new_tri(pw, NULL, x1, y1, x2 - x1, y2 - y1,
1532                                       px, py, gx, gy, gx + 5, gy + 5,
1533                                       255, 40, 40, 128);
1534                 pan_item_tri_border(pi, BORDER_1 | BORDER_3,
1535                                     255, 0, 0, 128);
1536                 }
1537
1538         pw->list = g_list_concat(group->items, pw->list);
1539         group->items = NULL;
1540
1541         group->circumference = 0;
1542         work = group->children;
1543         while (work)
1544                 {
1545                 FlowerGroup *child;
1546
1547                 child = work->data;
1548                 work = work->next;
1549
1550                 group->circumference += child->diameter;
1551                 }
1552
1553         work = g_list_last(group->children);
1554         while (work)
1555                 {
1556                 FlowerGroup *child;
1557
1558                 child = work->data;
1559                 work = work->prev;
1560
1561                 pan_window_layout_compute_folder_flower_build(pw, child, group);
1562                 }
1563
1564         g_list_free(group->children);
1565         g_free(group);
1566 }
1567
1568 static FlowerGroup *pan_window_layout_compute_folders_flower_path(PanWindow *pw, const gchar *path,
1569                                                                   gint x, gint y)
1570 {
1571         FlowerGroup *group;
1572         GList *f;
1573         GList *d;
1574         GList *work;
1575         PanItem *pi_box;
1576         gint x_start;
1577         gint y_height;
1578         gint grid_size;
1579         gint grid_count;
1580
1581         if (!filelist_read(path, &f, &d)) return NULL;
1582         if (!f && !d) return NULL;
1583
1584         f = filelist_sort(f, SORT_NAME, TRUE);
1585         d = filelist_sort(d, SORT_NAME, TRUE);
1586
1587         pi_box = pan_item_new_text(pw, x, y, path, TEXT_ATTR_NONE,
1588                                    PAN_TEXT_COLOR, 255);
1589
1590         y += pi_box->height;
1591
1592         pi_box = pan_item_new_box(pw, file_data_new_simple(path),
1593                                   x, y,
1594                                   PAN_FOLDER_BOX_BORDER * 2, PAN_FOLDER_BOX_BORDER * 2,
1595                                   PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1596                                   PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1597                                   PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1598
1599         x += PAN_FOLDER_BOX_BORDER;
1600         y += PAN_FOLDER_BOX_BORDER;
1601
1602         grid_size = (gint)(sqrt(g_list_length(f)) + 0.9);
1603         grid_count = 0;
1604         x_start = x;
1605         y_height = y;
1606
1607         work = f;
1608         while (work)
1609                 {
1610                 FileData *fd;
1611                 PanItem *pi;
1612
1613                 fd = work->data;
1614                 work = work->next;
1615
1616                 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1617                         {
1618                         pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1619                         x += pi->width + PAN_THUMB_GAP;
1620                         if (pi->height > y_height) y_height = pi->height;
1621                         }
1622                 else
1623                         {
1624                         pi = pan_item_new_thumb(pw, fd, x, y);
1625                         x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1626                         y_height = PAN_THUMB_SIZE;
1627                         }
1628
1629                 grid_count++;
1630                 if (grid_count >= grid_size)
1631                         {
1632                         grid_count = 0;
1633                         x = x_start;
1634                         y += y_height + PAN_THUMB_GAP;
1635                         y_height = 0;
1636                         }
1637
1638                 pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER);
1639                 }
1640
1641         g_list_free(f);
1642
1643         group = g_new0(FlowerGroup, 1);
1644         group->items = pw->list;
1645         pw->list = NULL;
1646
1647         group->width = pi_box->width;
1648         group->height = pi_box->y + pi_box->height;
1649         group->diameter = (int)sqrt(group->width * group->width + group->height * group->height);
1650
1651         group->children = NULL;
1652
1653         work = d;
1654         while (work)
1655                 {
1656                 FileData *fd;
1657                 FlowerGroup *child;
1658
1659                 fd = work->data;
1660                 work = work->next;
1661
1662                 child = pan_window_layout_compute_folders_flower_path(pw, fd->path, 0, 0);
1663                 if (child) group->children = g_list_prepend(group->children, child);
1664                 }
1665
1666         filelist_free(d);
1667
1668         return group;
1669 }
1670
1671 static void pan_window_layout_compute_folders_flower(PanWindow *pw, const gchar *path,
1672                                                      gint *width, gint *height,
1673                                                      gint *scroll_x, gint *scroll_y)
1674 {
1675         FlowerGroup *group;
1676         PanItem *pi;
1677
1678         group = pan_window_layout_compute_folders_flower_path(pw, path, 0, 0);
1679         pan_window_layout_compute_folder_flower_build(pw, group, NULL);
1680
1681         pan_window_Layout_compute_folders_flower_size(pw, width, height);
1682
1683         pi = pan_item_find_by_path(pw, ITEM_BOX, path, FALSE, FALSE);
1684         if (pi)
1685                 {
1686                 *scroll_x = pi->x + pi->width / 2;
1687                 *scroll_y = pi->y + pi->height / 2;
1688                 }
1689 }
1690
1691 static void pan_window_layout_compute_folders_linear_path(PanWindow *pw, const gchar *path,
1692                                                           gint *x, gint *y, gint *level,
1693                                                           PanItem *parent,
1694                                                           gint *width, gint *height)
1695 {
1696         GList *f;
1697         GList *d;
1698         GList *work;
1699         PanItem *pi_box;
1700         gint y_height = 0;
1701
1702         if (!filelist_read(path, &f, &d)) return;
1703         if (!f && !d) return;
1704
1705         f = filelist_sort(f, SORT_NAME, TRUE);
1706         d = filelist_sort(d, SORT_NAME, TRUE);
1707
1708         *x = PAN_FOLDER_BOX_BORDER + ((*level) * MAX(PAN_FOLDER_BOX_BORDER, PAN_THUMB_GAP));
1709
1710         pi_box = pan_item_new_text(pw, *x, *y, path, TEXT_ATTR_NONE,
1711                                    PAN_TEXT_COLOR, 255);
1712
1713         *y += pi_box->height;
1714
1715         pi_box = pan_item_new_box(pw, file_data_new_simple(path),
1716                                   *x, *y,
1717                                   PAN_FOLDER_BOX_BORDER, PAN_FOLDER_BOX_BORDER,
1718                                   PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1719                                   PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1720                                   PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1721
1722         *x += PAN_FOLDER_BOX_BORDER;
1723         *y += PAN_FOLDER_BOX_BORDER;
1724
1725         work = f;
1726         while (work)
1727                 {
1728                 FileData *fd;
1729                 PanItem *pi;
1730
1731                 fd = work->data;
1732                 work = work->next;
1733
1734                 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1735                         {
1736                         pi = pan_item_new_image(pw, fd, *x, *y, 10, 10);
1737                         *x += pi->width + PAN_THUMB_GAP;
1738                         if (pi->height > y_height) y_height = pi->height;
1739                         }
1740                 else
1741                         {
1742                         pi = pan_item_new_thumb(pw, fd, *x, *y);
1743                         *x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1744                         y_height = PAN_THUMB_SIZE;
1745                         }
1746
1747                 pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER);
1748                 }
1749
1750         if (f) *y = pi_box->y + pi_box->height;
1751
1752         g_list_free(f);
1753
1754         work = d;
1755         while (work)
1756                 {
1757                 FileData *fd;
1758
1759                 fd = work->data;
1760                 work = work->next;
1761
1762                 *level = *level + 1;
1763                 pan_window_layout_compute_folders_linear_path(pw, fd->path, x, y, level,
1764                                                               pi_box, width, height);
1765                 *level = *level - 1;
1766                 }
1767
1768         filelist_free(d);
1769
1770         pan_item_size_by_item(parent, pi_box, PAN_FOLDER_BOX_BORDER);
1771
1772         if (*y < pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER)
1773                 *y = pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER;
1774
1775         pan_item_size_coordinates(pi_box, PAN_FOLDER_BOX_BORDER, width, height);
1776 }
1777
1778 static void pan_window_layout_compute_folders_linear(PanWindow *pw, const gchar *path, gint *width, gint *height)
1779 {
1780         gint x, y;
1781         gint level;
1782         gint w, h;
1783
1784         level = 0;
1785         x = PAN_FOLDER_BOX_BORDER;
1786         y = PAN_FOLDER_BOX_BORDER;
1787         w = PAN_FOLDER_BOX_BORDER * 2;
1788         h = PAN_FOLDER_BOX_BORDER * 2;
1789
1790         pan_window_layout_compute_folders_linear_path(pw, path, &x, &y, &level, NULL, &w, &h);
1791
1792         if (width) *width = w;
1793         if (height) *height = h;
1794 }
1795
1796 static void pan_window_layout_compute_timeline(PanWindow *pw, const gchar *path, gint *width, gint *height)
1797 {
1798         GList *list;
1799         GList *work;
1800         gint x, y;
1801         time_t tc;
1802         gint total;
1803         gint count;
1804         PanItem *pi_month = NULL;
1805         PanItem *pi_day = NULL;
1806         gint month_start;
1807         gint day_start;
1808         gint x_width;
1809         gint y_height;
1810
1811         pw->cache_list = filelist_sort(pw->cache_list, SORT_TIME, TRUE);
1812
1813         list = pan_window_layout_list(path, SORT_NONE, TRUE);
1814         list = filelist_sort(list, SORT_TIME, TRUE);
1815
1816         *width = PAN_FOLDER_BOX_BORDER * 2;
1817         *height = PAN_FOLDER_BOX_BORDER * 2;
1818
1819         x = 0;
1820         y = 0;
1821         month_start = y;
1822         day_start = month_start;
1823         x_width = 0;
1824         y_height = 0;
1825         tc = 0;
1826         total = 0;
1827         count = 0;
1828         work = list;
1829         while (work)
1830                 {
1831                 FileData *fd;
1832                 PanItem *pi;
1833
1834                 fd = work->data;
1835                 work = work->next;
1836
1837                 if (!date_compare(fd->date, tc, DATE_LENGTH_DAY))
1838                         {
1839                         GList *needle;
1840                         gchar *buf;
1841
1842                         if (!date_compare(fd->date, tc, DATE_LENGTH_MONTH))
1843                                 {
1844                                 pi_day = NULL;
1845
1846                                 if (pi_month)
1847                                         {
1848                                         x = pi_month->x + pi_month->width + PAN_FOLDER_BOX_BORDER;
1849                                         }
1850                                 else
1851                                         {
1852                                         x = PAN_FOLDER_BOX_BORDER;
1853                                         }
1854
1855                                 y = PAN_FOLDER_BOX_BORDER;
1856
1857                                 buf = date_value_string(fd->date, DATE_LENGTH_MONTH);
1858                                 pi = pan_item_new_text(pw, x, y, buf,
1859                                                        TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
1860                                                        PAN_TEXT_COLOR, 255);
1861                                 y += pi->height;
1862
1863                                 pi_month = pan_item_new_box(pw, file_data_new_simple(fd->path),
1864                                                             x, y, 0, 0,
1865                                                             PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1866                                                             PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1867                                                             PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1868
1869                                 x += PAN_FOLDER_BOX_BORDER;
1870                                 y += PAN_FOLDER_BOX_BORDER;
1871                                 month_start = y;
1872                                 }
1873
1874                         if (pi_day) x = pi_day->x + pi_day->width + PAN_FOLDER_BOX_BORDER;
1875
1876                         tc = fd->date;
1877                         total = 1;
1878                         count = 0;
1879
1880                         needle = work;
1881                         while (needle)
1882                                 {
1883                                 FileData *nfd;
1884
1885                                 nfd = needle->data;
1886                                 if (date_compare(nfd->date, tc, DATE_LENGTH_DAY))
1887                                         {
1888                                         needle = needle->next;
1889                                         total++;
1890                                         }
1891                                 else
1892                                         {
1893                                         needle = NULL;
1894                                         }
1895                                 }
1896
1897                         buf = date_value_string(fd->date, DATE_LENGTH_WEEK);
1898                         pi = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_NONE,
1899                                                PAN_TEXT_COLOR, 255);
1900                         g_free(buf);
1901
1902                         y += pi->height;
1903
1904                         pi_day = pan_item_new_box(pw, file_data_new_simple(fd->path), x, y, 0, 0,
1905                                                   PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1906                                                   PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1907                                                   PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1908
1909                         x += PAN_FOLDER_BOX_BORDER;
1910                         y += PAN_FOLDER_BOX_BORDER;
1911                         day_start = y;
1912                         }
1913
1914                 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1915                         {
1916                         pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1917                         if (pi->width > x_width) x_width = pi->width;
1918                         y_height = pi->height;
1919                         }
1920                 else
1921                         {
1922                         pi = pan_item_new_thumb(pw, fd, x, y);
1923                         x_width = PAN_THUMB_SIZE;
1924                         y_height = PAN_THUMB_SIZE;
1925                         }
1926
1927                 pan_item_size_by_item(pi_day, pi, PAN_FOLDER_BOX_BORDER);
1928                 pan_item_size_by_item(pi_month, pi_day, PAN_FOLDER_BOX_BORDER);
1929
1930                 total--;
1931                 count++;
1932
1933                 if (total > 0 && count < PAN_GROUP_MAX)
1934                         {
1935                         y += y_height + PAN_THUMB_GAP;
1936                         }
1937                 else
1938                         {
1939                         x += x_width + PAN_THUMB_GAP;
1940                         x_width = 0;
1941                         count = 0;
1942
1943                         if (total > 0)
1944                                 y = day_start;
1945                         else
1946                                 y = month_start;
1947                         }
1948
1949                 pan_item_size_coordinates(pi_month, PAN_FOLDER_BOX_BORDER, width, height);
1950                 }
1951
1952         g_list_free(list);
1953 }
1954
1955 static void pan_window_layout_compute(PanWindow *pw, const gchar *path,
1956                                       gint *width, gint *height,
1957                                       gint *scroll_x, gint *scroll_y)
1958 {
1959         pan_window_items_free(pw);
1960
1961         switch (pw->size)
1962                 {
1963                 case LAYOUT_SIZE_THUMB_DOTS:
1964                         pw->thumb_size = PAN_THUMB_SIZE_DOTS;
1965                         pw->thumb_gap = PAN_THUMB_GAP_DOTS;
1966                         break;
1967                 case LAYOUT_SIZE_THUMB_NONE:
1968                         pw->thumb_size = PAN_THUMB_SIZE_NONE;
1969                         pw->thumb_gap = PAN_THUMB_GAP_SMALL;
1970                         break;
1971                 case LAYOUT_SIZE_THUMB_SMALL:
1972                         pw->thumb_size = PAN_THUMB_SIZE_SMALL;
1973                         pw->thumb_gap = PAN_THUMB_GAP_SMALL;
1974                         break;
1975                 case LAYOUT_SIZE_THUMB_NORMAL:
1976                 default:
1977                         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
1978                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
1979                         break;
1980                 case LAYOUT_SIZE_THUMB_LARGE:
1981                         pw->thumb_size = PAN_THUMB_SIZE_LARGE;
1982                         pw->thumb_gap = PAN_THUMB_GAP_LARGE;
1983                         break;
1984                 case LAYOUT_SIZE_10:
1985                         pw->image_size = 10;
1986                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
1987                         break;
1988                 case LAYOUT_SIZE_25:
1989                         pw->image_size = 25;
1990                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
1991                         break;
1992                 case LAYOUT_SIZE_33:
1993                         pw->image_size = 33;
1994                         pw->thumb_gap = PAN_THUMB_GAP_LARGE;
1995                         break;
1996                 case LAYOUT_SIZE_50:
1997                         pw->image_size = 50;
1998                         pw->thumb_gap = PAN_THUMB_GAP_HUGE;
1999                         break;
2000                 case LAYOUT_SIZE_100:
2001                         pw->image_size = 100;
2002                         pw->thumb_gap = PAN_THUMB_GAP_HUGE;
2003                         break;
2004                 }
2005
2006         *width = 0;
2007         *height = 0;
2008         *scroll_x = 0;
2009         *scroll_y = 0;
2010
2011         switch (pw->layout)
2012                 {
2013                 case LAYOUT_GRID:
2014                 default:
2015                         pan_window_layout_compute_grid(pw, path, width, height);
2016                         break;
2017                 case LAYOUT_FOLDERS_LINEAR:
2018                         pan_window_layout_compute_folders_linear(pw, path, width, height);
2019                         break;
2020                 case LAYOUT_FOLDERS_FLOWER:
2021                         pan_window_layout_compute_folders_flower(pw, path, width, height, scroll_x, scroll_y);
2022                         break;
2023                 case LAYOUT_TIMELINE:
2024                         pan_window_layout_compute_timeline(pw, path, width, height);
2025                         break;
2026                 }
2027
2028         pan_cache_free(pw);
2029
2030         printf("computed %d objects\n", g_list_length(pw->list));
2031 }
2032
2033 static GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height)
2034 {
2035         GList *list = NULL;
2036         GList *work;
2037
2038         work = pw->list;
2039         while (work)
2040                 {
2041                 PanItem *pi;
2042
2043                 pi = work->data;
2044                 work = work->next;
2045
2046                 if (util_clip_region_test(x, y, width, height,
2047                                           pi->x, pi->y, pi->width, pi->height))
2048                         {
2049                         list = g_list_prepend(list, pi);
2050                         }
2051                 }
2052
2053         return list;
2054 }
2055
2056
2057
2058 /*
2059  *-----------------------------------------------------------------------------
2060  * tile generation
2061  *-----------------------------------------------------------------------------
2062  */
2063
2064 static gint pan_layout_queue_step(PanWindow *pw);
2065
2066
2067 static void pan_layout_queue_thumb_done_cb(ThumbLoader *tl, gpointer data)
2068 {
2069         PanWindow *pw = data;
2070
2071         if (pw->queue_pi)
2072                 {
2073                 PanItem *pi;
2074                 gint rc;
2075
2076                 pi = pw->queue_pi;
2077                 pw->queue_pi = NULL;
2078
2079                 pi->queued = FALSE;
2080
2081                 if (pi->pixbuf) g_object_unref(pi->pixbuf);
2082                 pi->pixbuf = thumb_loader_get_pixbuf(tl, TRUE);
2083
2084                 rc = pi->refcount;
2085                 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
2086                 pi->refcount = rc;
2087                 }
2088
2089         thumb_loader_free(pw->tl);
2090         pw->tl = NULL;
2091
2092         while (pan_layout_queue_step(pw));
2093 }
2094
2095 static void pan_layout_queue_image_done_cb(ImageLoader *il, gpointer data)
2096 {
2097         PanWindow *pw = data;
2098
2099         if (pw->queue_pi)
2100                 {
2101                 PanItem *pi;
2102                 gint rc;
2103
2104                 pi = pw->queue_pi;
2105                 pw->queue_pi = NULL;
2106
2107                 pi->queued = FALSE;
2108
2109                 if (pi->pixbuf) g_object_unref(pi->pixbuf);
2110                 pi->pixbuf = image_loader_get_pixbuf(pw->il);
2111                 if (pi->pixbuf) g_object_ref(pi->pixbuf);
2112
2113                 if (pi->pixbuf && pw->size != LAYOUT_SIZE_100 &&
2114                     (gdk_pixbuf_get_width(pi->pixbuf) > pi->width ||
2115                      gdk_pixbuf_get_height(pi->pixbuf) > pi->height))
2116                         {
2117                         GdkPixbuf *tmp;
2118
2119                         tmp = pi->pixbuf;
2120                         pi->pixbuf = gdk_pixbuf_scale_simple(tmp, pi->width, pi->height,
2121                                                              (GdkInterpType)zoom_quality);
2122                         g_object_unref(tmp);
2123                         }
2124
2125                 rc = pi->refcount;
2126                 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
2127                 pi->refcount = rc;
2128                 }
2129
2130         image_loader_free(pw->il);
2131         pw->il = NULL;
2132
2133         while (pan_layout_queue_step(pw));
2134 }
2135
2136 #if 0
2137 static void pan_layout_queue_image_area_cb(ImageLoader *il, guint x, guint y,
2138                                            guint width, guint height, gpointer data)
2139 {
2140         PanWindow *pw = data;
2141
2142         if (pw->queue_pi)
2143                 {
2144                 PanItem *pi;
2145                 gint rc;
2146
2147                 pi = pw->queue_pi;
2148
2149                 if (!pi->pixbuf)
2150                         {
2151                         pi->pixbuf = image_loader_get_pixbuf(pw->il);
2152                         if (pi->pixbuf) g_object_ref(pi->pixbuf);
2153                         }
2154
2155                 rc = pi->refcount;
2156                 image_area_changed(pw->imd, pi->x + x, pi->y + y, width, height);
2157                 pi->refcount = rc;
2158                 }
2159 }
2160 #endif
2161
2162 static gint pan_layout_queue_step(PanWindow *pw)
2163 {
2164         PanItem *pi;
2165
2166         if (!pw->queue) return FALSE;
2167
2168         pi = pw->queue->data;
2169         pw->queue = g_list_remove(pw->queue, pi);
2170         pw->queue_pi = pi;
2171
2172         image_loader_free(pw->il);
2173         pw->il = NULL;
2174         thumb_loader_free(pw->tl);
2175         pw->tl = NULL;
2176
2177         if (pi->type == ITEM_IMAGE)
2178                 {
2179                 pw->il = image_loader_new(pi->fd->path);
2180
2181                 if (pw->size != LAYOUT_SIZE_100)
2182                         {
2183                         image_loader_set_requested_size(pw->il, pi->width, pi->height);
2184                         }
2185
2186 #if 0
2187                 image_loader_set_area_ready_func(pw->il, pan_layout_queue_image_area_cb, pw);
2188 #endif
2189                 image_loader_set_error_func(pw->il, pan_layout_queue_image_done_cb, pw);
2190
2191                 if (image_loader_start(pw->il, pan_layout_queue_image_done_cb, pw)) return FALSE;
2192
2193                 image_loader_free(pw->il);
2194                 pw->il = NULL;
2195                 }
2196         else if (pi->type == ITEM_THUMB)
2197                 {
2198                 pw->tl = thumb_loader_new(PAN_THUMB_SIZE, PAN_THUMB_SIZE);
2199
2200                 if (!pw->tl->standard_loader)
2201                         {
2202                         /* The classic loader will recreate a thumbnail any time we
2203                          * request a different size than what exists. This view will
2204                          * almost never use the user configured sizes so disable cache.
2205                          */
2206                         thumb_loader_set_cache(pw->tl, FALSE, FALSE, FALSE);
2207                         }
2208
2209                 thumb_loader_set_callbacks(pw->tl,
2210                                            pan_layout_queue_thumb_done_cb,
2211                                            pan_layout_queue_thumb_done_cb,
2212                                            NULL, pw);
2213
2214                 if (thumb_loader_start(pw->tl, pi->fd->path)) return FALSE;
2215
2216                 thumb_loader_free(pw->tl);
2217                 pw->tl = NULL;
2218                 }
2219
2220         pw->queue_pi->queued = FALSE;
2221         pw->queue_pi = NULL;
2222         return TRUE;
2223 }
2224
2225 static void pan_layout_queue(PanWindow *pw, PanItem *pi)
2226 {
2227         if (!pi || pi->queued || pi->pixbuf) return;
2228         if (pw->size <= LAYOUT_SIZE_THUMB_NONE) return;
2229
2230         pi->queued = TRUE;
2231         pw->queue = g_list_prepend(pw->queue, pi);
2232
2233         if (!pw->tl && !pw->il) while(pan_layout_queue_step(pw));
2234 }
2235
2236 static gint pan_window_request_tile_cb(ImageWindow *imd, gint x, gint y, gint width, gint height,
2237                                        GdkPixbuf *pixbuf, gpointer data)
2238 {
2239         PanWindow *pw = data;
2240         GList *list;
2241         GList *work;
2242         gint i;
2243
2244         pixbuf_draw_rect_fill(pixbuf,
2245                          0, 0, width, height,
2246                          PAN_BACKGROUND_COLOR, 255);
2247
2248         for (i = (x / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < x + width; i += PAN_GRID_SIZE)
2249                 {
2250                 gint rx, ry, rw, rh;
2251
2252                 if (util_clip_region(x, y, width, height,
2253                                      i, y, 1, height,
2254                                      &rx, &ry, &rw, &rh))
2255                         {
2256                         pixbuf_draw_rect_fill(pixbuf,
2257                                               rx - x, ry - y, rw, rh,
2258                                               PAN_GRID_COLOR, PAN_GRID_ALPHA);
2259                         }
2260                 }
2261         for (i = (y / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < y + height; i += PAN_GRID_SIZE)
2262                 {
2263                 gint rx, ry, rw, rh;
2264
2265                 if (util_clip_region(x, y, width, height,
2266                                      x, i, width, 1,
2267                                      &rx, &ry, &rw, &rh))
2268                         {
2269                         pixbuf_draw_rect_fill(pixbuf,
2270                                               rx - x, ry - y, rw, rh,
2271                                               PAN_GRID_COLOR, PAN_GRID_ALPHA);
2272                         }
2273                 }
2274
2275         list = pan_layout_intersect(pw, x, y, width, height);
2276         work = list;
2277         while (work)
2278                 {
2279                 PanItem *pi;
2280                 gint tx, ty, tw, th;
2281                 gint rx, ry, rw, rh;
2282
2283                 pi = work->data;
2284                 work = work->next;
2285
2286                 pi->refcount++;
2287
2288                 if (pi->type == ITEM_THUMB && pi->pixbuf)
2289                         {
2290                         tw = gdk_pixbuf_get_width(pi->pixbuf);
2291                         th = gdk_pixbuf_get_height(pi->pixbuf);
2292
2293                         tx = pi->x + (pi->width - tw) / 2;
2294                         ty = pi->y + (pi->height - th) / 2;
2295
2296                         if (gdk_pixbuf_get_has_alpha(pi->pixbuf))
2297                                 {
2298                                 if (util_clip_region(x, y, width, height,
2299                                                      tx + PAN_SHADOW_OFFSET, ty + PAN_SHADOW_OFFSET, tw, th,
2300                                                      &rx, &ry, &rw, &rh))
2301                                         {
2302                                         pixbuf_draw_shadow(pixbuf,
2303                                                            rx - x, ry - y, rw, rh,
2304                                                            tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2305                                                            PAN_SHADOW_FADE,
2306                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2307                                         }
2308                                 }
2309                         else
2310                                 {
2311                                 if (util_clip_region(x, y, width, height,
2312                                                      tx + tw, ty + PAN_SHADOW_OFFSET,
2313                                                      PAN_SHADOW_OFFSET, th - PAN_SHADOW_OFFSET,
2314                                                      &rx, &ry, &rw, &rh))
2315                                         {
2316                                         pixbuf_draw_shadow(pixbuf,
2317                                                            rx - x, ry - y, rw, rh,
2318                                                            tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2319                                                            PAN_SHADOW_FADE,
2320                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2321                                         }
2322                                 if (util_clip_region(x, y, width, height,
2323                                                      tx + PAN_SHADOW_OFFSET, ty + th, tw, PAN_SHADOW_OFFSET,
2324                                                      &rx, &ry, &rw, &rh))
2325                                         {
2326                                         pixbuf_draw_shadow(pixbuf,
2327                                                            rx - x, ry - y, rw, rh,
2328                                                            tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2329                                                            PAN_SHADOW_FADE,
2330                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2331                                         }
2332                                 }
2333
2334                         if (util_clip_region(x, y, width, height,
2335                                              tx, ty, tw, th,
2336                                              &rx, &ry, &rw, &rh))
2337                                 {
2338                                 gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh,
2339                                                      (double) tx - x,
2340                                                      (double) ty - y,
2341                                                      1.0, 1.0, GDK_INTERP_NEAREST,
2342                                                      255);
2343                                 }
2344
2345                         if (util_clip_region(x, y, width, height,
2346                                              tx, ty, tw, PAN_OUTLINE_THICKNESS,
2347                                              &rx, &ry, &rw, &rh))
2348                                 {
2349                                 pixbuf_draw_rect_fill(pixbuf,
2350                                                       rx - x, ry - y, rw, rh,
2351                                                       PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA);
2352                                 }
2353                         if (util_clip_region(x, y, width, height,
2354                                              tx, ty, PAN_OUTLINE_THICKNESS, th,
2355                                              &rx, &ry, &rw, &rh))
2356                                 {
2357                                 pixbuf_draw_rect_fill(pixbuf,
2358                                                       rx - x, ry - y, rw, rh,
2359                                                       PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA);
2360                                 }
2361                         if (util_clip_region(x, y, width, height,
2362                                              tx + tw - PAN_OUTLINE_THICKNESS, ty +  PAN_OUTLINE_THICKNESS,
2363                                              PAN_OUTLINE_THICKNESS, th - PAN_OUTLINE_THICKNESS,
2364                                              &rx, &ry, &rw, &rh))
2365                                 {
2366                                 pixbuf_draw_rect_fill(pixbuf,
2367                                                       rx - x, ry - y, rw, rh,
2368                                                       PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA);
2369                                 }
2370                         if (util_clip_region(x, y, width, height,
2371                                              tx +  PAN_OUTLINE_THICKNESS, ty + th - PAN_OUTLINE_THICKNESS,
2372                                              tw - PAN_OUTLINE_THICKNESS * 2, PAN_OUTLINE_THICKNESS,
2373                                              &rx, &ry, &rw, &rh))
2374                                 {
2375                                 pixbuf_draw_rect_fill(pixbuf,
2376                                                       rx - x, ry - y, rw, rh,
2377                                                       PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA);
2378                                 }
2379                         }
2380                 else if (pi->type == ITEM_THUMB)
2381                         {
2382                         tw = pi->width - PAN_SHADOW_OFFSET * 2;
2383                         th = pi->height - PAN_SHADOW_OFFSET * 2;
2384                         tx = pi->x + PAN_SHADOW_OFFSET;
2385                         ty = pi->y + PAN_SHADOW_OFFSET;
2386
2387                         if (util_clip_region(x, y, width, height,
2388                                              tx, ty, tw, th,
2389                                              &rx, &ry, &rw, &rh))
2390                                 {
2391                                 gint d;
2392
2393                                 d = (pw->size <= LAYOUT_SIZE_THUMB_NONE) ? 2 : 8;
2394                                 pixbuf_draw_rect_fill(pixbuf,
2395                                                       rx - x, ry - y, rw, rh,
2396                                                       PAN_SHADOW_COLOR,
2397                                                       PAN_SHADOW_ALPHA / d);
2398                                 }
2399
2400                         pan_layout_queue(pw, pi);
2401                         }
2402                 else if (pi->type == ITEM_IMAGE)
2403                         {
2404                         if (util_clip_region(x, y, width, height,
2405                                              pi->x, pi->y, pi->width, pi->height,
2406                                              &rx, &ry, &rw, &rh))
2407                                 {
2408                                 if (pi->pixbuf)
2409                                         {
2410                                         gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh,
2411                                                              (double) pi->x - x,
2412                                                              (double) pi->y - y,
2413                                                              1.0, 1.0, GDK_INTERP_NEAREST,
2414                                                              255);
2415                                         }
2416                                 else
2417                                         {
2418                                         pixbuf_draw_rect_fill(pixbuf,
2419                                                               rx - x, ry - y, rw, rh,
2420                                                               PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA / 2);
2421                                         pan_layout_queue(pw, pi);
2422                                         }
2423                                 }
2424                         }
2425                 else if (pi->type == ITEM_BOX)
2426                         {
2427                         gint bw, bh;
2428                         gint *shadow;
2429
2430                         bw = pi->width;
2431                         bh = pi->height;
2432
2433                         shadow = pi->data;
2434                         if (shadow)
2435                                 {
2436                                 bw -= shadow[0];
2437                                 bh -= shadow[0];
2438
2439                                 if (pi->color_a > 254)
2440                                         {
2441                                         pixbuf_draw_shadow(pixbuf, pi->x - x + bw, pi->y - y + shadow[0],
2442                                                            shadow[0], bh - shadow[0],
2443                                                            pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2444                                                            shadow[1],
2445                                                            PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2446                                         pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + bh,
2447                                                            bw, 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                                         }
2452                                 else
2453                                         {
2454                                         gint a;
2455                                         a = pi->color_a * PAN_SHADOW_ALPHA >> 8;
2456                                         pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + shadow[0],
2457                                                            bw, bh,
2458                                                            pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2459                                                            shadow[1],
2460                                                            PAN_SHADOW_COLOR, a);
2461                                         }
2462                                 }
2463
2464                         if (util_clip_region(x, y, width, height,
2465                                              pi->x, pi->y, bw, bh,
2466                                              &rx, &ry, &rw, &rh))
2467                                 {
2468                                 pixbuf_draw_rect_fill(pixbuf,
2469                                                       rx - x, ry - y, rw, rh,
2470                                                       pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2471                                 }
2472                         if (util_clip_region(x, y, width, height,
2473                                              pi->x, pi->y, bw, pi->border,
2474                                              &rx, &ry, &rw, &rh))
2475                                 {
2476                                 pixbuf_draw_rect_fill(pixbuf,
2477                                                       rx - x, ry - y, rw, rh,
2478                                                       pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2479                                 }
2480                         if (util_clip_region(x, y, width, height,
2481                                              pi->x, pi->y + pi->border, pi->border, bh - pi->border * 2,
2482                                              &rx, &ry, &rw, &rh))
2483                                 {
2484                                 pixbuf_draw_rect_fill(pixbuf,
2485                                                       rx - x, ry - y, rw, rh,
2486                                                       pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2487                                 }
2488                         if (util_clip_region(x, y, width, height,
2489                                              pi->x + bw - pi->border, pi->y + pi->border,
2490                                              pi->border, bh - pi->border * 2,
2491                                              &rx, &ry, &rw, &rh))
2492                                 {
2493                                 pixbuf_draw_rect_fill(pixbuf,
2494                                                       rx - x, ry - y, rw, rh,
2495                                                       pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2496                                 }
2497                         if (util_clip_region(x, y, width, height,
2498                                              pi->x, pi->y + bh - pi->border,
2499                                              bw,  pi->border,
2500                                              &rx, &ry, &rw, &rh))
2501                                 {
2502                                 pixbuf_draw_rect_fill(pixbuf,
2503                                                       rx - x, ry - y, rw, rh,
2504                                                       pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2505                                 }
2506                         }
2507                 else if (pi->type == ITEM_TRIANGLE)
2508                         {
2509                         if (util_clip_region(x, y, width, height,
2510                                              pi->x, pi->y, pi->width, pi->height,
2511                                              &rx, &ry, &rw, &rh) && pi->data)
2512                                 {
2513                                 gint *coord = pi->data;
2514                                 pixbuf_draw_triangle(pixbuf,
2515                                                      rx - x, ry - y, rw, rh,
2516                                                      coord[0] - x, coord[1] - y,
2517                                                      coord[2] - x, coord[3] - y,
2518                                                      coord[4] - x, coord[5] - y,
2519                                                      pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2520
2521                                 if (pi->border & BORDER_1)
2522                                         {
2523                                         pixbuf_draw_line(pixbuf,
2524                                                          rx - x, ry - y, rw, rh,
2525                                                          coord[0] - x, coord[1] - y,
2526                                                          coord[2] - x, coord[3] - y,
2527                                                          pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2528                                         }
2529                                 if (pi->border & BORDER_2)
2530                                         {
2531                                         pixbuf_draw_line(pixbuf,
2532                                                          rx - x, ry - y, rw, rh,
2533                                                          coord[2] - x, coord[3] - y,
2534                                                          coord[4] - x, coord[5] - y,
2535                                                          pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2536                                         }
2537                                 if (pi->border & BORDER_3)
2538                                         {
2539                                         pixbuf_draw_line(pixbuf,
2540                                                          rx - x, ry - y, rw, rh,
2541                                                          coord[4] - x, coord[5] - y,
2542                                                          coord[0] - x, coord[1] - y,
2543                                                          pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2544                                         }
2545                                 }
2546                         }
2547                 else if (pi->type == ITEM_TEXT && pi->text)
2548                         {
2549                         PangoLayout *layout;
2550
2551                         layout = pan_item_text_layout(pi, imd->image);
2552                         pixbuf_draw_layout(pixbuf, layout, imd->image,
2553                                            pi->x - x + PAN_TEXT_BORDER_SIZE, pi->y - y + PAN_TEXT_BORDER_SIZE,
2554                                            pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2555                         g_object_unref(G_OBJECT(layout));
2556                         }
2557                 }
2558         g_list_free(list);
2559
2560         if (0)
2561                 {
2562                 static gint count = 0;
2563                 PangoLayout *layout;
2564                 gint lx, ly;
2565                 gint lw, lh;
2566                 GdkPixmap *pixmap;
2567                 gchar *buf;
2568
2569                 layout = gtk_widget_create_pango_layout(imd->image, NULL);
2570
2571                 buf = g_strdup_printf("%d,%d\n(#%d)", x, y,
2572                                       (x / imd->source_tile_width) +
2573                                       (y / imd->source_tile_height * (imd->image_width/imd->source_tile_width + 1)));
2574                 pango_layout_set_text(layout, buf, -1);
2575                 g_free(buf);
2576
2577                 pango_layout_get_pixel_size(layout, &lw, &lh);
2578
2579                 pixmap = gdk_pixmap_new(imd->widget->window, lw, lh, -1);
2580                 gdk_draw_rectangle(pixmap, imd->widget->style->black_gc, TRUE, 0, 0, lw, lh);
2581                 gdk_draw_layout(pixmap, imd->widget->style->white_gc, 0, 0, layout);
2582                 g_object_unref(G_OBJECT(layout));
2583
2584                 lx = MAX(0, width / 2 - lw / 2);
2585                 ly = MAX(0, height / 2 - lh / 2);
2586                 lw = MIN(lw, width - lx);
2587                 lh = MIN(lh, height - ly);
2588                 gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_drawable_get_colormap(imd->image->window),
2589                                              0, 0, lx, ly, lw, lh);
2590                 g_object_unref(pixmap);
2591
2592                 count++;
2593                 }
2594
2595         return TRUE;
2596 }
2597
2598 static void pan_window_dispose_tile_cb(ImageWindow *imd, gint x, gint y, gint width, gint height,
2599                                        GdkPixbuf *pixbuf, gpointer data)
2600 {
2601         PanWindow *pw = data;
2602         GList *list;
2603         GList *work;
2604
2605         list = pan_layout_intersect(pw, x, y, width, height);
2606         work = list;
2607         while (work)
2608                 {
2609                 PanItem *pi;
2610
2611                 pi = work->data;
2612                 work = work->next;
2613
2614                 if (pi->refcount > 0)
2615                         {
2616                         pi->refcount--;
2617
2618                         if ((pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE) &&
2619                             pi->refcount == 0)
2620                                 {
2621                                 if (pi->queued)
2622                                         {
2623                                         pw->queue = g_list_remove(pw->queue, pi);
2624                                         pi->queued = FALSE;
2625                                         }
2626                                 if (pw->queue_pi == pi) pw->queue_pi = NULL;
2627                                 if (pi->pixbuf)
2628                                         {
2629                                         g_object_unref(pi->pixbuf);
2630                                         pi->pixbuf = NULL;
2631                                         }
2632                                 }
2633                         }
2634                 }
2635
2636         g_list_free(list);
2637 }
2638
2639
2640 /*
2641  *-----------------------------------------------------------------------------
2642  * misc
2643  *-----------------------------------------------------------------------------
2644  */ 
2645
2646 static void pan_window_message(PanWindow *pw, const gchar *text)
2647 {
2648         GList *work;
2649         gint count = 0;
2650         gint64 size = 0;
2651         gchar *ss;
2652         gchar *buf;
2653
2654         if (text)
2655                 {
2656                 gtk_label_set_text(GTK_LABEL(pw->label_message), text);
2657                 return;
2658                 }
2659
2660         work = pw->list;
2661         while (work)
2662                 {
2663                 PanItem *pi;
2664
2665                 pi = work->data;
2666                 work = work->next;
2667
2668                 if (pi->fd &&
2669                     (pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE))
2670                         {
2671                         size += pi->fd->size;
2672                         count++;
2673                         }
2674                 }
2675
2676         ss = text_from_size_abrev(size);
2677         buf = g_strdup_printf(_("%d images, %s"), count, ss);
2678         g_free(ss);
2679         gtk_label_set_text(GTK_LABEL(pw->label_message), buf);
2680         g_free(buf);
2681 }
2682
2683 static ImageWindow *pan_window_active_image(PanWindow *pw)
2684 {
2685         if (pw->fs) return pw->fs->imd;
2686
2687         return pw->imd;
2688 }
2689
2690 static void pan_window_zoom_limit(PanWindow *pw)
2691 {
2692         gdouble min;
2693
2694         switch (pw->size)
2695                 {
2696                 case LAYOUT_SIZE_THUMB_DOTS:
2697                 case LAYOUT_SIZE_THUMB_NONE:
2698                 case LAYOUT_SIZE_THUMB_SMALL:
2699                 case LAYOUT_SIZE_THUMB_NORMAL:
2700 #if 0
2701                         /* easily requires > 512mb ram when window size > 1024x768 and zoom is <= -8 */
2702                         min = -16.0;
2703                         break;
2704 #endif
2705                 case LAYOUT_SIZE_THUMB_LARGE:
2706                         min = -6.0;
2707                         break;
2708                 case LAYOUT_SIZE_10:
2709                 case LAYOUT_SIZE_25:
2710                         min = -4.0;
2711                         break;
2712                 case LAYOUT_SIZE_33:
2713                 case LAYOUT_SIZE_50:
2714                 case LAYOUT_SIZE_100:
2715                 default:
2716                         min = -2.0;
2717                         break;
2718                 }
2719
2720         image_zoom_set_limits(pw->imd, min, 32.0);
2721 }
2722
2723 static gint pan_window_layout_update_idle_cb(gpointer data)
2724 {
2725         PanWindow *pw = data;
2726         gint width;
2727         gint height;
2728         gint scroll_x;
2729         gint scroll_y;
2730
2731         if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
2732                 {
2733                 if (!pw->cache_list && !pw->cache_todo)
2734                         {
2735                         pan_cache_fill(pw, pw->path);
2736                         if (pw->cache_todo)
2737                                 {
2738                                 pan_window_message(pw, _("Reading dimensions..."));
2739                                 return TRUE;
2740                                 }
2741                         }
2742                 if (pan_cache_step(pw))
2743                         {
2744                         pw->cache_count++;
2745                         pw->cache_tick++;
2746                         if (pw->cache_count == pw->cache_total)
2747                                 {
2748                                 pan_window_message(pw, _("Sorting images..."));
2749                                 }
2750                         else if (pw->cache_tick > 9)
2751                                 {
2752                                 gchar *buf;
2753
2754                                 buf = g_strdup_printf("%s %d", _("Reading dimensions..."),
2755                                                       pw->cache_total - pw->cache_count);
2756                                 pan_window_message(pw, buf);
2757                                 g_free(buf);
2758
2759                                 pw->cache_tick = 0;
2760                                 }
2761
2762                         return TRUE;
2763                         }
2764                 }
2765
2766         pan_window_layout_compute(pw, pw->path, &width, &height, &scroll_x, &scroll_y);
2767
2768         pan_window_zoom_limit(pw);
2769
2770         if (width > 0 && height > 0)
2771                 {
2772                 gdouble align;
2773
2774                 image_set_image_as_tiles(pw->imd, width, height,
2775                                          PAN_TILE_SIZE, PAN_TILE_SIZE, 8,
2776                                          pan_window_request_tile_cb,
2777                                          pan_window_dispose_tile_cb, pw, 1.0);
2778
2779                 if (scroll_x == 0 && scroll_y == 0)
2780                         {
2781                         align = 0.0;
2782                         }
2783                 else
2784                         {
2785                         align = 0.5;
2786                         }
2787                 image_scroll_to_point(pw->imd, scroll_x, scroll_y, align, align);
2788                 }
2789
2790         pan_window_message(pw, NULL);
2791
2792         pw->idle_id = -1;
2793
2794         return FALSE;
2795 }
2796
2797 static void pan_window_layout_update_idle(PanWindow *pw)
2798 {
2799         if (pw->idle_id == -1)
2800                 {
2801                 pan_window_message(pw, _("Sorting images..."));
2802                 pw->idle_id = g_idle_add(pan_window_layout_update_idle_cb, pw);
2803                 }
2804 }
2805
2806 /*
2807  *-----------------------------------------------------------------------------
2808  * pan window keyboard
2809  *-----------------------------------------------------------------------------
2810  */
2811
2812 static const gchar *pan_menu_click_path(PanWindow *pw)
2813 {
2814         if (pw->click_pi && pw->click_pi->fd) return pw->click_pi->fd->path;
2815         return NULL;
2816 }
2817
2818 static void pan_window_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
2819 {
2820         PanWindow *pw = data;
2821         ImageWindow *imd;
2822
2823         imd = pan_window_active_image(pw);
2824         gdk_window_get_origin(imd->image->window, x, y);
2825         popup_menu_position_clamp(menu, x, y, 0);
2826 }
2827
2828 static gint pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
2829 {
2830         PanWindow *pw = data;
2831         ImageWindow *imd;
2832         const gchar *path;
2833         gint stop_signal = FALSE;
2834         GtkWidget *menu;
2835         gint x = 0;
2836         gint y = 0;
2837         gint focused;
2838
2839         focused = (pw->fs || GTK_WIDGET_HAS_FOCUS(pw->imd->widget));
2840
2841         imd = pan_window_active_image(pw);
2842         path = pan_menu_click_path(pw);
2843
2844         if (focused)
2845                 {
2846                 switch (event->keyval)
2847                         {
2848                         case GDK_Left: case GDK_KP_Left:
2849                                 x -= 1;
2850                                 stop_signal = TRUE;
2851                                 break;
2852                         case GDK_Right: case GDK_KP_Right:
2853                                 x += 1;
2854                                 stop_signal = TRUE;
2855                                 break;
2856                         case GDK_Up: case GDK_KP_Up:
2857                                 y -= 1;
2858                                 stop_signal = TRUE;
2859                                 break;
2860                         case GDK_Down: case GDK_KP_Down:
2861                                 y += 1;
2862                                 stop_signal = TRUE;
2863                                 break;
2864                         case GDK_Page_Up: case GDK_KP_Page_Up:
2865                                 image_scroll(imd, 0, 0-imd->vis_height / 2);
2866                                 break;
2867                         case GDK_Page_Down: case GDK_KP_Page_Down:
2868                                 image_scroll(imd, 0, imd->vis_height / 2);
2869                                 break;
2870                         case GDK_Home: case GDK_KP_Home:
2871                                 image_scroll(imd, 0-imd->vis_width / 2, 0);
2872                                 break;
2873                         case GDK_End: case GDK_KP_End:
2874                                 image_scroll(imd, imd->vis_width / 2, 0);
2875                                 break;
2876                         }
2877                 }
2878
2879         if (focused && !(event->state & GDK_CONTROL_MASK) )
2880             switch (event->keyval)
2881                 {
2882                 case '+': case '=': case GDK_KP_Add:
2883                         image_zoom_adjust(imd, ZOOM_INCREMENT);
2884                         break;
2885                 case '-': case GDK_KP_Subtract:
2886                         image_zoom_adjust(imd, -ZOOM_INCREMENT);
2887                         break;
2888                 case 'Z': case 'z': case GDK_KP_Divide: case '1':
2889                         image_zoom_set(imd, 1.0);
2890                         break;
2891                 case '2':
2892                         image_zoom_set(imd, 2.0);
2893                         break;
2894                 case '3':
2895                         image_zoom_set(imd, 3.0);
2896                         break;
2897                 case '4':
2898                         image_zoom_set(imd, 4.0);
2899                         break;
2900                 case '7':
2901                         image_zoom_set(imd, -4.0);
2902                         break;
2903                 case '8':
2904                         image_zoom_set(imd, -3.0);
2905                         break;
2906                 case '9':
2907                         image_zoom_set(imd, -2.0);
2908                         break;
2909                 case 'F': case 'f':
2910                 case 'V': case 'v':
2911                         pan_fullscreen_toggle(pw, FALSE);
2912                         stop_signal = TRUE;
2913                         break;
2914                 case 'I': case 'i':
2915                         pan_overlay_toggle(pw);
2916                         break;
2917                 case GDK_Delete: case GDK_KP_Delete:
2918                         break;
2919                 case '/':
2920                         if (!pw->fs)
2921                                 {
2922                                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), TRUE);
2923                                 stop_signal = TRUE;
2924                                 }
2925                         break;
2926                 case GDK_Escape:
2927                         if (pw->fs)
2928                                 {
2929                                 pan_fullscreen_toggle(pw, TRUE);
2930                                 stop_signal = TRUE;
2931                                 }
2932                         else if (GTK_WIDGET_VISIBLE(pw->search_entry))
2933                                 {
2934                                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
2935                                 stop_signal = TRUE;
2936                                 }
2937                         break;
2938                 case GDK_Menu:
2939                 case GDK_F10:
2940                         menu = pan_popup_menu(pw);
2941                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME);
2942                         stop_signal = TRUE;
2943                         break;
2944                 }
2945
2946         if (event->state & GDK_CONTROL_MASK)
2947                 {
2948                 gint n = -1;
2949                 switch (event->keyval)
2950                         {
2951                         case '1':
2952                                 n = 0;
2953                                 break;
2954                         case '2':
2955                                 n = 1;
2956                                 break;
2957                         case '3':
2958                                 n = 2;
2959                                 break;
2960                         case '4':
2961                                 n = 3;
2962                                 break;
2963                         case '5':
2964                                 n = 4;
2965                                 break;
2966                         case '6':
2967                                 n = 5;
2968                                 break;
2969                         case '7':
2970                                 n = 6;
2971                                 break;
2972                         case '8':
2973                                 n = 7;
2974                                 break;
2975                         case '9':
2976                                 n = 8;
2977                                 break;
2978                         case '0':
2979                                 n = 9;
2980                                 break;
2981                         case 'C': case 'c':
2982                                 if (path) file_util_copy(path, NULL, NULL, imd->widget);
2983                                 break;
2984                         case 'M': case 'm':
2985                                 if (path) file_util_move(path, NULL, NULL, imd->widget);
2986                                 break;
2987                         case 'R': case 'r':
2988                                 if (path) file_util_rename(path, NULL, imd->widget);
2989                                 break;
2990                         case 'D': case 'd':
2991                                 if (path) file_util_delete(path, NULL, imd->widget);
2992                                 break;
2993                         case 'P': case 'p':
2994                                 if (path) info_window_new(path, NULL);
2995                                 break;
2996                         case 'W': case 'w':
2997                                 pan_window_close(pw);
2998                                 break;
2999                         }
3000                 if (n != -1 && path)
3001                         {
3002                         pan_fullscreen_toggle(pw, TRUE);
3003                         start_editor_from_file(n, path);
3004                         stop_signal = TRUE;
3005                         }
3006                 }
3007         else if (event->state & GDK_SHIFT_MASK)
3008                 {
3009                 x *= 3;
3010                 y *= 3;
3011                 }
3012         else if (!focused)
3013                 {
3014                 switch (event->keyval)
3015                         {
3016                         case GDK_Escape:
3017                                 if (pw->fs)
3018                                         {
3019                                         pan_fullscreen_toggle(pw, TRUE);
3020                                         stop_signal = TRUE;
3021                                         }
3022                                 else if (GTK_WIDGET_HAS_FOCUS(pw->search_entry))
3023                                         {
3024                                         gtk_widget_grab_focus(pw->imd->widget);
3025                                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
3026                                         stop_signal = TRUE;
3027                                         }
3028                         break;
3029                         default:
3030                                 break;
3031                         }
3032                 }
3033
3034         if (x != 0 || y!= 0)
3035                 {
3036                 keyboard_scroll_calc(&x, &y, event);
3037                 image_scroll(imd, x, y);
3038                 }
3039
3040         return stop_signal;
3041 }
3042
3043 /*
3044  *-----------------------------------------------------------------------------
3045  * info popup
3046  *-----------------------------------------------------------------------------
3047  */
3048
3049 static void pan_info_update(PanWindow *pw, PanItem *pi)
3050 {
3051         PanItem *pbox;
3052         PanItem *plabel;
3053         PanItem *p;
3054         gchar *buf;
3055         gint x1, y1, x2, y2, x3, y3;
3056         gint x, y, w, h;
3057
3058         if (pw->click_pi == pi) return;
3059         if (pi && !pi->fd) pi = NULL;
3060
3061         while ((p = pan_item_find_by_key(pw, ITEM_NONE, "info"))) pan_item_remove(pw, p);
3062         pw->click_pi = pi;
3063
3064         if (!pi) return;
3065
3066         printf("info set to %s\n", pi->fd->path);
3067
3068         pbox = pan_item_new_box(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10,
3069                              PAN_POPUP_BORDER,
3070                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
3071                              PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3072         pan_item_set_key(pbox, "info");
3073
3074         if (pi->type == ITEM_THUMB && pi->pixbuf)
3075                 {
3076                 w = gdk_pixbuf_get_width(pi->pixbuf);
3077                 h = gdk_pixbuf_get_height(pi->pixbuf);
3078
3079                 x1 = pi->x + pi->width - (pi->width - w) / 2 - 8;
3080                 y1 = pi->y + (pi->height - h) / 2 + 8;
3081                 }
3082         else
3083                 {
3084                 x1 = pi->x + pi->width - 8;
3085                 y1 = pi->y + 8;
3086                 }
3087
3088         x2 = pbox->x + 1;
3089         y2 = pbox->y + 36;
3090         x3 = pbox->x + 1;
3091         y3 = pbox->y + 12;
3092         triangle_rect_region(x1, y1, x2, y2, x3, y3,
3093                              &x, &y, &w, &h);
3094
3095         p = pan_item_new_tri(pw, NULL, x, y, w, h,
3096                              x1, y1, x2, y2, x3, y3,
3097                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA);
3098         pan_item_tri_border(p, BORDER_1 | BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3099         pan_item_set_key(p, "info");
3100         pan_item_added(pw, p);
3101
3102         plabel = pan_item_new_text(pw, pbox->x, pbox->y,
3103                                    _("Filename:"), TEXT_ATTR_BOLD,
3104                                    PAN_POPUP_TEXT_COLOR, 255);
3105         pan_item_set_key(plabel, "info");
3106         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3107                               pi->fd->name, TEXT_ATTR_NONE,
3108                               PAN_POPUP_TEXT_COLOR, 255);
3109         pan_item_set_key(p, "info");
3110         pan_item_size_by_item(pbox, p, 0);
3111
3112         plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3113                                    _("Date:"), TEXT_ATTR_BOLD,
3114                                    PAN_POPUP_TEXT_COLOR, 255);
3115         pan_item_set_key(plabel, "info");
3116         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3117                               text_from_time(pi->fd->date), TEXT_ATTR_NONE,
3118                               PAN_POPUP_TEXT_COLOR, 255);
3119         pan_item_set_key(p, "info");
3120         pan_item_size_by_item(pbox, p, 0);
3121
3122         plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3123                                    _("Size:"), TEXT_ATTR_BOLD,
3124                                    PAN_POPUP_TEXT_COLOR, 255);
3125         pan_item_set_key(plabel, "info");
3126         buf = text_from_size(pi->fd->size);
3127         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3128                               buf, TEXT_ATTR_NONE,
3129                               PAN_POPUP_TEXT_COLOR, 255);
3130         g_free(buf);
3131         pan_item_set_key(p, "info");
3132         pan_item_size_by_item(pbox, p, 0);
3133
3134         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
3135         pan_item_added(pw, pbox);
3136 }
3137
3138
3139 /*
3140  *-----------------------------------------------------------------------------
3141  * search
3142  *-----------------------------------------------------------------------------
3143  */
3144
3145 static void pan_search_status(PanWindow *pw, const gchar *text)
3146 {
3147         gtk_label_set_text(GTK_LABEL(pw->search_label), (text) ? text : "");
3148 }
3149
3150 static gint pan_search_by_path(PanWindow *pw, const gchar *path)
3151 {
3152         PanItem *pi;
3153         ItemType type;
3154
3155         type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3156
3157         pi = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
3158         if (!pi) return FALSE;
3159
3160         pan_info_update(pw, pi);
3161         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3162
3163         pan_search_status(pw, (path[0] == '/') ? _("path found") : _("filename found"));
3164
3165         return TRUE;
3166 }
3167
3168 static gint pan_search_by_partial(PanWindow *pw, const gchar *text)
3169 {
3170         PanItem *pi;
3171         ItemType type;
3172
3173         type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3174
3175         pi = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
3176         if (!pi) pi = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
3177         if (!pi)
3178                 {
3179                 gchar *needle;
3180
3181                 needle = g_utf8_strdown(text, -1);
3182                 pi = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
3183                 g_free(needle);
3184                 }
3185         if (!pi) return FALSE;
3186
3187         pan_info_update(pw, pi);
3188         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3189
3190         pan_search_status(pw, _("partial match"));
3191
3192         return TRUE;
3193 }
3194
3195 static gint valid_date_separator(gchar c)
3196 {
3197         return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
3198 }
3199
3200 static PanItem *pan_search_by_date_val(PanWindow *pw, ItemType type, gint year, gint month, gint day)
3201 {
3202         GList *work;
3203
3204         work = g_list_last(pw->list);
3205         while (work)
3206                 {
3207                 PanItem *pi;
3208
3209                 pi = work->data;
3210                 work = work->prev;
3211
3212                 if (pi->fd && (pi->type == type || type == ITEM_NONE))
3213                         {
3214                         struct tm *tl;
3215
3216                         tl = localtime(&pi->fd->date);
3217                         if (tl)
3218                                 {
3219                                 gint match;
3220
3221                                 match = (tl->tm_year == year - 1900);
3222                                 if (match && month >= 0) match = (tl->tm_mon == month - 1);
3223                                 if (match && day > 0) match = (tl->tm_mday == day);
3224
3225                                 if (match) return pi;
3226                                 }
3227                         }
3228                 }
3229
3230         return NULL;
3231 }
3232
3233 static gint pan_search_by_date(PanWindow *pw, const gchar *text)
3234 {
3235         PanItem *pi;
3236         gint year;
3237         gint month = -1;
3238         gint day = -1;
3239         gchar *ptr;
3240         gchar *mptr;
3241         struct tm *lt;
3242         time_t t;
3243         gchar *message;
3244         gchar *buf;
3245         ItemType type;
3246
3247         if (!text) return FALSE;
3248
3249         ptr = (gchar *)text;
3250         while (*ptr != '\0')
3251                 {
3252                 if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
3253                 ptr++;
3254                 }
3255
3256         t = time(NULL);
3257         if (t == -1) return FALSE;
3258         lt = localtime(&t);
3259         if (!lt) return FALSE;
3260
3261         if (valid_date_separator(*text))
3262                 {
3263                 year = -1;
3264                 mptr = (gchar *)text;
3265                 }
3266         else
3267                 {
3268                 year = (gint)strtol(text, &mptr, 10);
3269                 if (mptr == text) return FALSE;
3270                 }
3271
3272         if (*mptr != '\0' && valid_date_separator(*mptr))
3273                 {
3274                 gchar *dptr;
3275
3276                 mptr++;
3277                 month = strtol(mptr, &dptr, 10);
3278                 if (dptr == mptr)
3279                         {
3280                         if (valid_date_separator(*dptr))
3281                                 {
3282                                 month = lt->tm_mon + 1;
3283                                 dptr++;
3284                                 }
3285                         else
3286                                 {
3287                                 month = -1;
3288                                 }
3289                         }
3290                 if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
3291                         {
3292                         gchar *eptr;
3293                         dptr++;
3294                         day = strtol(dptr, &eptr, 10);
3295                         if (dptr == eptr)
3296                                 {
3297                                 day = lt->tm_mday;
3298                                 }
3299                         }
3300                 }
3301
3302         if (year == -1)
3303                 {
3304                 year = lt->tm_year + 1900;
3305                 }
3306         else if (year < 100)
3307                 {
3308                 if (year > 70)
3309                         year+= 1900;
3310                 else
3311                         year+= 2000;
3312                 }
3313
3314         if (year < 1970 ||
3315             month < -1 || month == 0 || month > 12 ||
3316             day < -1 || day == 0 || day > 31) return FALSE;
3317
3318         t = date_to_time(year, month, day);
3319         if (t < 0) return FALSE;
3320
3321         type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3322
3323         pi = pan_search_by_date_val(pw, type, year, month, day);
3324         if (pi)
3325                 {
3326                 pan_info_update(pw, pi);
3327                 image_scroll_to_point(pw->imd,
3328                                       pi->x - PAN_FOLDER_BOX_BORDER * 5 / 2,
3329                                       pi->y, 0.0, 0.5);
3330                 }
3331
3332         if (month > 0)
3333                 {
3334                 buf = date_value_string(t, DATE_LENGTH_MONTH);
3335                 if (day > 0)
3336                         {
3337                         gchar *tmp;
3338                         tmp = buf;
3339                         buf = g_strdup_printf("%d %s", day, tmp);
3340                         g_free(tmp);
3341                         }
3342                 }
3343         else
3344                 {
3345                 buf = date_value_string(t, DATE_LENGTH_YEAR);
3346                 }
3347         message = g_strdup_printf("%s%s%s%s %s",
3348                                   (pi) ? "" : "(", (pi) ? "" : _("no match"), (pi) ? "" : ") " ,
3349                                   _("Date:"), buf);
3350         g_free(buf);
3351         pan_search_status(pw, message);
3352         g_free(message);
3353
3354         return TRUE;
3355 }
3356
3357 static void pan_search_activate_cb(const gchar *text, gpointer data)
3358 {
3359         PanWindow *pw = data;
3360
3361         if (!text) return;
3362
3363         tab_completion_append_to_history(pw->search_entry, text);
3364
3365         if (pan_search_by_path(pw, text)) return;
3366
3367         if (pw->layout == LAYOUT_TIMELINE && pan_search_by_date(pw, text)) return;
3368
3369         if (pan_search_by_partial(pw, text)) return;
3370
3371         pan_search_status(pw, _("no match"));
3372 }
3373
3374 static void pan_search_toggle_cb(GtkWidget *button, gpointer data)
3375 {
3376         PanWindow *pw = data;
3377         gint visible;
3378
3379         visible = GTK_WIDGET_VISIBLE(pw->search_box);
3380         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
3381
3382         if (visible)
3383                 {
3384                 gtk_widget_hide(pw->search_box);
3385                 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
3386                 }
3387         else
3388                 {
3389                 gtk_widget_show(pw->search_box);
3390                 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
3391                 gtk_widget_grab_focus(pw->search_entry);
3392                 }
3393 }
3394
3395
3396 /*
3397  *-----------------------------------------------------------------------------
3398  * view window main routines
3399  *-----------------------------------------------------------------------------
3400  */ 
3401
3402 static void button_cb(ImageWindow *imd, gint button, guint32 time,
3403                       gdouble x, gdouble y, guint state, gpointer data)
3404 {
3405         PanWindow *pw = data;
3406         PanItem *pi = NULL;
3407         GtkWidget *menu;
3408
3409         if (pw->imd->scale)
3410                 {
3411                 pi = pan_item_find_by_coord(pw, (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB,
3412                             (gint)((double)(pw->imd->x_scroll + x - pw->imd->x_offset) / pw->imd->scale),
3413                             (gint)((double)(pw->imd->y_scroll + y - pw->imd->y_offset) / pw->imd->scale));
3414                 }
3415
3416         switch (button)
3417                 {
3418                 case 1:
3419                         pan_info_update(pw, pi);
3420                         break;
3421                 case 2:
3422                         break;
3423                 case 3:
3424                         pan_info_update(pw, pi);
3425                         menu = pan_popup_menu(pw);
3426                         gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, time);
3427                         break;
3428                 default:
3429                         break;
3430                 }
3431 }
3432
3433 static void scroll_cb(ImageWindow *imd, GdkScrollDirection direction, guint32 time,
3434                       gdouble x, gdouble y, guint state, gpointer data)
3435 {
3436 #if 0
3437         PanWindow *pw = data;
3438 #endif
3439
3440         if (state & GDK_CONTROL_MASK)
3441                 {
3442                 switch (direction)
3443                         {
3444                         case GDK_SCROLL_UP:
3445                                 image_zoom_adjust_at_point(imd, ZOOM_INCREMENT, x, y);
3446                                 break;
3447                         case GDK_SCROLL_DOWN:
3448                                 image_zoom_adjust_at_point(imd, -ZOOM_INCREMENT, x, y);
3449                                 break;
3450                         default:
3451                                 break;
3452                         }
3453                 }
3454         else if ( (state & GDK_SHIFT_MASK) != (mousewheel_scrolls))
3455                 {
3456                 switch (direction)
3457                         {
3458                         case GDK_SCROLL_UP:
3459                                 image_scroll(imd, 0, -MOUSEWHEEL_SCROLL_SIZE);
3460                                 break;
3461                         case GDK_SCROLL_DOWN:
3462                                 image_scroll(imd, 0, MOUSEWHEEL_SCROLL_SIZE);
3463                                 break;
3464                         case GDK_SCROLL_LEFT:
3465                                 image_scroll(imd, -MOUSEWHEEL_SCROLL_SIZE, 0);
3466                                 break;
3467                         case GDK_SCROLL_RIGHT:
3468                                 image_scroll(imd, MOUSEWHEEL_SCROLL_SIZE, 0);
3469                                 break;
3470                         default:
3471                                 break;
3472                         }
3473                 }
3474         else
3475                 {
3476                 switch (direction)
3477                         {
3478                         case GDK_SCROLL_UP:
3479                                 break;
3480                         case GDK_SCROLL_DOWN:
3481                                 break;
3482                         default:
3483                                 break;
3484                         }
3485                 }
3486 }
3487
3488 static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd)
3489 {
3490         image_set_button_func(imd, button_cb, pw);
3491         image_set_scroll_func(imd, scroll_cb, pw);
3492 }
3493
3494 static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data)
3495 {
3496         PanWindow *pw = data;
3497
3498         pw->fs = NULL;
3499 }
3500
3501 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off)
3502 {
3503         if (force_off && !pw->fs) return;
3504
3505         if (pw->fs)
3506                 {
3507                 fullscreen_stop(pw->fs);
3508                 pw->imd = pw->imd_normal;
3509                 }
3510         else
3511                 {
3512                 pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw);
3513
3514                 pan_image_set_buttons(pw, pw->fs->imd);
3515                 g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event",
3516                                  G_CALLBACK(pan_window_key_press_cb), pw);
3517
3518                 pw->imd = pw->fs->imd;
3519                 }
3520 }
3521
3522 static void pan_overlay_toggle(PanWindow *pw)
3523 {
3524         ImageWindow *imd;
3525
3526         imd = pan_window_active_image(pw);
3527 #if 0
3528         if (pw->overlay_id == -1)
3529                 {
3530                 pw->overlay_id = image_overlay_info_enable(imd);
3531                 }
3532         else
3533                 {
3534                 image_overlay_info_disable(imd, pw->overlay_id);
3535                 pw->overlay_id = -1;
3536                 }
3537 #endif
3538 }
3539
3540 static void pan_window_image_update_cb(ImageWindow *imd, gpointer data)
3541 {
3542         PanWindow *pw = data;
3543         gchar *text;
3544
3545         text = image_zoom_get_as_text(imd);
3546         gtk_label_set_text(GTK_LABEL(pw->label_zoom), text);
3547         g_free(text);
3548 }
3549
3550 static void pan_window_image_scroll_notify_cb(ImageWindow *imd, gint x, gint y,
3551                                               gint width, gint height, gpointer data)
3552 {
3553         PanWindow *pw = data;
3554         GtkAdjustment *adj;
3555
3556         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h));
3557         adj->page_size = (gdouble)imd->vis_width / imd->scale;
3558         adj->page_increment = adj->page_size / 2.0;
3559         adj->step_increment = 48.0 / imd->scale;
3560         adj->lower = 0.0;
3561         adj->upper = MAX((gdouble)width + adj->page_size, 1.0);
3562         adj->value = (gdouble)x;
3563
3564         pref_signal_block_data(pw->scrollbar_h, pw);
3565         gtk_adjustment_changed(adj);
3566         pref_signal_unblock_data(pw->scrollbar_h, pw);
3567
3568         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v));
3569         adj->page_size = (gdouble)imd->vis_height / imd->scale;
3570         adj->page_increment = adj->page_size / 2.0;
3571         adj->step_increment = 48.0 / imd->scale;
3572         adj->lower = 0.0;
3573         adj->upper = MAX((gdouble)height + adj->page_size, 1.0);
3574         adj->value = (gdouble)y;
3575
3576         pref_signal_block_data(pw->scrollbar_v, pw);
3577         gtk_adjustment_changed(adj);
3578         pref_signal_unblock_data(pw->scrollbar_v, pw);
3579
3580 //      printf("scrolled to %d,%d @ %d x %d\n", x, y, width, height);
3581 }
3582
3583 static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data)
3584 {
3585         PanWindow *pw = data;
3586         gint x;
3587
3588         if (!pw->imd->scale) return;
3589
3590         x = (gint)gtk_range_get_value(range);
3591
3592         image_scroll_to_point(pw->imd, x, (gint)((gdouble)pw->imd->y_scroll / pw->imd->scale), 0.0, 0.0);
3593 }
3594
3595 static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data)
3596 {
3597         PanWindow *pw = data;
3598         gint y;
3599
3600         if (!pw->imd->scale) return;
3601
3602         y = (gint)gtk_range_get_value(range);
3603
3604         image_scroll_to_point(pw->imd, (gint)((gdouble)pw->imd->x_scroll / pw->imd->scale), y, 0.0, 0.0);
3605 }
3606
3607 static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data)
3608 {
3609         PanWindow *pw = data;
3610
3611         pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
3612         pan_window_layout_update_idle(pw);
3613 }
3614
3615 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
3616 {
3617         PanWindow *pw = data;
3618
3619         pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
3620         pan_window_layout_update_idle(pw);
3621 }
3622
3623 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
3624 {
3625         PanWindow *pw = data;
3626         gchar *path;
3627
3628         path = remove_trailing_slash(new_text);
3629         parse_out_relatives(path);
3630
3631         if (!isdir(path))
3632                 {
3633                 warning_dialog(_("Folder not found"),
3634                                _("The entered path is not a folder"),
3635                                GTK_STOCK_DIALOG_WARNING, pw->path_entry);
3636                 return;
3637                 }
3638
3639         tab_completion_append_to_history(pw->path_entry, path);
3640
3641         g_free(pw->path);
3642         pw->path = g_strdup(path);
3643
3644         pan_window_layout_update_idle(pw);
3645 }
3646
3647 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
3648 {
3649         PanWindow *pw = data;
3650         gchar *text;
3651
3652         if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return;
3653
3654         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
3655         pan_window_entry_activate_cb(text, pw);
3656         g_free(text);
3657 }
3658
3659 static void pan_window_close(PanWindow *pw)
3660 {
3661         pan_window_list = g_list_remove(pan_window_list, pw);
3662
3663         if (pw->idle_id != -1)
3664                 {
3665                 g_source_remove(pw->idle_id);
3666                 }
3667
3668         pan_fullscreen_toggle(pw, TRUE);
3669         gtk_widget_destroy(pw->window);
3670
3671         pan_window_items_free(pw);
3672
3673         g_free(pw->path);
3674
3675         g_free(pw);
3676 }
3677
3678 static gint pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
3679 {
3680         PanWindow *pw = data;
3681
3682         pan_window_close(pw);
3683         return TRUE;
3684 }
3685
3686 static void pan_window_new_real(const gchar *path)
3687 {
3688         PanWindow *pw;
3689         GtkWidget *vbox;
3690         GtkWidget *box;
3691         GtkWidget *combo;
3692         GtkWidget *hbox;
3693         GtkWidget *frame;
3694         GtkWidget *table;
3695         GdkGeometry geometry;
3696
3697         pw = g_new0(PanWindow, 1);
3698
3699         pw->path = g_strdup(path);
3700         pw->layout = LAYOUT_TIMELINE;
3701         pw->size = LAYOUT_SIZE_THUMB_NORMAL;
3702         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
3703         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
3704         pw->list = NULL;
3705
3706         pw->fs = NULL;
3707         pw->overlay_id = -1;
3708         pw->idle_id = -1;
3709
3710         pw->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3711
3712         geometry.min_width = 8;
3713         geometry.min_height = 8;
3714         gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE);
3715
3716         gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE);
3717         gtk_window_set_title (GTK_WINDOW(pw->window), "Pan View - GQview");
3718         gtk_window_set_wmclass(GTK_WINDOW(pw->window), "view", "GQview");
3719         gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0);
3720
3721         window_set_icon(pw->window, NULL, NULL);
3722
3723         vbox = gtk_vbox_new(FALSE, 0);
3724         gtk_container_add(GTK_CONTAINER(pw->window), vbox);
3725         gtk_widget_show(vbox);
3726
3727         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
3728
3729         pref_spacer(box, 0);
3730         pref_label_new(box, _("Location:"));
3731         combo = tab_completion_new_with_history(&pw->path_entry, path, "pan_view", -1,
3732                                                 pan_window_entry_activate_cb, pw);
3733         g_signal_connect(G_OBJECT(pw->path_entry->parent), "changed",
3734                          G_CALLBACK(pan_window_entry_change_cb), pw);
3735         gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
3736         gtk_widget_show(combo);
3737
3738         combo = gtk_combo_box_new_text();
3739         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Timeline"));
3740         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders"));
3741         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders (flower)"));
3742         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Grid"));
3743
3744         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout);
3745         g_signal_connect(G_OBJECT(combo), "changed",
3746                          G_CALLBACK(pan_window_layout_change_cb), pw);
3747         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
3748         gtk_widget_show(combo);
3749
3750         combo = gtk_combo_box_new_text();
3751         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Dots"));
3752         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("No Images"));
3753         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Small Thumbnails"));
3754         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Normal Thumbnails"));
3755         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Large Thumbnails"));
3756         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:10 (10%)"));
3757         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:4 (25%)"));
3758         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:3 (33%)"));
3759         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:2 (50%)"));
3760         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:1 (100%)"));
3761
3762         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size);
3763         g_signal_connect(G_OBJECT(combo), "changed",
3764                          G_CALLBACK(pan_window_layout_size_cb), pw);
3765         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
3766         gtk_widget_show(combo);
3767
3768         table = pref_table_new(vbox, 2, 2, FALSE, TRUE);
3769         gtk_table_set_row_spacings(GTK_TABLE(table), 2);
3770         gtk_table_set_col_spacings(GTK_TABLE(table), 2);
3771
3772         pw->imd = image_new(TRUE);
3773         pw->imd_normal = pw->imd;
3774
3775         if (black_window_background) image_background_set_black(pw->imd, TRUE);
3776         image_set_update_func(pw->imd, pan_window_image_update_cb, pw);
3777
3778         image_set_scroll_notify_func(pw->imd, pan_window_image_scroll_notify_cb, pw);
3779
3780 #if 0
3781         gtk_box_pack_start(GTK_BOX(vbox), pw->imd->widget, TRUE, TRUE, 0);
3782 #endif
3783         gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1,
3784                          GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
3785         gtk_widget_show(pw->imd->widget);
3786
3787         pan_window_dnd_init(pw);
3788
3789         pan_image_set_buttons(pw, pw->imd);
3790
3791         pw->scrollbar_h = gtk_hscrollbar_new(NULL);
3792         g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed",
3793                          G_CALLBACK(pan_window_scrollbar_h_value_cb), pw);
3794         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2,
3795                          GTK_FILL | GTK_EXPAND, 0, 0, 0);
3796         gtk_widget_show(pw->scrollbar_h);
3797
3798         pw->scrollbar_v = gtk_vscrollbar_new(NULL);
3799         g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed",
3800                          G_CALLBACK(pan_window_scrollbar_v_value_cb), pw);
3801         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1,
3802                          0, GTK_FILL | GTK_EXPAND, 0, 0);
3803         gtk_widget_show(pw->scrollbar_v);
3804
3805         /* find bar */
3806
3807         pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
3808         gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2);
3809
3810         pref_spacer(pw->search_box, 0);
3811         pref_label_new(pw->search_box, _("Find:"));
3812
3813         hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
3814         gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0);
3815         gtk_widget_show(hbox);
3816
3817         combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1,
3818                                                 pan_search_activate_cb, pw);
3819         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
3820         gtk_widget_show(combo);
3821
3822         pw->search_label = gtk_label_new("");
3823         gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0);
3824         gtk_widget_show(pw->search_label);
3825
3826         /* status bar */
3827
3828         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
3829
3830         frame = gtk_frame_new(NULL);
3831         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
3832         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
3833         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
3834         gtk_widget_show(frame);
3835
3836         hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
3837         gtk_container_add(GTK_CONTAINER(frame), hbox);
3838         gtk_widget_show(hbox);
3839
3840         pref_spacer(hbox, 0);
3841         pw->label_message = pref_label_new(hbox, "");
3842
3843         frame = gtk_frame_new(NULL);
3844         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
3845         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
3846         gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0);
3847         gtk_widget_show(frame);
3848
3849         pw->label_zoom = gtk_label_new("");
3850         gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
3851         gtk_widget_show(pw->label_zoom);
3852
3853         pw->search_button = gtk_toggle_button_new();
3854         gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE);
3855         gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE);
3856         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
3857         gtk_container_add(GTK_CONTAINER(pw->search_button), hbox);
3858         gtk_widget_show(hbox);
3859         pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
3860         gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0);
3861         gtk_widget_show(pw->search_button_arrow);
3862         pref_label_new(hbox, _("Find"));
3863
3864         gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0);
3865         gtk_widget_show(pw->search_button);
3866         g_signal_connect(G_OBJECT(pw->search_button), "clicked",
3867                          G_CALLBACK(pan_search_toggle_cb), pw);
3868
3869         g_signal_connect(G_OBJECT(pw->window), "delete_event",
3870                          G_CALLBACK(pan_window_delete_cb), pw);
3871         g_signal_connect(G_OBJECT(pw->window), "key_press_event",
3872                          G_CALLBACK(pan_window_key_press_cb), pw);
3873
3874         gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
3875
3876         pan_window_layout_update_idle(pw);
3877
3878         gtk_widget_grab_focus(pw->imd->widget);
3879         gtk_widget_show(pw->window);
3880
3881         pan_window_list = g_list_append(pan_window_list, pw);
3882 }
3883
3884 /*
3885  *-----------------------------------------------------------------------------
3886  * peformance warnings
3887  *-----------------------------------------------------------------------------
3888  */
3889
3890 static void pan_warning_ok_cb(GenericDialog *gd, gpointer data)
3891 {
3892         gchar *path = data;
3893
3894         generic_dialog_close(gd);
3895
3896         pan_window_new_real(path);
3897         g_free(path);
3898 }
3899
3900 static void pan_warning_hide_cb(GtkWidget *button, gpointer data)
3901 {
3902         gint hide_dlg;
3903
3904         hide_dlg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
3905         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, hide_dlg);
3906 }
3907
3908 static gint pan_warning(const gchar *path)
3909 {
3910         GenericDialog *gd;
3911         GtkWidget *box;
3912         GtkWidget *group;
3913         GtkWidget *button;
3914         GtkWidget *ct_button;
3915         gint hide_dlg;
3916
3917         if (enable_thumb_caching &&
3918             thumbnail_spec_standard) return FALSE;
3919
3920         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, &hide_dlg)) hide_dlg = FALSE;
3921         if (hide_dlg) return FALSE;
3922
3923         gd = generic_dialog_new(_("Pan View Performance"), "GQview", "pan_view_warning", NULL, FALSE,
3924                                 NULL, NULL);
3925         gd->data = g_strdup(path);
3926         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
3927                                   pan_warning_ok_cb, TRUE);
3928
3929         box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO,
3930                                          _("Pan view performance may be poor."),
3931                                          _("To improve performance of thumbnails in the pan view the"
3932                                            " following options can be enabled. Note that both options"
3933                                            " must be enabled to notice a change in performance."));
3934
3935         group = pref_box_new(box, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
3936         pref_spacer(group, PREF_PAD_INDENT);
3937         group = pref_box_new(group, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
3938
3939         ct_button = pref_checkbox_new_int(group, _("Cache thumbnails"),
3940                                           enable_thumb_caching, &enable_thumb_caching);
3941         button = pref_checkbox_new_int(group, _("Use shared thumbnail cache"),
3942                                        thumbnail_spec_standard, &thumbnail_spec_standard);
3943         pref_checkbox_link_sensitivity(ct_button, button);
3944
3945         pref_line(box, 0);
3946
3947         pref_checkbox_new(box, _("Do not show this dialog again"), hide_dlg,
3948                           G_CALLBACK(pan_warning_hide_cb), NULL);
3949
3950         gtk_widget_show(gd->dialog);
3951
3952         return TRUE;
3953 }
3954
3955
3956 /*
3957  *-----------------------------------------------------------------------------
3958  * public
3959  *-----------------------------------------------------------------------------
3960  */
3961
3962 void pan_window_new(const gchar *path)
3963 {
3964         if (pan_warning(path)) return;
3965
3966         pan_window_new_real(path);
3967 }
3968
3969 /*
3970  *-----------------------------------------------------------------------------
3971  * menus
3972  *-----------------------------------------------------------------------------
3973  */
3974
3975 static void pan_new_window_cb(GtkWidget *widget, gpointer data)
3976 {
3977         PanWindow *pw = data;
3978         const gchar *path;
3979
3980         path = pan_menu_click_path(pw);
3981         if (path)
3982                 {
3983                 pan_fullscreen_toggle(pw, TRUE);
3984                 view_window_new(path);
3985                 }
3986 }
3987
3988 static void pan_edit_cb(GtkWidget *widget, gpointer data)
3989 {
3990         PanWindow *pw;
3991         const gchar *path;
3992         gint n;
3993
3994         pw = submenu_item_get_data(widget);
3995         n = GPOINTER_TO_INT(data);
3996         if (!pw) return;
3997
3998         path = pan_menu_click_path(pw);
3999         if (path)
4000                 {
4001                 pan_fullscreen_toggle(pw, TRUE);
4002                 start_editor_from_file(n, path);
4003                 }
4004 }
4005
4006 static void pan_info_cb(GtkWidget *widget, gpointer data)
4007 {
4008         PanWindow *pw = data;
4009         const gchar *path;
4010
4011         path = pan_menu_click_path(pw);
4012         if (path) info_window_new(path, NULL);
4013 }
4014
4015 static void pan_zoom_in_cb(GtkWidget *widget, gpointer data)
4016 {
4017         PanWindow *pw = data;
4018
4019         image_zoom_adjust(pan_window_active_image(pw), ZOOM_INCREMENT);
4020 }
4021
4022 static void pan_zoom_out_cb(GtkWidget *widget, gpointer data)
4023 {
4024         PanWindow *pw = data;
4025
4026         image_zoom_adjust(pan_window_active_image(pw), -ZOOM_INCREMENT);
4027 }
4028
4029 static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data)
4030 {
4031         PanWindow *pw = data;
4032
4033         image_zoom_set(pan_window_active_image(pw), 1.0);
4034 }
4035
4036 static void pan_copy_cb(GtkWidget *widget, gpointer data)
4037 {
4038         PanWindow *pw = data;
4039         const gchar *path;
4040
4041         path = pan_menu_click_path(pw);
4042         if (path) file_util_copy(path, NULL, NULL, pw->imd->widget);
4043 }
4044
4045 static void pan_move_cb(GtkWidget *widget, gpointer data)
4046 {
4047         PanWindow *pw = data;
4048         const gchar *path;
4049
4050         path = pan_menu_click_path(pw);
4051         if (path) file_util_move(path, NULL, NULL, pw->imd->widget);
4052 }
4053
4054 static void pan_rename_cb(GtkWidget *widget, gpointer data)
4055 {
4056         PanWindow *pw = data;
4057         const gchar *path;
4058
4059         path = pan_menu_click_path(pw);
4060         if (path) file_util_rename(path, NULL, pw->imd->widget);
4061 }
4062
4063 static void pan_delete_cb(GtkWidget *widget, gpointer data)
4064 {
4065         PanWindow *pw = data;
4066         const gchar *path;
4067
4068         path = pan_menu_click_path(pw);
4069         if (path) file_util_delete(path, NULL, pw->imd->widget);
4070 }
4071
4072 static void pan_fullscreen_cb(GtkWidget *widget, gpointer data)
4073 {
4074         PanWindow *pw = data;
4075
4076         pan_fullscreen_toggle(pw, FALSE);
4077 }
4078
4079 static void pan_close_cb(GtkWidget *widget, gpointer data)
4080 {
4081         PanWindow *pw = data;
4082
4083         pan_window_close(pw);
4084 }
4085
4086 static GtkWidget *pan_popup_menu(PanWindow *pw)
4087 {
4088         GtkWidget *menu;
4089         GtkWidget *item;
4090         gint active;
4091
4092         active = (pw->click_pi != NULL);
4093
4094         menu = popup_menu_short_lived();
4095
4096         menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
4097                             G_CALLBACK(pan_zoom_in_cb), pw);
4098         menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
4099                             G_CALLBACK(pan_zoom_out_cb), pw);
4100         menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100,
4101                             G_CALLBACK(pan_zoom_1_1_cb), pw);
4102         menu_item_add_divider(menu);
4103
4104         submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw);
4105         gtk_widget_set_sensitive(item, active);
4106
4107         menu_item_add_stock_sensitive(menu, _("_Properties"), GTK_STOCK_PROPERTIES, active,
4108                                       G_CALLBACK(pan_info_cb), pw);
4109
4110         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
4111                                       G_CALLBACK(pan_new_window_cb), pw);
4112
4113         menu_item_add_divider(menu);
4114         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
4115                                       G_CALLBACK(pan_copy_cb), pw);
4116         menu_item_add_sensitive(menu, _("_Move..."), active,
4117                                 G_CALLBACK(pan_move_cb), pw);
4118         menu_item_add_sensitive(menu, _("_Rename..."), active,
4119                                 G_CALLBACK(pan_rename_cb), pw);
4120         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
4121                                       G_CALLBACK(pan_delete_cb), pw);
4122
4123         menu_item_add_divider(menu);
4124
4125         if (pw->fs)
4126                 {
4127                 menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4128                 }
4129         else
4130                 {
4131                 menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4132                 }
4133
4134         menu_item_add_divider(menu);
4135         menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw);
4136
4137         return menu;
4138 }
4139
4140 /*
4141  *-----------------------------------------------------------------------------
4142  * drag and drop
4143  *-----------------------------------------------------------------------------
4144  */
4145
4146 static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context,
4147                                     gint x, gint y,
4148                                     GtkSelectionData *selection_data, guint info,
4149                                     guint time, gpointer data)
4150 {
4151         PanWindow *pw = data;
4152         ImageWindow *imd;
4153
4154         if (gtk_drag_get_source_widget(context) == pw->imd->image) return;
4155
4156         imd = pw->imd;
4157
4158         if (info == TARGET_URI_LIST)
4159                 {
4160                 GList *list;
4161
4162                 list = uri_list_from_text(selection_data->data, TRUE);
4163                 if (list && isdir((gchar *)list->data))
4164                         {
4165                         gchar *path = list->data;
4166
4167                         g_free(pw->path);
4168                         pw->path = g_strdup(path);
4169
4170                         pan_window_layout_update_idle(pw);
4171                         }
4172
4173                 path_list_free(list);
4174                 }
4175 }
4176
4177 static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context,
4178                                     GtkSelectionData *selection_data, guint info,
4179                                     guint time, gpointer data)
4180 {
4181         PanWindow *pw = data;
4182         const gchar *path;
4183
4184         path = pan_menu_click_path(pw);
4185         if (path)
4186                 {
4187                 gchar *text = NULL;
4188                 gint len;
4189                 gint plain_text;
4190                 GList *list;
4191
4192                 switch (info)
4193                         {
4194                         case TARGET_URI_LIST:
4195                                 plain_text = FALSE;
4196                                 break;
4197                         case TARGET_TEXT_PLAIN:
4198                         default:
4199                                 plain_text = TRUE;
4200                                 break;
4201                         }
4202                 list = g_list_append(NULL, (gchar *)path);
4203                 text = uri_text_from_list(list, &len, plain_text);
4204                 g_list_free(list);
4205                 if (text)
4206                         {
4207                         gtk_selection_data_set (selection_data, selection_data->target,
4208                                                 8, text, len);
4209                         g_free(text);
4210                         }
4211                 }
4212         else
4213                 {
4214                 gtk_selection_data_set (selection_data, selection_data->target,
4215                                         8, NULL, 0);
4216                 }
4217 }
4218
4219 static void pan_window_dnd_init(PanWindow *pw)
4220 {
4221         ImageWindow *imd;
4222
4223         imd = pw->imd;
4224
4225         gtk_drag_source_set(imd->image, GDK_BUTTON2_MASK,
4226                             dnd_file_drag_types, dnd_file_drag_types_count,
4227                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4228         g_signal_connect(G_OBJECT(imd->image), "drag_data_get",
4229                          G_CALLBACK(pan_window_set_dnd_data), pw);
4230
4231         gtk_drag_dest_set(imd->image,
4232                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
4233                           dnd_file_drop_types, dnd_file_drop_types_count,
4234                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4235         g_signal_connect(G_OBJECT(imd->image), "drag_data_received",
4236                          G_CALLBACK(pan_window_get_dnd_data), pw);
4237 }
4238
4239 /*
4240  *-----------------------------------------------------------------------------
4241  * maintenance (for rename, move, remove)
4242  *-----------------------------------------------------------------------------
4243  */
4244