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