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