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