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