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