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