Sun Oct 15 12:36:06 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                 switch (event->keyval)
3417                         {
3418                         case GDK_Left: case GDK_KP_Left:
3419                                 x -= 1;
3420                                 stop_signal = TRUE;
3421                                 break;
3422                         case GDK_Right: case GDK_KP_Right:
3423                                 x += 1;
3424                                 stop_signal = TRUE;
3425                                 break;
3426                         case GDK_Up: case GDK_KP_Up:
3427                                 y -= 1;
3428                                 stop_signal = TRUE;
3429                                 break;
3430                         case GDK_Down: case GDK_KP_Down:
3431                                 y += 1;
3432                                 stop_signal = TRUE;
3433                                 break;
3434                         case GDK_Page_Up: case GDK_KP_Page_Up:
3435                                 pixbuf_renderer_scroll(pr, 0, 0 - pr->vis_height / 2);
3436                                 break;
3437                         case GDK_Page_Down: case GDK_KP_Page_Down:
3438                                 pixbuf_renderer_scroll(pr, 0, pr->vis_height / 2);
3439                                 break;
3440                         case GDK_Home: case GDK_KP_Home:
3441                                 pixbuf_renderer_scroll(pr, 0 - pr->vis_width / 2, 0);
3442                                 break;
3443                         case GDK_End: case GDK_KP_End:
3444                                 pixbuf_renderer_scroll(pr, pr->vis_width / 2, 0);
3445                                 break;
3446                         }
3447                 }
3448
3449         if (focused && !(event->state & GDK_CONTROL_MASK) )
3450             switch (event->keyval)
3451                 {
3452                 case '+': case '=': case GDK_KP_Add:
3453                         pixbuf_renderer_zoom_adjust(pr, ZOOM_INCREMENT);
3454                         break;
3455                 case '-': case GDK_KP_Subtract:
3456                         pixbuf_renderer_zoom_adjust(pr, -ZOOM_INCREMENT);
3457                         break;
3458                 case 'Z': case 'z': case GDK_KP_Divide: case '1':
3459                         pixbuf_renderer_zoom_set(pr, 1.0);
3460                         break;
3461                 case '2':
3462                         pixbuf_renderer_zoom_set(pr, 2.0);
3463                         break;
3464                 case '3':
3465                         pixbuf_renderer_zoom_set(pr, 3.0);
3466                         break;
3467                 case '4':
3468                         pixbuf_renderer_zoom_set(pr, 4.0);
3469                         break;
3470                 case '7':
3471                         pixbuf_renderer_zoom_set(pr, -4.0);
3472                         break;
3473                 case '8':
3474                         pixbuf_renderer_zoom_set(pr, -3.0);
3475                         break;
3476                 case '9':
3477                         pixbuf_renderer_zoom_set(pr, -2.0);
3478                         break;
3479                 case 'F': case 'f':
3480                 case 'V': case 'v':
3481                         pan_fullscreen_toggle(pw, FALSE);
3482                         stop_signal = TRUE;
3483                         break;
3484                 case 'I': case 'i':
3485 #if 0
3486                         pan_overlay_toggle(pw);
3487 #endif
3488                         break;
3489                 case GDK_Delete: case GDK_KP_Delete:
3490                         break;
3491                 case '/':
3492                         if (!pw->fs)
3493                                 {
3494                                 if (GTK_WIDGET_VISIBLE(pw->search_box))
3495                                         {
3496                                         gtk_widget_grab_focus(pw->search_entry);
3497                                         }
3498                                 else
3499                                         {
3500                                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), TRUE);
3501                                         }
3502                                 stop_signal = TRUE;
3503                                 }
3504                         break;
3505                 case GDK_Escape:
3506                         if (pw->fs)
3507                                 {
3508                                 pan_fullscreen_toggle(pw, TRUE);
3509                                 stop_signal = TRUE;
3510                                 }
3511                         else if (GTK_WIDGET_VISIBLE(pw->search_entry))
3512                                 {
3513                                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
3514                                 stop_signal = TRUE;
3515                                 }
3516                         break;
3517                 case GDK_Menu:
3518                 case GDK_F10:
3519                         menu = pan_popup_menu(pw);
3520                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME);
3521                         stop_signal = TRUE;
3522                         break;
3523                 }
3524
3525         if (event->state & GDK_CONTROL_MASK)
3526                 {
3527                 gint n = -1;
3528                 switch (event->keyval)
3529                         {
3530                         case '1':
3531                                 n = 0;
3532                                 break;
3533                         case '2':
3534                                 n = 1;
3535                                 break;
3536                         case '3':
3537                                 n = 2;
3538                                 break;
3539                         case '4':
3540                                 n = 3;
3541                                 break;
3542                         case '5':
3543                                 n = 4;
3544                                 break;
3545                         case '6':
3546                                 n = 5;
3547                                 break;
3548                         case '7':
3549                                 n = 6;
3550                                 break;
3551                         case '8':
3552                                 n = 7;
3553                                 break;
3554                         case '9':
3555                                 n = 8;
3556                                 break;
3557                         case '0':
3558                                 n = 9;
3559                                 break;
3560                         case 'C': case 'c':
3561                                 if (path) file_util_copy(path, NULL, NULL, GTK_WIDGET(pr));
3562                                 break;
3563                         case 'M': case 'm':
3564                                 if (path) file_util_move(path, NULL, NULL, GTK_WIDGET(pr));
3565                                 break;
3566                         case 'R': case 'r':
3567                                 if (path) file_util_rename(path, NULL, GTK_WIDGET(pr));
3568                                 break;
3569                         case 'D': case 'd':
3570                                 if (path) file_util_delete(path, NULL, GTK_WIDGET(pr));
3571                                 break;
3572                         case 'P': case 'p':
3573                                 if (path) info_window_new(path, NULL);
3574                                 break;
3575                         case 'W': case 'w':
3576                                 pan_window_close(pw);
3577                                 break;
3578                         }
3579                 if (n != -1 && path)
3580                         {
3581                         if (!editor_window_flag_set(n))
3582                                 {
3583                                 pan_fullscreen_toggle(pw, TRUE);
3584                                 }
3585                         start_editor_from_file(n, path);
3586                         stop_signal = TRUE;
3587                         }
3588                 }
3589         else if (event->state & GDK_SHIFT_MASK)
3590                 {
3591                 x *= 3;
3592                 y *= 3;
3593                 }
3594         else if (!focused)
3595                 {
3596                 switch (event->keyval)
3597                         {
3598                         case GDK_Escape:
3599                                 if (pw->fs)
3600                                         {
3601                                         pan_fullscreen_toggle(pw, TRUE);
3602                                         stop_signal = TRUE;
3603                                         }
3604                                 else if (GTK_WIDGET_HAS_FOCUS(pw->search_entry))
3605                                         {
3606                                         gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
3607                                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
3608                                         stop_signal = TRUE;
3609                                         }
3610                         break;
3611                         default:
3612                                 break;
3613                         }
3614                 }
3615
3616         if (x != 0 || y!= 0)
3617                 {
3618                 keyboard_scroll_calc(&x, &y, event);
3619                 pixbuf_renderer_scroll(pr, x, y);
3620                 }
3621
3622         return stop_signal;
3623 }
3624
3625 /*
3626  *-----------------------------------------------------------------------------
3627  * info popup
3628  *-----------------------------------------------------------------------------
3629  */
3630
3631 static void pan_info_update(PanWindow *pw, PanItem *pi)
3632 {
3633         PanItem *pbox;
3634         PanItem *plabel;
3635         PanItem *p;
3636         gchar *buf;
3637         gint x1, y1, x2, y2, x3, y3;
3638         gint x, y, w, h;
3639
3640         if (pw->click_pi == pi) return;
3641         if (pi && !pi->fd) pi = NULL;
3642
3643         while ((p = pan_item_find_by_key(pw, ITEM_NONE, "info"))) pan_item_remove(pw, p);
3644         pw->click_pi = pi;
3645
3646         if (!pi) return;
3647
3648         if (debug) printf("info set to %s\n", pi->fd->path);
3649
3650         pbox = pan_item_new_box(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10,
3651                              PAN_POPUP_BORDER,
3652                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
3653                              PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3654         pan_item_set_key(pbox, "info");
3655
3656         if (pi->type == ITEM_THUMB && pi->pixbuf)
3657                 {
3658                 w = gdk_pixbuf_get_width(pi->pixbuf);
3659                 h = gdk_pixbuf_get_height(pi->pixbuf);
3660
3661                 x1 = pi->x + pi->width - (pi->width - w) / 2 - 8;
3662                 y1 = pi->y + (pi->height - h) / 2 + 8;
3663                 }
3664         else
3665                 {
3666                 x1 = pi->x + pi->width - 8;
3667                 y1 = pi->y + 8;
3668                 }
3669
3670         x2 = pbox->x + 1;
3671         y2 = pbox->y + 36;
3672         x3 = pbox->x + 1;
3673         y3 = pbox->y + 12;
3674         util_clip_triangle(x1, y1, x2, y2, x3, y3,
3675                            &x, &y, &w, &h);
3676
3677         p = pan_item_new_tri(pw, NULL, x, y, w, h,
3678                              x1, y1, x2, y2, x3, y3,
3679                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA);
3680         pan_item_tri_border(p, BORDER_1 | BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3681         pan_item_set_key(p, "info");
3682         pan_item_added(pw, p);
3683
3684         plabel = pan_item_new_text(pw, pbox->x, pbox->y,
3685                                    _("Filename:"), TEXT_ATTR_BOLD,
3686                                    PAN_POPUP_TEXT_COLOR, 255);
3687         pan_item_set_key(plabel, "info");
3688         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3689                               pi->fd->name, TEXT_ATTR_NONE,
3690                               PAN_POPUP_TEXT_COLOR, 255);
3691         pan_item_set_key(p, "info");
3692         pan_item_size_by_item(pbox, p, 0);
3693
3694         plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3695                                    _("Date:"), TEXT_ATTR_BOLD,
3696                                    PAN_POPUP_TEXT_COLOR, 255);
3697         pan_item_set_key(plabel, "info");
3698         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3699                               text_from_time(pi->fd->date), TEXT_ATTR_NONE,
3700                               PAN_POPUP_TEXT_COLOR, 255);
3701         pan_item_set_key(p, "info");
3702         pan_item_size_by_item(pbox, p, 0);
3703
3704         plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3705                                    _("Size:"), TEXT_ATTR_BOLD,
3706                                    PAN_POPUP_TEXT_COLOR, 255);
3707         pan_item_set_key(plabel, "info");
3708         buf = text_from_size(pi->fd->size);
3709         p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3710                               buf, TEXT_ATTR_NONE,
3711                               PAN_POPUP_TEXT_COLOR, 255);
3712         g_free(buf);
3713         pan_item_set_key(p, "info");
3714         pan_item_size_by_item(pbox, p, 0);
3715
3716         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
3717         pan_item_added(pw, pbox);
3718
3719         if (TRUE)
3720                 {
3721                 gint iw, ih;
3722                 if (image_load_dimensions(pi->fd->path, &iw, &ih))
3723                         {
3724                         pbox = pan_item_new_box(pw, NULL, pbox->x, pbox->y + pbox->height + 8, 10, 10,
3725                                                 PAN_POPUP_BORDER,
3726                                                 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
3727                                                 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3728                         pan_item_set_key(pbox, "info");
3729
3730                         p = pan_item_new_image(pw, file_data_new_simple(pi->fd->path),
3731                                                pbox->x + 8, pbox->y + 8, iw, ih);
3732                         pan_item_set_key(p, "info");
3733                         pan_item_size_by_item(pbox, p, 8);
3734
3735                         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
3736                         pan_item_added(pw, pbox);
3737
3738                         pan_layout_resize(pw);
3739                         }
3740                 }
3741 }
3742
3743
3744 /*
3745  *-----------------------------------------------------------------------------
3746  * search
3747  *-----------------------------------------------------------------------------
3748  */
3749
3750 static void pan_search_status(PanWindow *pw, const gchar *text)
3751 {
3752         gtk_label_set_text(GTK_LABEL(pw->search_label), (text) ? text : "");
3753 }
3754
3755 static gint pan_search_by_path(PanWindow *pw, const gchar *path)
3756 {
3757         PanItem *pi;
3758         GList *list;
3759         GList *found;
3760         ItemType type;
3761         gchar *buf;
3762
3763         type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3764
3765         list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
3766         if (!list) return FALSE;
3767
3768         found = g_list_find(list, pw->click_pi);
3769         if (found && found->next)
3770                 {
3771                 found = found->next;
3772                 pi = found->data;
3773                 }
3774         else
3775                 {
3776                 pi = list->data;
3777                 }
3778
3779         pan_info_update(pw, pi);
3780         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3781
3782         buf = g_strdup_printf("%s ( %d / %d )",
3783                               (path[0] == '/') ? _("path found") : _("filename found"),
3784                               g_list_index(list, pi) + 1,
3785                               g_list_length(list));
3786         pan_search_status(pw, buf);
3787         g_free(buf);
3788
3789         g_list_free(list);
3790
3791         return TRUE;
3792 }
3793
3794 static gint pan_search_by_partial(PanWindow *pw, const gchar *text)
3795 {
3796         PanItem *pi;
3797         GList *list;
3798         GList *found;
3799         ItemType type;
3800         gchar *buf;
3801
3802         type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3803
3804         list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
3805         if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
3806         if (!list)
3807                 {
3808                 gchar *needle;
3809
3810                 needle = g_utf8_strdown(text, -1);
3811                 list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
3812                 g_free(needle);
3813                 }
3814         if (!list) return FALSE;
3815
3816         found = g_list_find(list, pw->click_pi);
3817         if (found && found->next)
3818                 {
3819                 found = found->next;
3820                 pi = found->data;
3821                 }
3822         else
3823                 {
3824                 pi = list->data;
3825                 }
3826
3827         pan_info_update(pw, pi);
3828         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3829
3830         buf = g_strdup_printf("%s ( %d / %d )",
3831                               _("partial match"),
3832                               g_list_index(list, pi) + 1,
3833                               g_list_length(list));
3834         pan_search_status(pw, buf);
3835         g_free(buf);
3836
3837         g_list_free(list);
3838
3839         return TRUE;
3840 }
3841
3842 static gint valid_date_separator(gchar c)
3843 {
3844         return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
3845 }
3846
3847 static GList *pan_search_by_date_val(PanWindow *pw, ItemType type,
3848                                      gint year, gint month, gint day,
3849                                      const gchar *key)
3850 {
3851         GList *list = NULL;
3852         GList *work;
3853
3854         work = g_list_last(pw->list_static);
3855         while (work)
3856                 {
3857                 PanItem *pi;
3858
3859                 pi = work->data;
3860                 work = work->prev;
3861
3862                 if (pi->fd && (pi->type == type || type == ITEM_NONE) &&
3863                     ((!key && !pi->key) || (key && pi->key && strcmp(key, pi->key) == 0)))
3864                         {
3865                         struct tm *tl;
3866
3867                         tl = localtime(&pi->fd->date);
3868                         if (tl)
3869                                 {
3870                                 gint match;
3871
3872                                 match = (tl->tm_year == year - 1900);
3873                                 if (match && month >= 0) match = (tl->tm_mon == month - 1);
3874                                 if (match && day > 0) match = (tl->tm_mday == day);
3875
3876                                 if (match) list = g_list_prepend(list, pi);
3877                                 }
3878                         }
3879                 }
3880
3881         return g_list_reverse(list);
3882 }
3883
3884 static gint pan_search_by_date(PanWindow *pw, const gchar *text)
3885 {
3886         PanItem *pi = NULL;
3887         GList *list = NULL;
3888         GList *found;
3889         gint year;
3890         gint month = -1;
3891         gint day = -1;
3892         gchar *ptr;
3893         gchar *mptr;
3894         struct tm *lt;
3895         time_t t;
3896         gchar *message;
3897         gchar *buf;
3898         gchar *buf_count;
3899
3900         if (!text) return FALSE;
3901
3902         ptr = (gchar *)text;
3903         while (*ptr != '\0')
3904                 {
3905                 if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
3906                 ptr++;
3907                 }
3908
3909         t = time(NULL);
3910         if (t == -1) return FALSE;
3911         lt = localtime(&t);
3912         if (!lt) return FALSE;
3913
3914         if (valid_date_separator(*text))
3915                 {
3916                 year = -1;
3917                 mptr = (gchar *)text;
3918                 }
3919         else
3920                 {
3921                 year = (gint)strtol(text, &mptr, 10);
3922                 if (mptr == text) return FALSE;
3923                 }
3924
3925         if (*mptr != '\0' && valid_date_separator(*mptr))
3926                 {
3927                 gchar *dptr;
3928
3929                 mptr++;
3930                 month = strtol(mptr, &dptr, 10);
3931                 if (dptr == mptr)
3932                         {
3933                         if (valid_date_separator(*dptr))
3934                                 {
3935                                 month = lt->tm_mon + 1;
3936                                 dptr++;
3937                                 }
3938                         else
3939                                 {
3940                                 month = -1;
3941                                 }
3942                         }
3943                 if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
3944                         {
3945                         gchar *eptr;
3946                         dptr++;
3947                         day = strtol(dptr, &eptr, 10);
3948                         if (dptr == eptr)
3949                                 {
3950                                 day = lt->tm_mday;
3951                                 }
3952                         }
3953                 }
3954
3955         if (year == -1)
3956                 {
3957                 year = lt->tm_year + 1900;
3958                 }
3959         else if (year < 100)
3960                 {
3961                 if (year > 70)
3962                         year+= 1900;
3963                 else
3964                         year+= 2000;
3965                 }
3966
3967         if (year < 1970 ||
3968             month < -1 || month == 0 || month > 12 ||
3969             day < -1 || day == 0 || day > 31) return FALSE;
3970
3971         t = date_to_time(year, month, day);
3972         if (t < 0) return FALSE;
3973
3974         if (pw->layout == LAYOUT_CALENDAR)
3975                 {
3976                 list = pan_search_by_date_val(pw, ITEM_BOX, year, month, day, "day");
3977                 }
3978         else
3979                 {
3980                 ItemType type;
3981
3982                 type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3983                 list = pan_search_by_date_val(pw, type, year, month, day, NULL);
3984                 }
3985
3986         if (list)
3987                 {
3988                 found = g_list_find(list, pw->search_pi);
3989                 if (found && found->next)
3990                         {
3991                         found = found->next;
3992                         pi = found->data;
3993                         }
3994                 else
3995                         {
3996                         pi = list->data;
3997                         }
3998                 }
3999
4000         pw->search_pi = pi;
4001
4002         if (pw->layout == LAYOUT_CALENDAR && pi && pi->type == ITEM_BOX)
4003                 {
4004                 pan_info_update(pw, NULL);
4005                 pan_calendar_update(pw, pi);
4006                 image_scroll_to_point(pw->imd,
4007                                       pi->x + pi->width / 2,
4008                                       pi->y + pi->height / 2, 0.5, 0.5);
4009                 }
4010         else if (pi)
4011                 {
4012                 pan_info_update(pw, pi);
4013                 image_scroll_to_point(pw->imd,
4014                                       pi->x - PAN_FOLDER_BOX_BORDER * 5 / 2,
4015                                       pi->y, 0.0, 0.5);
4016                 }
4017
4018         if (month > 0)
4019                 {
4020                 buf = date_value_string(t, DATE_LENGTH_MONTH);
4021                 if (day > 0)
4022                         {
4023                         gchar *tmp;
4024                         tmp = buf;
4025                         buf = g_strdup_printf("%d %s", day, tmp);
4026                         g_free(tmp);
4027                         }
4028                 }
4029         else
4030                 {
4031                 buf = date_value_string(t, DATE_LENGTH_YEAR);
4032                 }
4033
4034         if (pi)
4035                 {
4036                 buf_count = g_strdup_printf("( %d / %d )",
4037                                             g_list_index(list, pi) + 1,
4038                                             g_list_length(list));
4039                 }
4040         else
4041                 {
4042                 buf_count = g_strdup_printf("(%s)", _("no match"));
4043                 }
4044
4045         message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
4046         g_free(buf);
4047         g_free(buf_count);
4048         pan_search_status(pw, message);
4049         g_free(message);
4050
4051         g_list_free(list);
4052
4053         return TRUE;
4054 }
4055
4056 static void pan_search_activate_cb(const gchar *text, gpointer data)
4057 {
4058         PanWindow *pw = data;
4059
4060         if (!text) return;
4061
4062         tab_completion_append_to_history(pw->search_entry, text);
4063
4064         if (pan_search_by_path(pw, text)) return;
4065
4066         if ((pw->layout == LAYOUT_TIMELINE ||
4067              pw->layout == LAYOUT_CALENDAR) &&
4068             pan_search_by_date(pw, text))
4069                 {
4070                 return;
4071                 }
4072
4073         if (pan_search_by_partial(pw, text)) return;
4074
4075         pan_search_status(pw, _("no match"));
4076 }
4077
4078 static void pan_search_toggle_cb(GtkWidget *button, gpointer data)
4079 {
4080         PanWindow *pw = data;
4081         gint visible;
4082
4083         visible = GTK_WIDGET_VISIBLE(pw->search_box);
4084         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
4085
4086         if (visible)
4087                 {
4088                 gtk_widget_hide(pw->search_box);
4089                 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
4090                 }
4091         else
4092                 {
4093                 gtk_widget_show(pw->search_box);
4094                 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
4095                 gtk_widget_grab_focus(pw->search_entry);
4096                 }
4097 }
4098
4099
4100 /*
4101  *-----------------------------------------------------------------------------
4102  * view window main routines
4103  *-----------------------------------------------------------------------------
4104  */ 
4105
4106 static void button_cb(PixbufRenderer *pr, GdkEventButton *event, gpointer data)
4107 {
4108         PanWindow *pw = data;
4109         PanItem *pi = NULL;
4110         GtkWidget *menu;
4111         gint rx, ry;
4112
4113         rx = ry = 0;
4114         if (pr->scale)
4115                 {
4116                 rx = (double)(pr->x_scroll + event->x - pr->x_offset) / pr->scale;
4117                 ry = (double)(pr->y_scroll + event->y - pr->y_offset) / pr->scale;
4118                 }
4119
4120         pi = pan_item_find_by_coord(pw, ITEM_BOX, rx, ry, "info");
4121         if (pi && event->button == 1)
4122                 {
4123                 pan_info_update(pw, NULL);
4124                 return;
4125                 }
4126
4127         pi = pan_item_find_by_coord(pw, (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB,
4128                                     rx, ry, NULL);
4129
4130         switch (event->button)
4131                 {
4132                 case 1:
4133                         pan_info_update(pw, pi);
4134
4135                         if (!pi && pw->layout == LAYOUT_CALENDAR)
4136                                 {
4137                                 pi = pan_item_find_by_coord(pw, ITEM_BOX, rx, ry, "day");
4138                                 pan_calendar_update(pw, pi);
4139                                 }
4140                         break;
4141                 case 2:
4142                         break;
4143                 case 3:
4144                         pan_info_update(pw, pi);
4145                         menu = pan_popup_menu(pw);
4146                         gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
4147                         break;
4148                 default:
4149                         break;
4150                 }
4151 }
4152
4153 static void scroll_cb(PixbufRenderer *pr, GdkEventScroll *event, gpointer data)
4154 {
4155 #if 0
4156         PanWindow *pw = data;
4157 #endif
4158         gint w, h;
4159
4160         w = pr->vis_width;
4161         h = pr->vis_height;
4162
4163         if (!(event->state & GDK_SHIFT_MASK))
4164                 {
4165                 w /= 3;
4166                 h /= 3;
4167                 }
4168
4169         if (event->state & GDK_CONTROL_MASK)
4170                 {
4171                 switch (event->direction)
4172                         {
4173                         case GDK_SCROLL_UP:
4174                                 pixbuf_renderer_zoom_adjust_at_point(pr, ZOOM_INCREMENT,
4175                                                                      (gint)event->x, (gint)event->y);
4176                                 break;
4177                         case GDK_SCROLL_DOWN:
4178                                 pixbuf_renderer_zoom_adjust_at_point(pr, -ZOOM_INCREMENT,
4179                                                                      (gint)event->x, (gint)event->y);
4180                                 break;
4181                         default:
4182                                 break;
4183                         }
4184                 }
4185         else
4186                 {
4187                 switch (event->direction)
4188                         {
4189                         case GDK_SCROLL_UP:
4190                                 pixbuf_renderer_scroll(pr, 0, -h);
4191                                 break;
4192                         case GDK_SCROLL_DOWN:
4193                                 pixbuf_renderer_scroll(pr, 0, h);
4194                                 break;
4195                         case GDK_SCROLL_LEFT:
4196                                 pixbuf_renderer_scroll(pr, -w, 0);
4197                                 break;
4198                         case GDK_SCROLL_RIGHT:
4199                                 pixbuf_renderer_scroll(pr, w, 0);
4200                                 break;
4201                         default:
4202                                 break;
4203                         }
4204                 }
4205 }
4206
4207 static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd)
4208 {
4209         g_signal_connect(G_OBJECT(imd->pr), "clicked",
4210                          G_CALLBACK(button_cb), pw);
4211         g_signal_connect(G_OBJECT(imd->pr), "scroll_event",
4212                          G_CALLBACK(scroll_cb), pw);
4213 }
4214
4215 static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data)
4216 {
4217         PanWindow *pw = data;
4218
4219         pw->fs = NULL;
4220         pw->imd = pw->imd_normal;
4221 }
4222
4223 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off)
4224 {
4225         if (force_off && !pw->fs) return;
4226
4227         if (pw->fs)
4228                 {
4229                 fullscreen_stop(pw->fs);
4230                 }
4231         else
4232                 {
4233                 pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw);
4234                 pan_image_set_buttons(pw, pw->fs->imd);
4235                 g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event",
4236                                  G_CALLBACK(pan_window_key_press_cb), pw);
4237
4238                 pw->imd = pw->fs->imd;
4239                 }
4240 }
4241
4242 static void pan_window_image_zoom_cb(PixbufRenderer *pr, gdouble zoom, gpointer data)
4243 {
4244         PanWindow *pw = data;
4245         gchar *text;
4246
4247         text = image_zoom_get_as_text(pw->imd);
4248         gtk_label_set_text(GTK_LABEL(pw->label_zoom), text);
4249         g_free(text);
4250 }
4251
4252 static void pan_window_image_scroll_notify_cb(PixbufRenderer *pr, gpointer data)
4253 {
4254         PanWindow *pw = data;
4255         GtkAdjustment *adj;
4256         GdkRectangle rect;
4257         gint width, height;
4258
4259         if (pr->scale == 0.0) return;
4260
4261         pixbuf_renderer_get_visible_rect(pr, &rect);
4262         pixbuf_renderer_get_image_size(pr, &width, &height);
4263
4264         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h));
4265         adj->page_size = (gdouble)rect.width;
4266         adj->page_increment = adj->page_size / 2.0;
4267         adj->step_increment = 48.0 / pr->scale;
4268         adj->lower = 0.0;
4269         adj->upper = MAX((gdouble)width, 1.0);
4270         adj->value = (gdouble)rect.x;
4271
4272         pref_signal_block_data(pw->scrollbar_h, pw);
4273         gtk_adjustment_changed(adj);
4274         gtk_adjustment_value_changed(adj);
4275         pref_signal_unblock_data(pw->scrollbar_h, pw);
4276
4277         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v));
4278         adj->page_size = (gdouble)rect.height;
4279         adj->page_increment = adj->page_size / 2.0;
4280         adj->step_increment = 48.0 / pr->scale;
4281         adj->lower = 0.0;
4282         adj->upper = MAX((gdouble)height, 1.0);
4283         adj->value = (gdouble)rect.y;
4284
4285         pref_signal_block_data(pw->scrollbar_v, pw);
4286         gtk_adjustment_changed(adj);
4287         gtk_adjustment_value_changed(adj);
4288         pref_signal_unblock_data(pw->scrollbar_v, pw);
4289 }
4290
4291 static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data)
4292 {
4293         PanWindow *pw = data;
4294         PixbufRenderer *pr;
4295         gint x;
4296
4297         pr = PIXBUF_RENDERER(pw->imd_normal->pr);
4298
4299         if (!pr->scale) return;
4300
4301         x = (gint)gtk_range_get_value(range);
4302
4303         pixbuf_renderer_scroll_to_point(pr, x, (gint)((gdouble)pr->y_scroll / pr->scale), 0.0, 0.0);
4304 }
4305
4306 static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data)
4307 {
4308         PanWindow *pw = data;
4309         PixbufRenderer *pr;
4310         gint y;
4311
4312         pr = PIXBUF_RENDERER(pw->imd_normal->pr);
4313
4314         if (!pr->scale) return;
4315
4316         y = (gint)gtk_range_get_value(range);
4317
4318         pixbuf_renderer_scroll_to_point(pr, (gint)((gdouble)pr->x_scroll / pr->scale), y, 0.0, 0.0);
4319 }
4320
4321 static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data)
4322 {
4323         PanWindow *pw = data;
4324
4325         pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
4326         pan_window_layout_update(pw);
4327 }
4328
4329 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
4330 {
4331         PanWindow *pw = data;
4332
4333         pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
4334         pan_window_layout_update(pw);
4335 }
4336
4337 #if 0
4338 static void pan_window_date_toggle_cb(GtkWidget *button, gpointer data)
4339 {
4340         PanWindow *pw = data;
4341
4342         pw->exif_date_enable = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
4343         pan_window_layout_update(pw);
4344 }
4345 #endif
4346
4347 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
4348 {
4349         PanWindow *pw = data;
4350         gchar *path;
4351
4352         path = remove_trailing_slash(new_text);
4353         parse_out_relatives(path);
4354
4355         if (!isdir(path))
4356                 {
4357                 warning_dialog(_("Folder not found"),
4358                                _("The entered path is not a folder"),
4359                                GTK_STOCK_DIALOG_WARNING, pw->path_entry);
4360                 }
4361         else
4362                 {
4363                 tab_completion_append_to_history(pw->path_entry, path);
4364
4365                 pan_window_layout_set_path(pw, path);
4366                 }
4367
4368         g_free(path);
4369 }
4370
4371 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
4372 {
4373         PanWindow *pw = data;
4374         gchar *text;
4375
4376         if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return;
4377
4378         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
4379         pan_window_entry_activate_cb(text, pw);
4380         g_free(text);
4381 }
4382
4383 static void pan_window_close(PanWindow *pw)
4384 {
4385         pan_window_list = g_list_remove(pan_window_list, pw);
4386
4387         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_EXIF_DATE, pw->exif_date_enable);
4388
4389         if (pw->idle_id != -1)
4390                 {
4391                 g_source_remove(pw->idle_id);
4392                 }
4393
4394         pan_fullscreen_toggle(pw, TRUE);
4395         gtk_widget_destroy(pw->window);
4396
4397         pan_window_items_free(pw);
4398         pan_cache_free(pw);
4399
4400         g_free(pw->path);
4401
4402         g_free(pw);
4403 }
4404
4405 static gint pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
4406 {
4407         PanWindow *pw = data;
4408
4409         pan_window_close(pw);
4410         return TRUE;
4411 }
4412
4413 static void pan_window_new_real(const gchar *path)
4414 {
4415         PanWindow *pw;
4416         GtkWidget *vbox;
4417         GtkWidget *box;
4418         GtkWidget *combo;
4419         GtkWidget *hbox;
4420         GtkWidget *frame;
4421         GtkWidget *table;
4422         GdkGeometry geometry;
4423
4424         pw = g_new0(PanWindow, 1);
4425
4426         pw->path = g_strdup(path);
4427         pw->layout = LAYOUT_TIMELINE;
4428         pw->size = LAYOUT_SIZE_THUMB_NORMAL;
4429         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
4430         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
4431
4432         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_EXIF_DATE, &pw->exif_date_enable))
4433                 {
4434                 pw->exif_date_enable = FALSE;
4435                 }
4436
4437         pw->ignore_symlinks = TRUE;
4438
4439         pw->list = NULL;
4440         pw->list_static = NULL;
4441         pw->list_grid = NULL;
4442
4443         pw->fs = NULL;
4444         pw->overlay_id = -1;
4445         pw->idle_id = -1;
4446
4447         pw->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4448
4449         geometry.min_width = 8;
4450         geometry.min_height = 8;
4451         gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE);
4452
4453         gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE);
4454         gtk_window_set_title (GTK_WINDOW(pw->window), _("Pan View - GQview"));
4455         gtk_window_set_wmclass(GTK_WINDOW(pw->window), "view", "GQview");
4456         gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0);
4457
4458         window_set_icon(pw->window, NULL, NULL);
4459
4460         vbox = gtk_vbox_new(FALSE, 0);
4461         gtk_container_add(GTK_CONTAINER(pw->window), vbox);
4462         gtk_widget_show(vbox);
4463
4464         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
4465
4466         pref_spacer(box, 0);
4467         pref_label_new(box, _("Location:"));
4468         combo = tab_completion_new_with_history(&pw->path_entry, path, "pan_view_path", -1,
4469                                                 pan_window_entry_activate_cb, pw);
4470         g_signal_connect(G_OBJECT(pw->path_entry->parent), "changed",
4471                          G_CALLBACK(pan_window_entry_change_cb), pw);
4472         gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
4473         gtk_widget_show(combo);
4474
4475         combo = gtk_combo_box_new_text();
4476         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Timeline"));
4477         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Calendar"));
4478         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders"));
4479         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders (flower)"));
4480         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Grid"));
4481
4482         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout);
4483         g_signal_connect(G_OBJECT(combo), "changed",
4484                          G_CALLBACK(pan_window_layout_change_cb), pw);
4485         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
4486         gtk_widget_show(combo);
4487
4488         combo = gtk_combo_box_new_text();
4489         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Dots"));
4490         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("No Images"));
4491         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Small Thumbnails"));
4492         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Normal Thumbnails"));
4493         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Large Thumbnails"));
4494         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:10 (10%)"));
4495         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:4 (25%)"));
4496         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:3 (33%)"));
4497         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:2 (50%)"));
4498         gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:1 (100%)"));
4499
4500         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size);
4501         g_signal_connect(G_OBJECT(combo), "changed",
4502                          G_CALLBACK(pan_window_layout_size_cb), pw);
4503         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
4504         gtk_widget_show(combo);
4505
4506         table = pref_table_new(vbox, 2, 2, FALSE, TRUE);
4507         gtk_table_set_row_spacings(GTK_TABLE(table), 2);
4508         gtk_table_set_col_spacings(GTK_TABLE(table), 2);
4509
4510         pw->imd = image_new(TRUE);
4511         pw->imd_normal = pw->imd;
4512
4513         g_signal_connect(G_OBJECT(pw->imd->pr), "zoom",
4514                          G_CALLBACK(pan_window_image_zoom_cb), pw);
4515         g_signal_connect(G_OBJECT(pw->imd->pr), "scroll_notify",
4516                          G_CALLBACK(pan_window_image_scroll_notify_cb), pw);
4517
4518         gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1,
4519                          GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
4520         gtk_widget_show(GTK_WIDGET(pw->imd->widget));
4521
4522         pan_window_dnd_init(pw);
4523
4524         pan_image_set_buttons(pw, pw->imd);
4525
4526         pw->scrollbar_h = gtk_hscrollbar_new(NULL);
4527         g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed",
4528                          G_CALLBACK(pan_window_scrollbar_h_value_cb), pw);
4529         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2,
4530                          GTK_FILL | GTK_EXPAND, 0, 0, 0);
4531         gtk_widget_show(pw->scrollbar_h);
4532
4533         pw->scrollbar_v = gtk_vscrollbar_new(NULL);
4534         g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed",
4535                          G_CALLBACK(pan_window_scrollbar_v_value_cb), pw);
4536         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1,
4537                          0, GTK_FILL | GTK_EXPAND, 0, 0);
4538         gtk_widget_show(pw->scrollbar_v);
4539
4540         /* find bar */
4541
4542         pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
4543         gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2);
4544
4545         pref_spacer(pw->search_box, 0);
4546         pref_label_new(pw->search_box, _("Find:"));
4547
4548         hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
4549         gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0);
4550         gtk_widget_show(hbox);
4551
4552         combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1,
4553                                                 pan_search_activate_cb, pw);
4554         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
4555         gtk_widget_show(combo);
4556
4557         pw->search_label = gtk_label_new("");
4558         gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0);
4559         gtk_widget_show(pw->search_label);
4560
4561         /* status bar */
4562
4563         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
4564
4565         frame = gtk_frame_new(NULL);
4566         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
4567         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
4568         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
4569         gtk_widget_show(frame);
4570
4571         hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
4572         gtk_container_add(GTK_CONTAINER(frame), hbox);
4573         gtk_widget_show(hbox);
4574
4575         pref_spacer(hbox, 0);
4576         pw->label_message = pref_label_new(hbox, "");
4577
4578         frame = gtk_frame_new(NULL);
4579         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
4580         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
4581         gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0);
4582         gtk_widget_show(frame);
4583
4584         pw->label_zoom = gtk_label_new("");
4585         gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
4586         gtk_widget_show(pw->label_zoom);
4587
4588 #if 0
4589         pw->date_button = pref_checkbox_new(box, _("Use Exif date"), pw->exif_date_enable,
4590                                             G_CALLBACK(pan_window_date_toggle_cb), pw);
4591 #endif
4592
4593         pw->search_button = gtk_toggle_button_new();
4594         gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE);
4595         gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE);
4596         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
4597         gtk_container_add(GTK_CONTAINER(pw->search_button), hbox);
4598         gtk_widget_show(hbox);
4599         pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
4600         gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0);
4601         gtk_widget_show(pw->search_button_arrow);
4602         pref_label_new(hbox, _("Find"));
4603
4604         gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0);
4605         gtk_widget_show(pw->search_button);
4606         g_signal_connect(G_OBJECT(pw->search_button), "clicked",
4607                          G_CALLBACK(pan_search_toggle_cb), pw);
4608
4609         g_signal_connect(G_OBJECT(pw->window), "delete_event",
4610                          G_CALLBACK(pan_window_delete_cb), pw);
4611         g_signal_connect(G_OBJECT(pw->window), "key_press_event",
4612                          G_CALLBACK(pan_window_key_press_cb), pw);
4613
4614         gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
4615
4616         pan_window_layout_update(pw);
4617
4618         gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
4619         gtk_widget_show(pw->window);
4620
4621         pan_window_list = g_list_append(pan_window_list, pw);
4622 }
4623
4624 /*
4625  *-----------------------------------------------------------------------------
4626  * peformance warnings
4627  *-----------------------------------------------------------------------------
4628  */
4629
4630 static void pan_warning_ok_cb(GenericDialog *gd, gpointer data)
4631 {
4632         gchar *path = data;
4633
4634         generic_dialog_close(gd);
4635
4636         pan_window_new_real(path);
4637         g_free(path);
4638 }
4639
4640 static void pan_warning_hide_cb(GtkWidget *button, gpointer data)
4641 {
4642         gint hide_dlg;
4643
4644         hide_dlg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
4645         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, hide_dlg);
4646 }
4647
4648 static gint pan_warning(const gchar *path)
4649 {
4650         GenericDialog *gd;
4651         GtkWidget *box;
4652         GtkWidget *group;
4653         GtkWidget *button;
4654         GtkWidget *ct_button;
4655         gint hide_dlg;
4656
4657         if (path && strcmp(path, "/") == 0)
4658                 {
4659                 pan_warning_folder(path, NULL);
4660                 return TRUE;
4661                 }
4662
4663         if (enable_thumb_caching &&
4664             thumbnail_spec_standard) return FALSE;
4665
4666         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, &hide_dlg)) hide_dlg = FALSE;
4667         if (hide_dlg) return FALSE;
4668
4669         gd = generic_dialog_new(_("Pan View Performance"), "GQview", "pan_view_warning", NULL, FALSE,
4670                                 NULL, NULL);
4671         gd->data = g_strdup(path);
4672         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
4673                                   pan_warning_ok_cb, TRUE);
4674
4675         box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO,
4676                                          _("Pan view performance may be poor."),
4677                                          _("To improve performance of thumbnails in the pan view the"
4678                                            " following options can be enabled. Note that both options"
4679                                            " must be enabled to notice a change in performance."));
4680
4681         group = pref_box_new(box, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
4682         pref_spacer(group, PREF_PAD_INDENT);
4683         group = pref_box_new(group, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
4684
4685         ct_button = pref_checkbox_new_int(group, _("Cache thumbnails"),
4686                                           enable_thumb_caching, &enable_thumb_caching);
4687         button = pref_checkbox_new_int(group, _("Use shared thumbnail cache"),
4688                                        thumbnail_spec_standard, &thumbnail_spec_standard);
4689         pref_checkbox_link_sensitivity(ct_button, button);
4690
4691         pref_line(box, 0);
4692
4693         pref_checkbox_new(box, _("Do not show this dialog again"), hide_dlg,
4694                           G_CALLBACK(pan_warning_hide_cb), NULL);
4695
4696         gtk_widget_show(gd->dialog);
4697
4698         return TRUE;
4699 }
4700
4701
4702 /*
4703  *-----------------------------------------------------------------------------
4704  * public
4705  *-----------------------------------------------------------------------------
4706  */
4707
4708 void pan_window_new(const gchar *path)
4709 {
4710         if (pan_warning(path)) return;
4711
4712         pan_window_new_real(path);
4713 }
4714
4715 /*
4716  *-----------------------------------------------------------------------------
4717  * menus
4718  *-----------------------------------------------------------------------------
4719  */
4720
4721 static void pan_new_window_cb(GtkWidget *widget, gpointer data)
4722 {
4723         PanWindow *pw = data;
4724         const gchar *path;
4725
4726         path = pan_menu_click_path(pw);
4727         if (path)
4728                 {
4729                 pan_fullscreen_toggle(pw, TRUE);
4730                 view_window_new(path);
4731                 }
4732 }
4733
4734 static void pan_edit_cb(GtkWidget *widget, gpointer data)
4735 {
4736         PanWindow *pw;
4737         const gchar *path;
4738         gint n;
4739
4740         pw = submenu_item_get_data(widget);
4741         n = GPOINTER_TO_INT(data);
4742         if (!pw) return;
4743
4744         path = pan_menu_click_path(pw);
4745         if (path)
4746                 {
4747                 if (!editor_window_flag_set(n))
4748                         {
4749                         pan_fullscreen_toggle(pw, TRUE);
4750                         }
4751                 start_editor_from_file(n, path);
4752                 }
4753 }
4754
4755 static void pan_info_cb(GtkWidget *widget, gpointer data)
4756 {
4757         PanWindow *pw = data;
4758         const gchar *path;
4759
4760         path = pan_menu_click_path(pw);
4761         if (path) info_window_new(path, NULL);
4762 }
4763
4764 static void pan_zoom_in_cb(GtkWidget *widget, gpointer data)
4765 {
4766         PanWindow *pw = data;
4767
4768         image_zoom_adjust(pw->imd, ZOOM_INCREMENT);
4769 }
4770
4771 static void pan_zoom_out_cb(GtkWidget *widget, gpointer data)
4772 {
4773         PanWindow *pw = data;
4774
4775         image_zoom_adjust(pw->imd, -ZOOM_INCREMENT);
4776 }
4777
4778 static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data)
4779 {
4780         PanWindow *pw = data;
4781
4782         image_zoom_set(pw->imd, 1.0);
4783 }
4784
4785 static void pan_copy_cb(GtkWidget *widget, gpointer data)
4786 {
4787         PanWindow *pw = data;
4788         const gchar *path;
4789
4790         path = pan_menu_click_path(pw);
4791         if (path) file_util_copy(path, NULL, NULL, pw->imd->widget);
4792 }
4793
4794 static void pan_move_cb(GtkWidget *widget, gpointer data)
4795 {
4796         PanWindow *pw = data;
4797         const gchar *path;
4798
4799         path = pan_menu_click_path(pw);
4800         if (path) file_util_move(path, NULL, NULL, pw->imd->widget);
4801 }
4802
4803 static void pan_rename_cb(GtkWidget *widget, gpointer data)
4804 {
4805         PanWindow *pw = data;
4806         const gchar *path;
4807
4808         path = pan_menu_click_path(pw);
4809         if (path) file_util_rename(path, NULL, pw->imd->widget);
4810 }
4811
4812 static void pan_delete_cb(GtkWidget *widget, gpointer data)
4813 {
4814         PanWindow *pw = data;
4815         const gchar *path;
4816
4817         path = pan_menu_click_path(pw);
4818         if (path) file_util_delete(path, NULL, pw->imd->widget);
4819 }
4820
4821 static void pan_exif_date_toggle_cb(GtkWidget *widget, gpointer data)
4822 {
4823         PanWindow *pw = data;
4824
4825         pw->exif_date_enable = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
4826         pan_window_layout_update(pw);
4827 }
4828
4829 static void pan_fullscreen_cb(GtkWidget *widget, gpointer data)
4830 {
4831         PanWindow *pw = data;
4832
4833         pan_fullscreen_toggle(pw, FALSE);
4834 }
4835
4836 static void pan_close_cb(GtkWidget *widget, gpointer data)
4837 {
4838         PanWindow *pw = data;
4839
4840         pan_window_close(pw);
4841 }
4842
4843 static GtkWidget *pan_popup_menu(PanWindow *pw)
4844 {
4845         GtkWidget *menu;
4846         GtkWidget *item;
4847         gint active;
4848
4849         active = (pw->click_pi != NULL);
4850
4851         menu = popup_menu_short_lived();
4852
4853         menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
4854                             G_CALLBACK(pan_zoom_in_cb), pw);
4855         menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
4856                             G_CALLBACK(pan_zoom_out_cb), pw);
4857         menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100,
4858                             G_CALLBACK(pan_zoom_1_1_cb), pw);
4859         menu_item_add_divider(menu);
4860
4861         submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw);
4862         gtk_widget_set_sensitive(item, active);
4863
4864         menu_item_add_stock_sensitive(menu, _("_Properties"), GTK_STOCK_PROPERTIES, active,
4865                                       G_CALLBACK(pan_info_cb), pw);
4866
4867         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
4868                                       G_CALLBACK(pan_new_window_cb), pw);
4869
4870         menu_item_add_divider(menu);
4871         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
4872                                       G_CALLBACK(pan_copy_cb), pw);
4873         menu_item_add_sensitive(menu, _("_Move..."), active,
4874                                 G_CALLBACK(pan_move_cb), pw);
4875         menu_item_add_sensitive(menu, _("_Rename..."), active,
4876                                 G_CALLBACK(pan_rename_cb), pw);
4877         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
4878                                       G_CALLBACK(pan_delete_cb), pw);
4879
4880         menu_item_add_divider(menu);
4881         item = menu_item_add_check(menu, _("Sort by E_xif date"), pw->exif_date_enable,
4882                                    G_CALLBACK(pan_exif_date_toggle_cb), pw);
4883         gtk_widget_set_sensitive(item, (pw->layout == LAYOUT_TIMELINE || pw->layout == LAYOUT_CALENDAR));
4884
4885         menu_item_add_divider(menu);
4886
4887         if (pw->fs)
4888                 {
4889                 menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4890                 }
4891         else
4892                 {
4893                 menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4894                 }
4895
4896         menu_item_add_divider(menu);
4897         menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw);
4898
4899         return menu;
4900 }
4901
4902 /*
4903  *-----------------------------------------------------------------------------
4904  * drag and drop
4905  *-----------------------------------------------------------------------------
4906  */
4907
4908 static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context,
4909                                     gint x, gint y,
4910                                     GtkSelectionData *selection_data, guint info,
4911                                     guint time, gpointer data)
4912 {
4913         PanWindow *pw = data;
4914
4915         if (gtk_drag_get_source_widget(context) == pw->imd->pr) return;
4916
4917         if (info == TARGET_URI_LIST)
4918                 {
4919                 GList *list;
4920
4921                 list = uri_list_from_text((gchar *)selection_data->data, TRUE);
4922                 if (list && isdir((gchar *)list->data))
4923                         {
4924                         gchar *path = list->data;
4925
4926                         pan_window_layout_set_path(pw, path);
4927                         }
4928
4929                 path_list_free(list);
4930                 }
4931 }
4932
4933 static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context,
4934                                     GtkSelectionData *selection_data, guint info,
4935                                     guint time, gpointer data)
4936 {
4937         PanWindow *pw = data;
4938         const gchar *path;
4939
4940         path = pan_menu_click_path(pw);
4941         if (path)
4942                 {
4943                 gchar *text = NULL;
4944                 gint len;
4945                 gint plain_text;
4946                 GList *list;
4947
4948                 switch (info)
4949                         {
4950                         case TARGET_URI_LIST:
4951                                 plain_text = FALSE;
4952                                 break;
4953                         case TARGET_TEXT_PLAIN:
4954                         default:
4955                                 plain_text = TRUE;
4956                                 break;
4957                         }
4958                 list = g_list_append(NULL, (gchar *)path);
4959                 text = uri_text_from_list(list, &len, plain_text);
4960                 g_list_free(list);
4961                 if (text)
4962                         {
4963                         gtk_selection_data_set (selection_data, selection_data->target,
4964                                                 8, (guchar *)text, len);
4965                         g_free(text);
4966                         }
4967                 }
4968         else
4969                 {
4970                 gtk_selection_data_set (selection_data, selection_data->target,
4971                                         8, NULL, 0);
4972                 }
4973 }
4974
4975 static void pan_window_dnd_init(PanWindow *pw)
4976 {
4977         GtkWidget *widget;
4978
4979         widget = pw->imd->pr;
4980
4981         gtk_drag_source_set(widget, GDK_BUTTON2_MASK,
4982                             dnd_file_drag_types, dnd_file_drag_types_count,
4983                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4984         g_signal_connect(G_OBJECT(widget), "drag_data_get",
4985                          G_CALLBACK(pan_window_set_dnd_data), pw);
4986
4987         gtk_drag_dest_set(widget,
4988                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
4989                           dnd_file_drop_types, dnd_file_drop_types_count,
4990                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4991         g_signal_connect(G_OBJECT(widget), "drag_data_received",
4992                          G_CALLBACK(pan_window_get_dnd_data), pw);
4993 }
4994
4995 /*
4996  *-----------------------------------------------------------------------------
4997  * maintenance (for rename, move, remove)
4998  *-----------------------------------------------------------------------------
4999  */
5000