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