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