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