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