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