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