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