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