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