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