Start moving pan view search code to its own module
[geeqie.git] / src / pan-view / pan-view.c
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "pan-view.h"
23
24 #include "bar_exif.h"
25 #include "dnd.h"
26 #include "editors.h"
27 #include "exif.h"
28 #include "fullscreen.h"
29 #include "history_list.h"
30 #include "image.h"
31 #include "img-view.h"
32 #include "menu.h"
33 #include "metadata.h"
34 #include "misc.h"
35 #include "pan-calendar.h"
36 #include "pan-folder.h"
37 #include "pan-grid.h"
38 #include "pan-item.h"
39 #include "pan-timeline.h"
40 #include "pan-util.h"
41 #include "pan-view-search.h"
42 #include "pixbuf-renderer.h"
43 #include "pixbuf_util.h"
44 #include "thumb.h"
45 #include "ui_fileops.h"
46 #include "ui_menu.h"
47 #include "ui_misc.h"
48 #include "ui_tabcomp.h"
49 #include "ui_utildlg.h"
50 #include "uri_utils.h"
51 #include "utilops.h"
52 #include "window.h"
53
54 #include <gdk/gdkkeysyms.h> /* for keyboard values */
55
56 #include <math.h>
57
58
59 #define PAN_WINDOW_DEFAULT_WIDTH 720
60 #define PAN_WINDOW_DEFAULT_HEIGHT 500
61
62 #define PAN_TILE_SIZE 512
63
64 #define ZOOM_INCREMENT 1.0
65 #define ZOOM_LABEL_WIDTH 64
66
67
68 #define PAN_PREF_GROUP          "pan_view_options"
69 #define PAN_PREF_HIDE_WARNING   "hide_performance_warning"
70 #define PAN_PREF_EXIF_PAN_DATE  "use_exif_date"
71 #define PAN_PREF_INFO_IMAGE     "info_image_size"
72 #define PAN_PREF_INFO_EXIF      "info_includes_exif"
73
74
75 static GList *pan_window_list = NULL;
76
77
78 static void pan_layout_update_idle(PanWindow *pw);
79
80 static void pan_fullscreen_toggle(PanWindow *pw, gboolean force_off);
81
82 static void pan_window_close(PanWindow *pw);
83
84 static GtkWidget *pan_popup_menu(PanWindow *pw);
85
86 static void pan_window_dnd_init(PanWindow *pw);
87
88
89 /*
90  *-----------------------------------------------------------------------------
91  * the image/thumb loader queue
92  *-----------------------------------------------------------------------------
93  */
94
95 static gboolean pan_queue_step(PanWindow *pw);
96
97
98 static void pan_queue_thumb_done_cb(ThumbLoader *tl, gpointer data)
99 {
100         PanWindow *pw = data;
101
102         if (pw->queue_pi)
103                 {
104                 PanItem *pi;
105                 gint rc;
106
107                 pi = pw->queue_pi;
108                 pw->queue_pi = NULL;
109
110                 pi->queued = FALSE;
111
112                 if (pi->pixbuf) g_object_unref(pi->pixbuf);
113                 pi->pixbuf = thumb_loader_get_pixbuf(tl);
114
115                 rc = pi->refcount;
116                 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
117                 pi->refcount = rc;
118                 }
119
120         thumb_loader_free(pw->tl);
121         pw->tl = NULL;
122
123         while (pan_queue_step(pw));
124 }
125
126 static void pan_queue_image_done_cb(ImageLoader *il, gpointer data)
127 {
128         PanWindow *pw = data;
129
130         if (pw->queue_pi)
131                 {
132                 PanItem *pi;
133                 gint rc;
134
135                 pi = pw->queue_pi;
136                 pw->queue_pi = NULL;
137
138                 pi->queued = FALSE;
139
140                 if (pi->pixbuf) g_object_unref(pi->pixbuf);
141                 pi->pixbuf = image_loader_get_pixbuf(pw->il);
142                 if (pi->pixbuf) g_object_ref(pi->pixbuf);
143
144                 if (pi->pixbuf && pw->size != PAN_IMAGE_SIZE_100 &&
145                     (gdk_pixbuf_get_width(pi->pixbuf) > pi->width ||
146                      gdk_pixbuf_get_height(pi->pixbuf) > pi->height))
147                         {
148                         GdkPixbuf *tmp;
149
150                         tmp = pi->pixbuf;
151                         pi->pixbuf = gdk_pixbuf_scale_simple(tmp, pi->width, pi->height,
152                                                              (GdkInterpType)options->image.zoom_quality);
153                         g_object_unref(tmp);
154                         }
155
156                 rc = pi->refcount;
157                 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
158                 pi->refcount = rc;
159                 }
160
161         image_loader_free(pw->il);
162         pw->il = NULL;
163
164         while (pan_queue_step(pw));
165 }
166
167 static gboolean pan_queue_step(PanWindow *pw)
168 {
169         PanItem *pi;
170
171         if (!pw->queue) return FALSE;
172
173         pi = pw->queue->data;
174         pw->queue = g_list_remove(pw->queue, pi);
175         pw->queue_pi = pi;
176
177         if (!pw->queue_pi->fd)
178                 {
179                 pw->queue_pi->queued = FALSE;
180                 pw->queue_pi = NULL;
181                 return TRUE;
182                 }
183
184         image_loader_free(pw->il);
185         pw->il = NULL;
186         thumb_loader_free(pw->tl);
187         pw->tl = NULL;
188
189         if (pi->type == PAN_ITEM_IMAGE)
190                 {
191                 pw->il = image_loader_new(pi->fd);
192
193                 if (pw->size != PAN_IMAGE_SIZE_100)
194                         {
195                         image_loader_set_requested_size(pw->il, pi->width, pi->height);
196                         }
197
198                 g_signal_connect(G_OBJECT(pw->il), "error", (GCallback)pan_queue_image_done_cb, pw);
199                 g_signal_connect(G_OBJECT(pw->il), "done", (GCallback)pan_queue_image_done_cb, pw);
200
201                 if (image_loader_start(pw->il)) return FALSE;
202
203                 image_loader_free(pw->il);
204                 pw->il = NULL;
205                 }
206         else if (pi->type == PAN_ITEM_THUMB)
207                 {
208                 pw->tl = thumb_loader_new(PAN_THUMB_SIZE, PAN_THUMB_SIZE);
209
210                 if (!pw->tl->standard_loader)
211                         {
212                         /* The classic loader will recreate a thumbnail any time we
213                          * request a different size than what exists. This view will
214                          * almost never use the user configured sizes so disable cache.
215                          */
216                         thumb_loader_set_cache(pw->tl, FALSE, FALSE, FALSE);
217                         }
218
219                 thumb_loader_set_callbacks(pw->tl,
220                                            pan_queue_thumb_done_cb,
221                                            pan_queue_thumb_done_cb,
222                                            NULL, pw);
223
224                 if (thumb_loader_start(pw->tl, pi->fd)) return FALSE;
225
226                 thumb_loader_free(pw->tl);
227                 pw->tl = NULL;
228                 }
229
230         pw->queue_pi->queued = FALSE;
231         pw->queue_pi = NULL;
232         return TRUE;
233 }
234
235 static void pan_queue_add(PanWindow *pw, PanItem *pi)
236 {
237         if (!pi || pi->queued || pi->pixbuf) return;
238         if (pw->size <= PAN_IMAGE_SIZE_THUMB_NONE &&
239             (!pi->key || strcmp(pi->key, "info") != 0) )
240                 {
241                 return;
242                 }
243
244         pi->queued = TRUE;
245         pw->queue = g_list_prepend(pw->queue, pi);
246
247         if (!pw->tl && !pw->il) while (pan_queue_step(pw));
248 }
249
250
251 /*
252  *-----------------------------------------------------------------------------
253  * tile request/dispose handlers
254  *-----------------------------------------------------------------------------
255  */
256
257 static gboolean pan_window_request_tile_cb(PixbufRenderer *pr, gint x, gint y,
258                                            gint width, gint height, GdkPixbuf *pixbuf, gpointer data)
259 {
260         PanWindow *pw = data;
261         GList *list;
262         GList *work;
263         gint i;
264
265         pixbuf_set_rect_fill(pixbuf,
266                              0, 0, width, height,
267                              PAN_BACKGROUND_COLOR, 255);
268
269         for (i = (x / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < x + width; i += PAN_GRID_SIZE)
270                 {
271                 gint rx, ry, rw, rh;
272
273                 if (util_clip_region(x, y, width, height,
274                                      i, y, 1, height,
275                                      &rx, &ry, &rw, &rh))
276                         {
277                         pixbuf_draw_rect_fill(pixbuf,
278                                               rx - x, ry - y, rw, rh,
279                                               PAN_GRID_COLOR, PAN_GRID_ALPHA);
280                         }
281                 }
282         for (i = (y / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < y + height; i += PAN_GRID_SIZE)
283                 {
284                 gint rx, ry, rw, rh;
285
286                 if (util_clip_region(x, y, width, height,
287                                      x, i, width, 1,
288                                      &rx, &ry, &rw, &rh))
289                         {
290                         pixbuf_draw_rect_fill(pixbuf,
291                                               rx - x, ry - y, rw, rh,
292                                               PAN_GRID_COLOR, PAN_GRID_ALPHA);
293                         }
294                 }
295
296         list = pan_layout_intersect(pw, x, y, width, height);
297         work = list;
298         while (work)
299                 {
300                 PanItem *pi;
301                 gboolean queue = FALSE;
302
303                 pi = work->data;
304                 work = work->next;
305
306                 pi->refcount++;
307
308                 switch (pi->type)
309                         {
310                         case PAN_ITEM_BOX:
311                                 queue = pan_item_box_draw(pw, pi, pixbuf, pr, x, y, width, height);
312                                 break;
313                         case PAN_ITEM_TRIANGLE:
314                                 queue = pan_item_tri_draw(pw, pi, pixbuf, pr, x, y, width, height);
315                                 break;
316                         case PAN_ITEM_TEXT:
317                                 queue = pan_item_text_draw(pw, pi, pixbuf, pr, x, y, width, height);
318                                 break;
319                         case PAN_ITEM_THUMB:
320                                 queue = pan_item_thumb_draw(pw, pi, pixbuf, pr, x, y, width, height);
321                                 break;
322                         case PAN_ITEM_IMAGE:
323                                 queue = pan_item_image_draw(pw, pi, pixbuf, pr, x, y, width, height);
324                                 break;
325                         case PAN_ITEM_NONE:
326                         default:
327                                 break;
328                         }
329
330                 if (queue) pan_queue_add(pw, pi);
331                 }
332
333         g_list_free(list);
334
335         return TRUE;
336 }
337
338 static void pan_window_dispose_tile_cb(PixbufRenderer *pr, gint x, gint y,
339                                        gint width, gint height, GdkPixbuf *pixbuf, gpointer data)
340 {
341         PanWindow *pw = data;
342         GList *list;
343         GList *work;
344
345         list = pan_layout_intersect(pw, x, y, width, height);
346         work = list;
347         while (work)
348                 {
349                 PanItem *pi;
350
351                 pi = work->data;
352                 work = work->next;
353
354                 if (pi->refcount > 0)
355                         {
356                         pi->refcount--;
357
358                         if (pi->refcount == 0)
359                                 {
360                                 if (pi->queued)
361                                         {
362                                         pw->queue = g_list_remove(pw->queue, pi);
363                                         pi->queued = FALSE;
364                                         }
365                                 if (pw->queue_pi == pi) pw->queue_pi = NULL;
366                                 if (pi->pixbuf)
367                                         {
368                                         g_object_unref(pi->pixbuf);
369                                         pi->pixbuf = NULL;
370                                         }
371                                 }
372                         }
373                 }
374
375         g_list_free(list);
376 }
377
378
379 /*
380  *-----------------------------------------------------------------------------
381  * misc
382  *-----------------------------------------------------------------------------
383  */
384
385 static void pan_window_message(PanWindow *pw, const gchar *text)
386 {
387         GList *work;
388         gint count = 0;
389         gint64 size = 0;
390         gchar *ss;
391         gchar *buf;
392
393         if (text)
394                 {
395                 gtk_label_set_text(GTK_LABEL(pw->label_message), text);
396                 return;
397                 }
398
399         work = pw->list_static;
400         if (pw->layout == PAN_LAYOUT_CALENDAR)
401                 {
402                 while (work)
403                         {
404                         PanItem *pi;
405
406                         pi = work->data;
407                         work = work->next;
408
409                         if (pi->fd &&
410                             pi->type == PAN_ITEM_BOX &&
411                             pi->key && strcmp(pi->key, "dot") == 0)
412                                 {
413                                 size += pi->fd->size;
414                                 count++;
415                                 }
416                         }
417                 }
418         else
419                 {
420                 while (work)
421                         {
422                         PanItem *pi;
423
424                         pi = work->data;
425                         work = work->next;
426
427                         if (pi->fd &&
428                             (pi->type == PAN_ITEM_THUMB || pi->type == PAN_ITEM_IMAGE))
429                                 {
430                                 size += pi->fd->size;
431                                 count++;
432                                 }
433                         }
434                 }
435
436         ss = text_from_size_abrev(size);
437         buf = g_strdup_printf(_("%d images, %s"), count, ss);
438         g_free(ss);
439         gtk_label_set_text(GTK_LABEL(pw->label_message), buf);
440         g_free(buf);
441 }
442
443 static void pan_warning_folder(const gchar *path, GtkWidget *parent)
444 {
445         gchar *message;
446
447         message = g_strdup_printf(_("The pan view does not support the folder \"%s\"."), path);
448         warning_dialog(_("Folder not supported"), message,
449                       GTK_STOCK_DIALOG_INFO, parent);
450         g_free(message);
451 }
452
453 static void pan_window_zoom_limit(PanWindow *pw)
454 {
455         gdouble min;
456
457         switch (pw->size)
458                 {
459                 case PAN_IMAGE_SIZE_THUMB_DOTS:
460                 case PAN_IMAGE_SIZE_THUMB_NONE:
461                 case PAN_IMAGE_SIZE_THUMB_SMALL:
462                 case PAN_IMAGE_SIZE_THUMB_NORMAL:
463 #if 0
464                         /* easily requires > 512mb ram when window size > 1024x768 and zoom is <= -8 */
465                         min = -16.0;
466                         break;
467 #endif
468                 case PAN_IMAGE_SIZE_THUMB_LARGE:
469                         min = -6.0;
470                         break;
471                 case PAN_IMAGE_SIZE_10:
472                 case PAN_IMAGE_SIZE_25:
473                         min = -4.0;
474                         break;
475                 case PAN_IMAGE_SIZE_33:
476                 case PAN_IMAGE_SIZE_50:
477                 case PAN_IMAGE_SIZE_100:
478                 default:
479                         min = -2.0;
480                         break;
481                 }
482
483         image_zoom_set_limits(pw->imd, min, 32.0);
484 }
485
486
487 /*
488  *-----------------------------------------------------------------------------
489  * cache
490  *-----------------------------------------------------------------------------
491  */
492
493 static gint pan_cache_sort_file_cb(gpointer a, gpointer b)
494 {
495         PanCacheData *pca = a;
496         PanCacheData *pcb = b;
497         return filelist_sort_compare_filedata(pca->fd, pcb->fd);
498 }
499 GList *pan_cache_sort(GList *list, SortType method, gboolean ascend)
500 {
501         return filelist_sort_full(list, method, ascend, (GCompareFunc) pan_cache_sort_file_cb);
502 }
503
504
505 static void pan_cache_free(PanWindow *pw)
506 {
507         GList *work;
508
509         work = pw->cache_list;
510         while (work)
511                 {
512                 PanCacheData *pc;
513
514                 pc = work->data;
515                 work = work->next;
516
517                 cache_sim_data_free(pc->cd);
518                 file_data_unref(pc->fd);
519                 g_free(pc);
520                 }
521
522         g_list_free(pw->cache_list);
523         pw->cache_list = NULL;
524
525         filelist_free(pw->cache_todo);
526         pw->cache_todo = NULL;
527
528         pw->cache_count = 0;
529         pw->cache_total = 0;
530         pw->cache_tick = 0;
531
532         cache_loader_free(pw->cache_cl);
533         pw->cache_cl = NULL;
534 }
535
536 static void pan_cache_fill(PanWindow *pw, FileData *dir_fd)
537 {
538         GList *list;
539
540         pan_cache_free(pw);
541
542         list = pan_list_tree(dir_fd, SORT_NAME, TRUE, pw->ignore_symlinks);
543         pw->cache_todo = g_list_reverse(list);
544
545         pw->cache_total = g_list_length(pw->cache_todo);
546 }
547
548 static void pan_cache_step_done_cb(CacheLoader *cl, gint error, gpointer data)
549 {
550         PanWindow *pw = data;
551
552         if (pw->cache_list)
553                 {
554                 PanCacheData *pc;
555                 pc = pw->cache_list->data;
556
557                 if (!pc->cd)
558                         {
559                         pc->cd = cl->cd;
560                         cl->cd = NULL;
561                         }
562                 }
563
564         cache_loader_free(cl);
565         pw->cache_cl = NULL;
566
567         pan_layout_update_idle(pw);
568 }
569
570 static gboolean pan_cache_step(PanWindow *pw)
571 {
572         FileData *fd;
573         PanCacheData *pc;
574         CacheDataType load_mask;
575
576         if (!pw->cache_todo) return TRUE;
577
578         fd = pw->cache_todo->data;
579         pw->cache_todo = g_list_remove(pw->cache_todo, fd);
580
581         pc = g_new0(PanCacheData, 1);
582         pc->fd = file_data_ref(fd);
583
584         pc->cd = NULL;
585
586         pw->cache_list = g_list_prepend(pw->cache_list, pc);
587
588         cache_loader_free(pw->cache_cl);
589
590         load_mask = CACHE_LOADER_NONE;
591         if (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) load_mask |= CACHE_LOADER_DIMENSIONS;
592         if (pw->exif_date_enable) load_mask |= CACHE_LOADER_DATE;
593         pw->cache_cl = cache_loader_new(pc->fd, load_mask,
594                                         pan_cache_step_done_cb, pw);
595         return (pw->cache_cl == NULL);
596 }
597
598 /* This sync date function is optimized for lists with a common sort */
599 void pan_cache_sync_date(PanWindow *pw, GList *list)
600 {
601         GList *haystack;
602         GList *work;
603
604         haystack = g_list_copy(pw->cache_list);
605
606         work = list;
607         while (work)
608                 {
609                 FileData *fd;
610                 GList *needle;
611
612                 fd = work->data;
613                 work = work->next;
614
615                 needle = haystack;
616                 while (needle)
617                         {
618                         PanCacheData *pc;
619
620                         pc = needle->data;
621                         if (pc->fd == fd)
622                                 {
623                                 if (pc->cd && pc->cd->have_date && pc->cd->date >= 0)
624                                         {
625                                         fd->date = pc->cd->date;
626                                         }
627
628                                 haystack = g_list_delete_link(haystack, needle);
629                                 needle = NULL;
630                                 }
631                         else
632                                 {
633                                 needle = needle->next;
634                                 }
635                         }
636                 }
637
638         g_list_free(haystack);
639 }
640
641 /*
642  *-----------------------------------------------------------------------------
643  * item grid
644  *-----------------------------------------------------------------------------
645  */
646
647 static void pan_grid_clear(PanWindow *pw)
648 {
649         GList *work;
650
651         work = pw->list_grid;
652         while (work)
653                 {
654                 PanGrid *pg;
655
656                 pg = work->data;
657                 work = work->next;
658
659                 g_list_free(pg->list);
660                 g_free(pg);
661                 }
662
663         g_list_free(pw->list_grid);
664         pw->list_grid = NULL;
665
666         pw->list = g_list_concat(pw->list, pw->list_static);
667         pw->list_static = NULL;
668 }
669
670 static void pan_grid_build(PanWindow *pw, gint width, gint height, gint grid_size)
671 {
672         GList *work;
673         gint col, row;
674         gint cw, ch;
675         gint l;
676         gint i, j;
677
678         pan_grid_clear(pw);
679
680         l = g_list_length(pw->list);
681
682         if (l < 1) return;
683
684         col = (gint)(sqrt((gdouble)l / grid_size) * width / height + 0.999);
685         col = CLAMP(col, 1, l / grid_size + 1);
686         row = (gint)((gdouble)l / grid_size / col);
687         if (row < 1) row = 1;
688
689         /* limit minimum size of grid so that a tile will always fit regardless of position */
690         cw = MAX((gint)ceil((gdouble)width / col), PAN_TILE_SIZE * 2);
691         ch = MAX((gint)ceil((gdouble)height / row), PAN_TILE_SIZE * 2);
692
693         row = row * 2 - 1;
694         col = col * 2 - 1;
695
696         DEBUG_1("intersect speedup grid is %dx%d, based on %d average per grid", col, row, grid_size);
697
698         for (j = 0; j < row; j++)
699             for (i = 0; i < col; i++)
700                 {
701                 if ((i + 1) * cw / 2 < width && (j + 1) * ch / 2 < height)
702                         {
703                         PanGrid *pg;
704
705                         pg = g_new0(PanGrid, 1);
706                         pg->x = i * cw / 2;
707                         pg->y = j * ch / 2;
708                         pg->w = cw;
709                         pg->h = ch;
710
711                         pw->list_grid = g_list_prepend(pw->list_grid, pg);
712
713                         DEBUG_1("grid section: %d,%d (%dx%d)", pg->x, pg->y, pg->w, pg->h);
714                         }
715                 }
716
717         work = pw->list;
718         while (work)
719                 {
720                 PanItem *pi;
721                 GList *grid;
722
723                 pi = work->data;
724                 work = work->next;
725
726                 grid = pw->list_grid;
727                 while (grid)
728                         {
729                         PanGrid *pg;
730                         gint rx, ry, rw, rh;
731
732                         pg = grid->data;
733                         grid = grid->next;
734
735                         if (util_clip_region(pi->x, pi->y, pi->width, pi->height,
736                                              pg->x, pg->y, pg->w, pg->h,
737                                              &rx, &ry, &rw, &rh))
738                                 {
739                                 pg->list = g_list_prepend(pg->list, pi);
740                                 }
741                         }
742                 }
743
744         work = pw->list_grid;
745         while (work)
746                 {
747                 PanGrid *pg;
748
749                 pg = work->data;
750                 work = work->next;
751
752                 pg->list = g_list_reverse(pg->list);
753                 }
754
755         pw->list_static = pw->list;
756         pw->list = NULL;
757 }
758
759
760 /*
761  *-----------------------------------------------------------------------------
762  * layout state reset
763  *-----------------------------------------------------------------------------
764  */
765
766 static void pan_window_items_free(PanWindow *pw)
767 {
768         GList *work;
769
770         pan_grid_clear(pw);
771
772         work = pw->list;
773         while (work)
774                 {
775                 PanItem *pi = work->data;
776                 work = work->next;
777
778                 pan_item_free(pi);
779                 }
780
781         g_list_free(pw->list);
782         pw->list = NULL;
783
784         g_list_free(pw->queue);
785         pw->queue = NULL;
786         pw->queue_pi = NULL;
787
788         image_loader_free(pw->il);
789         pw->il = NULL;
790
791         thumb_loader_free(pw->tl);
792         pw->tl = NULL;
793
794         pw->click_pi = NULL;
795         pw->search_pi = NULL;
796 }
797
798
799 /*
800  *-----------------------------------------------------------------------------
801  * layout generation, queries, sizing
802  *-----------------------------------------------------------------------------
803  */
804
805 static void pan_layout_compute(PanWindow *pw, FileData *dir_fd,
806                                gint *width, gint *height,
807                                gint *scroll_x, gint *scroll_y)
808 {
809         pan_window_items_free(pw);
810
811         switch (pw->size)
812                 {
813                 case PAN_IMAGE_SIZE_THUMB_DOTS:
814                         pw->thumb_size = PAN_THUMB_SIZE_DOTS;
815                         pw->thumb_gap = PAN_THUMB_GAP_DOTS;
816                         break;
817                 case PAN_IMAGE_SIZE_THUMB_NONE:
818                         pw->thumb_size = PAN_THUMB_SIZE_NONE;
819                         pw->thumb_gap = PAN_THUMB_GAP_SMALL;
820                         break;
821                 case PAN_IMAGE_SIZE_THUMB_SMALL:
822                         pw->thumb_size = PAN_THUMB_SIZE_SMALL;
823                         pw->thumb_gap = PAN_THUMB_GAP_SMALL;
824                         break;
825                 case PAN_IMAGE_SIZE_THUMB_NORMAL:
826                 default:
827                         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
828                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
829                         break;
830                 case PAN_IMAGE_SIZE_THUMB_LARGE:
831                         pw->thumb_size = PAN_THUMB_SIZE_LARGE;
832                         pw->thumb_gap = PAN_THUMB_GAP_LARGE;
833                         break;
834                 case PAN_IMAGE_SIZE_10:
835                         pw->image_size = 10;
836                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
837                         break;
838                 case PAN_IMAGE_SIZE_25:
839                         pw->image_size = 25;
840                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
841                         break;
842                 case PAN_IMAGE_SIZE_33:
843                         pw->image_size = 33;
844                         pw->thumb_gap = PAN_THUMB_GAP_LARGE;
845                         break;
846                 case PAN_IMAGE_SIZE_50:
847                         pw->image_size = 50;
848                         pw->thumb_gap = PAN_THUMB_GAP_HUGE;
849                         break;
850                 case PAN_IMAGE_SIZE_100:
851                         pw->image_size = 100;
852                         pw->thumb_gap = PAN_THUMB_GAP_HUGE;
853                         break;
854                 }
855
856         *width = 0;
857         *height = 0;
858         *scroll_x = 0;
859         *scroll_y = 0;
860
861         switch (pw->layout)
862                 {
863                 case PAN_LAYOUT_GRID:
864                 default:
865                         pan_grid_compute(pw, dir_fd, width, height);
866                         break;
867                 case PAN_LAYOUT_FOLDERS_LINEAR:
868                         pan_folder_tree_compute(pw, dir_fd, width, height);
869                         break;
870                 case PAN_LAYOUT_FOLDERS_FLOWER:
871                         pan_flower_compute(pw, dir_fd, width, height, scroll_x, scroll_y);
872                         break;
873                 case PAN_LAYOUT_CALENDAR:
874                         pan_calendar_compute(pw, dir_fd, width, height);
875                         break;
876                 case PAN_LAYOUT_TIMELINE:
877                         pan_timeline_compute(pw, dir_fd, width, height);
878                         break;
879                 }
880
881         pan_cache_free(pw);
882
883         DEBUG_1("computed %d objects", g_list_length(pw->list));
884 }
885
886 static GList *pan_layout_intersect_l(GList *list, GList *item_list,
887                                      gint x, gint y, gint width, gint height)
888 {
889         GList *work;
890
891         work = item_list;
892         while (work)
893                 {
894                 PanItem *pi;
895                 gint rx, ry, rw, rh;
896
897                 pi = work->data;
898                 work = work->next;
899
900                 if (util_clip_region(x, y, width, height,
901                                      pi->x, pi->y, pi->width, pi->height,
902                                      &rx, &ry, &rw, &rh))
903                         {
904                         list = g_list_prepend(list, pi);
905                         }
906                 }
907
908         return list;
909 }
910
911 GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height)
912 {
913         GList *list = NULL;
914         GList *grid;
915         PanGrid *pg = NULL;
916
917         grid = pw->list_grid;
918         while (grid && !pg)
919                 {
920                 pg = grid->data;
921                 grid = grid->next;
922
923                 if (x < pg->x || x + width > pg->x + pg->w ||
924                     y < pg->y || y + height > pg->y + pg->h)
925                         {
926                         pg = NULL;
927                         }
928                 }
929
930         list = pan_layout_intersect_l(list, pw->list, x, y, width, height);
931
932         if (pg)
933                 {
934                 list = pan_layout_intersect_l(list, pg->list, x, y, width, height);
935                 }
936         else
937                 {
938                 list = pan_layout_intersect_l(list, pw->list_static, x, y, width, height);
939                 }
940
941         return list;
942 }
943
944 void pan_layout_resize(PanWindow *pw)
945 {
946         gint width = 0;
947         gint height = 0;
948         GList *work;
949         PixbufRenderer *pr;
950
951         work = pw->list;
952         while (work)
953                 {
954                 PanItem *pi;
955
956                 pi = work->data;
957                 work = work->next;
958
959                 if (width < pi->x + pi->width) width = pi->x + pi->width;
960                 if (height < pi->y + pi->height) height = pi->y + pi->height;
961                 }
962         work = pw->list_static;
963         while (work)
964                 {
965                 PanItem *pi;
966
967                 pi = work->data;
968                 work = work->next;
969
970                 if (width < pi->x + pi->width) width = pi->x + pi->width;
971                 if (height < pi->y + pi->height) height = pi->y + pi->height;
972                 }
973
974         width += PAN_BOX_BORDER * 2;
975         height += PAN_BOX_BORDER * 2;
976
977         pr = PIXBUF_RENDERER(pw->imd->pr);
978         if (width < pr->window_width) width = pr->window_width;
979         if (height < pr->window_width) height = pr->window_height;
980
981         pixbuf_renderer_set_tiles_size(PIXBUF_RENDERER(pw->imd->pr), width, height);
982 }
983
984 static gint pan_layout_update_idle_cb(gpointer data)
985 {
986         PanWindow *pw = data;
987         gint width;
988         gint height;
989         gint scroll_x;
990         gint scroll_y;
991
992         if (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE ||
993             (pw->exif_date_enable && (pw->layout == PAN_LAYOUT_TIMELINE || pw->layout == PAN_LAYOUT_CALENDAR)))
994                 {
995                 if (!pw->cache_list && !pw->cache_todo)
996                         {
997                         pan_cache_fill(pw, pw->dir_fd);
998                         if (pw->cache_todo)
999                                 {
1000                                 pan_window_message(pw, _("Reading image data..."));
1001                                 return TRUE;
1002                                 }
1003                         }
1004                 if (pw->cache_todo)
1005                         {
1006                         pw->cache_count++;
1007                         pw->cache_tick++;
1008                         if (pw->cache_count == pw->cache_total)
1009                                 {
1010                                 pan_window_message(pw, _("Sorting..."));
1011                                 }
1012                         else if (pw->cache_tick > 9)
1013                                 {
1014                                 gchar *buf;
1015
1016                                 buf = g_strdup_printf("%s %d / %d", _("Reading image data..."),
1017                                                       pw->cache_count, pw->cache_total);
1018                                 pan_window_message(pw, buf);
1019                                 g_free(buf);
1020
1021                                 pw->cache_tick = 0;
1022                                 }
1023
1024                         if (pan_cache_step(pw)) return TRUE;
1025
1026                         pw->idle_id = 0;
1027                         return FALSE;
1028                         }
1029                 }
1030
1031         pan_layout_compute(pw, pw->dir_fd, &width, &height, &scroll_x, &scroll_y);
1032
1033         pan_window_zoom_limit(pw);
1034
1035         if (width > 0 && height > 0)
1036                 {
1037                 gdouble align;
1038
1039                 DEBUG_1("Canvas size is %d x %d", width, height);
1040
1041                 pan_grid_build(pw, width, height, 1000);
1042
1043                 pixbuf_renderer_set_tiles(PIXBUF_RENDERER(pw->imd->pr), width, height,
1044                                           PAN_TILE_SIZE, PAN_TILE_SIZE, 10,
1045                                           pan_window_request_tile_cb,
1046                                           pan_window_dispose_tile_cb, pw, 1.0);
1047
1048                 if (scroll_x == 0 && scroll_y == 0)
1049                         {
1050                         align = 0.0;
1051                         }
1052                 else
1053                         {
1054                         align = 0.5;
1055                         }
1056                 pixbuf_renderer_scroll_to_point(PIXBUF_RENDERER(pw->imd->pr), scroll_x, scroll_y, align, align);
1057                 }
1058
1059         pan_window_message(pw, NULL);
1060
1061         pw->idle_id = 0;
1062         return FALSE;
1063 }
1064
1065 static void pan_layout_update_idle(PanWindow *pw)
1066 {
1067         if (!pw->idle_id)
1068                 {
1069                 pw->idle_id = g_idle_add(pan_layout_update_idle_cb, pw);
1070                 }
1071 }
1072
1073 static void pan_layout_update(PanWindow *pw)
1074 {
1075         pan_window_message(pw, _("Sorting images..."));
1076         pan_layout_update_idle(pw);
1077 }
1078
1079 static void pan_layout_set_fd(PanWindow *pw, FileData *dir_fd)
1080 {
1081         if (!dir_fd) return;
1082
1083         if (strcmp(dir_fd->path, G_DIR_SEPARATOR_S) == 0)
1084                 {
1085                 pan_warning_folder(dir_fd->path, pw->window);
1086                 return;
1087                 }
1088
1089         file_data_unref(pw->dir_fd);
1090         pw->dir_fd = file_data_ref(dir_fd);
1091
1092         pan_layout_update(pw);
1093 }
1094
1095
1096 /*
1097  *-----------------------------------------------------------------------------
1098  * keyboard handlers
1099  *-----------------------------------------------------------------------------
1100  */
1101
1102 FileData *pan_menu_click_fd(PanWindow *pw)
1103 {
1104         if (pw->click_pi && pw->click_pi->fd) return pw->click_pi->fd;
1105         return NULL;
1106 }
1107
1108 static void pan_window_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
1109 {
1110         PanWindow *pw = data;
1111
1112         gdk_window_get_origin(gtk_widget_get_window(pw->imd->pr), x, y);
1113         popup_menu_position_clamp(menu, x, y, 0);
1114 }
1115
1116 static gboolean pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1117 {
1118         PanWindow *pw = data;
1119         PixbufRenderer *pr;
1120         FileData *fd;
1121         gboolean stop_signal = FALSE;
1122         GtkWidget *menu;
1123         GtkWidget *imd_widget;
1124         gint x = 0;
1125         gint y = 0;
1126         gint focused;
1127         gint on_entry;
1128
1129         pr = PIXBUF_RENDERER(pw->imd->pr);
1130         fd = pan_menu_click_fd(pw);
1131
1132         imd_widget = gtk_container_get_focus_child(GTK_CONTAINER(pw->imd->widget));
1133         focused = (pw->fs || (imd_widget && gtk_widget_has_focus(imd_widget)));
1134         on_entry = (gtk_widget_has_focus(pw->path_entry) ||
1135                     gtk_widget_has_focus(pw->search_entry));
1136
1137         if (focused)
1138                 {
1139                 stop_signal = TRUE;
1140                 switch (event->keyval)
1141                         {
1142                         case GDK_KEY_Left: case GDK_KEY_KP_Left:
1143                                 x -= 1;
1144                                 break;
1145                         case GDK_KEY_Right: case GDK_KEY_KP_Right:
1146                                 x += 1;
1147                                 break;
1148                         case GDK_KEY_Up: case GDK_KEY_KP_Up:
1149                                 y -= 1;
1150                                 break;
1151                         case GDK_KEY_Down: case GDK_KEY_KP_Down:
1152                                 y += 1;
1153                                 break;
1154                         case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
1155                                 pixbuf_renderer_scroll(pr, 0, 0 - pr->vis_height / 2);
1156                                 break;
1157                         case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
1158                                 pixbuf_renderer_scroll(pr, 0, pr->vis_height / 2);
1159                                 break;
1160                         case GDK_KEY_Home: case GDK_KEY_KP_Home:
1161                                 pixbuf_renderer_scroll(pr, 0 - pr->vis_width / 2, 0);
1162                                 break;
1163                         case GDK_KEY_End: case GDK_KEY_KP_End:
1164                                 pixbuf_renderer_scroll(pr, pr->vis_width / 2, 0);
1165                                 break;
1166                         default:
1167                                 stop_signal = FALSE;
1168                                 break;
1169                         }
1170
1171                 if (x != 0 || y!= 0)
1172                         {
1173                         if (event->state & GDK_SHIFT_MASK)
1174                                 {
1175                                 x *= 3;
1176                                 y *= 3;
1177                                 }
1178                         keyboard_scroll_calc(&x, &y, event);
1179                         pixbuf_renderer_scroll(pr, x, y);
1180                         }
1181                 }
1182
1183         if (stop_signal) return stop_signal;
1184
1185         if (event->state & GDK_CONTROL_MASK)
1186                 {
1187                 stop_signal = TRUE;
1188                 switch (event->keyval)
1189                         {
1190                         case '1':
1191                         case '2':
1192                         case '3':
1193                         case '4':
1194                         case '5':
1195                         case '6':
1196                         case '7':
1197                         case '8':
1198                         case '9':
1199                         case '0':
1200                                 break;
1201                         case 'C': case 'c':
1202                                 if (fd) file_util_copy(fd, NULL, NULL, GTK_WIDGET(pr));
1203                                 break;
1204                         case 'M': case 'm':
1205                                 if (fd) file_util_move(fd, NULL, NULL, GTK_WIDGET(pr));
1206                                 break;
1207                         case 'R': case 'r':
1208                                 if (fd) file_util_rename(fd, NULL, GTK_WIDGET(pr));
1209                                 break;
1210                         case 'D': case 'd':
1211                                 if (fd) file_util_delete(fd, NULL, GTK_WIDGET(pr));
1212                                 break;
1213                         case 'F': case 'f':
1214                                 pan_search_toggle_visible(pw, TRUE);
1215                                 break;
1216                         case 'G': case 'g':
1217                                 pan_search_activate(pw);
1218                                 break;
1219                         case 'W': case 'w':
1220                                 pan_window_close(pw);
1221                                 break;
1222                         default:
1223                                 stop_signal = FALSE;
1224                                 break;
1225                         }
1226                 }
1227         else
1228                 {
1229                 stop_signal = TRUE;
1230                 switch (event->keyval)
1231                         {
1232                         case GDK_KEY_Escape:
1233                                 if (pw->fs)
1234                                         {
1235                                         pan_fullscreen_toggle(pw, TRUE);
1236                                         }
1237                                 else
1238                                         {
1239                                         pan_search_toggle_visible(pw, FALSE);
1240                                         }
1241                                 break;
1242                         default:
1243                                 stop_signal = FALSE;
1244                                 break;
1245                         }
1246
1247                 if (stop_signal) return stop_signal;
1248
1249                 if (!on_entry)
1250                         {
1251                         stop_signal = TRUE;
1252                         switch (event->keyval)
1253                                 {
1254                                 case '+': case '=': case GDK_KEY_KP_Add:
1255                                         pixbuf_renderer_zoom_adjust(pr, ZOOM_INCREMENT);
1256                                         break;
1257                                 case '-': case GDK_KEY_KP_Subtract:
1258                                         pixbuf_renderer_zoom_adjust(pr, -ZOOM_INCREMENT);
1259                                         break;
1260                                 case 'Z': case 'z': case GDK_KEY_KP_Divide: case '1':
1261                                         pixbuf_renderer_zoom_set(pr, 1.0);
1262                                         break;
1263                                 case '2':
1264                                         pixbuf_renderer_zoom_set(pr, 2.0);
1265                                         break;
1266                                 case '3':
1267                                         pixbuf_renderer_zoom_set(pr, 3.0);
1268                                         break;
1269                                 case '4':
1270                                         pixbuf_renderer_zoom_set(pr, 4.0);
1271                                         break;
1272                                 case '7':
1273                                         pixbuf_renderer_zoom_set(pr, -4.0);
1274                                         break;
1275                                 case '8':
1276                                         pixbuf_renderer_zoom_set(pr, -3.0);
1277                                         break;
1278                                 case '9':
1279                                         pixbuf_renderer_zoom_set(pr, -2.0);
1280                                         break;
1281                                 case 'F': case 'f':
1282                                 case 'V': case 'v':
1283                                 case GDK_KEY_F11:
1284                                         pan_fullscreen_toggle(pw, FALSE);
1285                                         break;
1286                                 case 'I': case 'i':
1287                                         break;
1288                                 case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
1289                                         break;
1290                                 case GDK_KEY_Menu:
1291                                 case GDK_KEY_F10:
1292                                         menu = pan_popup_menu(pw);
1293                                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1294                                                        pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME);
1295                                         break;
1296                                 case '/':
1297                                         pan_search_toggle_visible(pw, TRUE);
1298                                         break;
1299                                 case GDK_KEY_F1:
1300                                         help_window_show("GuideReferenceKeyboardShortcuts.html#PanViewKeyboardShortcuts");
1301                                         break;
1302                                 default:
1303                                         stop_signal = FALSE;
1304                                         break;
1305                                 }
1306                         }
1307                 }
1308
1309         return stop_signal;
1310 }
1311
1312 /*
1313  *-----------------------------------------------------------------------------
1314  * info popup
1315  *-----------------------------------------------------------------------------
1316  */
1317
1318 static void pan_info_add_exif(PanTextAlignment *ta, FileData *fd)
1319 {
1320
1321         if (!fd) return;
1322
1323         pan_text_alignment_add(ta, NULL, NULL);
1324 }
1325
1326
1327 void pan_info_update(PanWindow *pw, PanItem *pi)
1328 {
1329         PanTextAlignment *ta;
1330         PanItem *pbox;
1331         PanItem *p;
1332         gchar *buf;
1333         gint x1, y1, x2, y2, x3, y3;
1334         gint x, y, w, h;
1335
1336         if (pw->click_pi == pi) return;
1337         if (pi && !pi->fd) pi = NULL;
1338
1339         while ((p = pan_item_find_by_key(pw, PAN_ITEM_NONE, "info"))) pan_item_remove(pw, p);
1340         pw->click_pi = pi;
1341
1342         if (!pi) return;
1343
1344         DEBUG_1("info set to %s", pi->fd->path);
1345
1346         pbox = pan_item_box_new(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10,
1347                                 PAN_POPUP_BORDER,
1348                                 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
1349                                 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1350         pan_item_set_key(pbox, "info");
1351
1352         if (pi->type == PAN_ITEM_THUMB && pi->pixbuf)
1353                 {
1354                 w = gdk_pixbuf_get_width(pi->pixbuf);
1355                 h = gdk_pixbuf_get_height(pi->pixbuf);
1356
1357                 x1 = pi->x + pi->width - (pi->width - w) / 2 - 8;
1358                 y1 = pi->y + (pi->height - h) / 2 + 8;
1359                 }
1360         else
1361                 {
1362                 x1 = pi->x + pi->width - 8;
1363                 y1 = pi->y + 8;
1364                 }
1365
1366         x2 = pbox->x + 1;
1367         y2 = pbox->y + 36;
1368         x3 = pbox->x + 1;
1369         y3 = pbox->y + 12;
1370         util_clip_triangle(x1, y1, x2, y2, x3, y3,
1371                            &x, &y, &w, &h);
1372
1373         p = pan_item_tri_new(pw, NULL, x, y, w, h,
1374                              x1, y1, x2, y2, x3, y3,
1375                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA);
1376         pan_item_tri_border(p, PAN_BORDER_1 | PAN_BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1377         pan_item_set_key(p, "info");
1378         pan_item_added(pw, p);
1379
1380         ta = pan_text_alignment_new(pw, pbox->x + PREF_PAD_BORDER, pbox->y + PREF_PAD_BORDER, "info");
1381
1382         pan_text_alignment_add(ta, _("Filename:"), pi->fd->name);
1383         buf = remove_level_from_path(pi->fd->path);
1384         pan_text_alignment_add(ta, _("Location:"), buf);
1385         g_free(buf);
1386         pan_text_alignment_add(ta, _("Date:"), text_from_time(pi->fd->date));
1387         buf = text_from_size(pi->fd->size);
1388         pan_text_alignment_add(ta, _("Size:"), buf);
1389         g_free(buf);
1390
1391         if (pw->info_includes_exif)
1392                 {
1393                 pan_info_add_exif(ta, pi->fd);
1394                 }
1395
1396         pan_text_alignment_calc(ta, pbox);
1397         pan_text_alignment_free(ta);
1398
1399         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
1400         pan_item_added(pw, pbox);
1401
1402         if (pw->info_image_size > PAN_IMAGE_SIZE_THUMB_NONE)
1403                 {
1404                 gint iw, ih;
1405                 if (image_load_dimensions(pi->fd, &iw, &ih))
1406                         {
1407                         gint scale = 25;
1408
1409                         switch (pw->info_image_size)
1410                                 {
1411                                 case PAN_IMAGE_SIZE_10:
1412                                         scale = 10;
1413                                         break;
1414                                 case PAN_IMAGE_SIZE_25:
1415                                         scale = 25;
1416                                         break;
1417                                 case PAN_IMAGE_SIZE_33:
1418                                         scale = 33;
1419                                         break;
1420                                 case PAN_IMAGE_SIZE_50:
1421                                         scale = 50;
1422                                         break;
1423                                 case PAN_IMAGE_SIZE_100:
1424                                         scale = 100;
1425                                         break;
1426                                 }
1427
1428                         iw = MAX(1, iw * scale / 100);
1429                         ih = MAX(1, ih * scale / 100);
1430
1431                         pbox = pan_item_box_new(pw, NULL, pbox->x, pbox->y + pbox->height + 8, 10, 10,
1432                                                 PAN_POPUP_BORDER,
1433                                                 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
1434                                                 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1435                         pan_item_set_key(pbox, "info");
1436
1437                         p = pan_item_image_new(pw, file_data_new_group(pi->fd->path),
1438                                                pbox->x + PREF_PAD_BORDER, pbox->y + PREF_PAD_BORDER, iw, ih);
1439                         pan_item_set_key(p, "info");
1440                         pan_item_size_by_item(pbox, p, PREF_PAD_BORDER);
1441
1442                         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
1443                         pan_item_added(pw, pbox);
1444                         }
1445                 }
1446
1447         pan_layout_resize(pw);
1448 }
1449
1450
1451 /*
1452  *-----------------------------------------------------------------------------
1453  * main window
1454  *-----------------------------------------------------------------------------
1455  */
1456
1457 static void button_cb(PixbufRenderer *pr, GdkEventButton *event, gpointer data)
1458 {
1459         PanWindow *pw = data;
1460         PanItem *pi = NULL;
1461         GtkWidget *menu;
1462         gint rx, ry;
1463
1464         rx = ry = 0;
1465         if (pr->scale)
1466                 {
1467                 rx = (gdouble)(pr->x_scroll + event->x - pr->x_offset) / pr->scale;
1468                 ry = (gdouble)(pr->y_scroll + event->y - pr->y_offset) / pr->scale;
1469                 }
1470
1471         pi = pan_item_find_by_coord(pw, PAN_ITEM_BOX, rx, ry, "info");
1472         if (pi && event->button == MOUSE_BUTTON_LEFT)
1473                 {
1474                 pan_info_update(pw, NULL);
1475                 return;
1476                 }
1477
1478         pi = pan_item_find_by_coord(pw, (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB,
1479                                     rx, ry, NULL);
1480
1481         switch (event->button)
1482                 {
1483                 case MOUSE_BUTTON_LEFT:
1484                         pan_info_update(pw, pi);
1485
1486                         if (!pi && pw->layout == PAN_LAYOUT_CALENDAR)
1487                                 {
1488                                 pi = pan_item_find_by_coord(pw, PAN_ITEM_BOX, rx, ry, "day");
1489                                 pan_calendar_update(pw, pi);
1490                                 }
1491                         break;
1492                 case MOUSE_BUTTON_MIDDLE:
1493                         break;
1494                 case MOUSE_BUTTON_RIGHT:
1495                         pan_info_update(pw, pi);
1496                         menu = pan_popup_menu(pw);
1497                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
1498                         break;
1499                 default:
1500                         break;
1501                 }
1502 }
1503
1504 static void scroll_cb(PixbufRenderer *pr, GdkEventScroll *event, gpointer data)
1505 {
1506         gint w, h;
1507
1508         w = pr->vis_width;
1509         h = pr->vis_height;
1510
1511         if (!(event->state & GDK_SHIFT_MASK))
1512                 {
1513                 w /= 3;
1514                 h /= 3;
1515                 }
1516
1517         if (event->state & GDK_CONTROL_MASK)
1518                 {
1519                 switch (event->direction)
1520                         {
1521                         case GDK_SCROLL_UP:
1522                                 pixbuf_renderer_zoom_adjust_at_point(pr, ZOOM_INCREMENT,
1523                                                                      (gint)event->x, (gint)event->y);
1524                                 break;
1525                         case GDK_SCROLL_DOWN:
1526                                 pixbuf_renderer_zoom_adjust_at_point(pr, -ZOOM_INCREMENT,
1527                                                                      (gint)event->x, (gint)event->y);
1528                                 break;
1529                         default:
1530                                 break;
1531                         }
1532                 }
1533         else
1534                 {
1535                 switch (event->direction)
1536                         {
1537                         case GDK_SCROLL_UP:
1538                                 pixbuf_renderer_scroll(pr, 0, -h);
1539                                 break;
1540                         case GDK_SCROLL_DOWN:
1541                                 pixbuf_renderer_scroll(pr, 0, h);
1542                                 break;
1543                         case GDK_SCROLL_LEFT:
1544                                 pixbuf_renderer_scroll(pr, -w, 0);
1545                                 break;
1546                         case GDK_SCROLL_RIGHT:
1547                                 pixbuf_renderer_scroll(pr, w, 0);
1548                                 break;
1549                         default:
1550                                 break;
1551                         }
1552                 }
1553 }
1554
1555 static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd)
1556 {
1557         g_signal_connect(G_OBJECT(imd->pr), "clicked",
1558                          G_CALLBACK(button_cb), pw);
1559         g_signal_connect(G_OBJECT(imd->pr), "scroll_event",
1560                          G_CALLBACK(scroll_cb), pw);
1561 }
1562
1563 static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data)
1564 {
1565         PanWindow *pw = data;
1566
1567         pw->fs = NULL;
1568         pw->imd = pw->imd_normal;
1569 }
1570
1571 static void pan_fullscreen_toggle(PanWindow *pw, gboolean force_off)
1572 {
1573         if (force_off && !pw->fs) return;
1574
1575         if (pw->fs)
1576                 {
1577                 fullscreen_stop(pw->fs);
1578                 }
1579         else
1580                 {
1581                 pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw);
1582                 pan_image_set_buttons(pw, pw->fs->imd);
1583                 g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event",
1584                                  G_CALLBACK(pan_window_key_press_cb), pw);
1585
1586                 pw->imd = pw->fs->imd;
1587                 }
1588 }
1589
1590 static void pan_window_image_zoom_cb(PixbufRenderer *pr, gdouble zoom, gpointer data)
1591 {
1592         PanWindow *pw = data;
1593         gchar *text;
1594
1595         text = image_zoom_get_as_text(pw->imd);
1596         gtk_label_set_text(GTK_LABEL(pw->label_zoom), text);
1597         g_free(text);
1598 }
1599
1600 static void pan_window_image_scroll_notify_cb(PixbufRenderer *pr, gpointer data)
1601 {
1602         PanWindow *pw = data;
1603         GtkAdjustment *adj;
1604         GdkRectangle rect;
1605         gint width, height;
1606
1607         if (pr->scale == 0.0) return;
1608
1609         pixbuf_renderer_get_visible_rect(pr, &rect);
1610         pixbuf_renderer_get_image_size(pr, &width, &height);
1611
1612         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h));
1613         gtk_adjustment_set_page_size(adj, rect.width);
1614         gtk_adjustment_set_page_increment(adj, gtk_adjustment_get_page_size(adj) / 2.0);
1615         gtk_adjustment_set_step_increment(adj, 48.0 / pr->scale);
1616         gtk_adjustment_set_lower(adj, 0.0);
1617         gtk_adjustment_set_upper(adj, MAX((gdouble)width, 1.0));
1618         gtk_adjustment_set_value(adj, (gdouble)rect.x);
1619
1620         pref_signal_block_data(pw->scrollbar_h, pw);
1621         gtk_adjustment_changed(adj);
1622         gtk_adjustment_value_changed(adj);
1623         pref_signal_unblock_data(pw->scrollbar_h, pw);
1624
1625         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v));
1626         gtk_adjustment_set_page_size(adj, rect.height);
1627         gtk_adjustment_set_page_increment(adj, gtk_adjustment_get_page_size(adj) / 2.0);
1628         gtk_adjustment_set_step_increment(adj, 48.0 / pr->scale);
1629         gtk_adjustment_set_lower(adj, 0.0);
1630         gtk_adjustment_set_upper(adj, MAX((gdouble)height, 1.0));
1631         gtk_adjustment_set_value(adj, (gdouble)rect.y);
1632
1633         pref_signal_block_data(pw->scrollbar_v, pw);
1634         gtk_adjustment_changed(adj);
1635         gtk_adjustment_value_changed(adj);
1636         pref_signal_unblock_data(pw->scrollbar_v, pw);
1637 }
1638
1639 static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data)
1640 {
1641         PanWindow *pw = data;
1642         PixbufRenderer *pr;
1643         gint x;
1644
1645         pr = PIXBUF_RENDERER(pw->imd_normal->pr);
1646
1647         if (!pr->scale) return;
1648
1649         x = (gint)gtk_range_get_value(range);
1650
1651         pixbuf_renderer_scroll_to_point(pr, x, (gint)((gdouble)pr->y_scroll / pr->scale), 0.0, 0.0);
1652 }
1653
1654 static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data)
1655 {
1656         PanWindow *pw = data;
1657         PixbufRenderer *pr;
1658         gint y;
1659
1660         pr = PIXBUF_RENDERER(pw->imd_normal->pr);
1661
1662         if (!pr->scale) return;
1663
1664         y = (gint)gtk_range_get_value(range);
1665
1666         pixbuf_renderer_scroll_to_point(pr, (gint)((gdouble)pr->x_scroll / pr->scale), y, 0.0, 0.0);
1667 }
1668
1669 static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data)
1670 {
1671         PanWindow *pw = data;
1672
1673         pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
1674         pan_layout_update(pw);
1675 }
1676
1677 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
1678 {
1679         PanWindow *pw = data;
1680
1681         pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
1682         pan_layout_update(pw);
1683 }
1684
1685 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
1686 {
1687         PanWindow *pw = data;
1688         gchar *path;
1689
1690         path = remove_trailing_slash(new_text);
1691         parse_out_relatives(path);
1692
1693         if (!isdir(path))
1694                 {
1695                 warning_dialog(_("Folder not found"),
1696                                _("The entered path is not a folder"),
1697                                GTK_STOCK_DIALOG_WARNING, pw->path_entry);
1698                 }
1699         else
1700                 {
1701                 FileData *dir_fd = file_data_new_dir(path);
1702                 tab_completion_append_to_history(pw->path_entry, path);
1703
1704                 pan_layout_set_fd(pw, dir_fd);
1705                 file_data_unref(dir_fd);
1706                 }
1707
1708         g_free(path);
1709 }
1710
1711 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
1712 {
1713         PanWindow *pw = data;
1714         gchar *text;
1715
1716         if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return;
1717
1718         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
1719         pan_window_entry_activate_cb(text, pw);
1720         g_free(text);
1721 }
1722
1723 static void pan_window_close(PanWindow *pw)
1724 {
1725         pan_window_list = g_list_remove(pan_window_list, pw);
1726
1727         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_EXIF_PAN_DATE, pw->exif_date_enable);
1728         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_INFO_IMAGE, pw->info_image_size);
1729         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_INFO_EXIF, pw->info_includes_exif);
1730
1731         if (pw->idle_id)
1732                 {
1733                 g_source_remove(pw->idle_id);
1734                 }
1735
1736         pan_fullscreen_toggle(pw, TRUE);
1737         gtk_widget_destroy(pw->window);
1738
1739         pan_window_items_free(pw);
1740         pan_cache_free(pw);
1741
1742         file_data_unref(pw->dir_fd);
1743
1744         g_free(pw);
1745 }
1746
1747 static gboolean pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
1748 {
1749         PanWindow *pw = data;
1750
1751         pan_window_close(pw);
1752         return TRUE;
1753 }
1754
1755 static void pan_window_new_real(FileData *dir_fd)
1756 {
1757         PanWindow *pw;
1758         GtkWidget *vbox;
1759         GtkWidget *box;
1760         GtkWidget *combo;
1761         GtkWidget *hbox;
1762         GtkWidget *frame;
1763         GtkWidget *table;
1764         GdkGeometry geometry;
1765
1766         pw = g_new0(PanWindow, 1);
1767
1768         pw->dir_fd = file_data_ref(dir_fd);
1769         pw->layout = PAN_LAYOUT_TIMELINE;
1770         pw->size = PAN_IMAGE_SIZE_THUMB_NORMAL;
1771         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
1772         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
1773
1774         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_EXIF_PAN_DATE, &pw->exif_date_enable))
1775                 {
1776                 pw->exif_date_enable = FALSE;
1777                 }
1778         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_INFO_IMAGE, &pw->info_image_size))
1779                 {
1780                 pw->info_image_size = PAN_IMAGE_SIZE_THUMB_NONE;
1781                 }
1782         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_INFO_EXIF, &pw->info_includes_exif))
1783                 {
1784                 pw->info_includes_exif = TRUE;
1785                 }
1786
1787         pw->ignore_symlinks = TRUE;
1788
1789         pw->idle_id = 0;
1790
1791         pw->window = window_new(GTK_WINDOW_TOPLEVEL, "panview", NULL, NULL, _("Pan View"));
1792
1793         geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
1794         geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
1795         gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE);
1796
1797         gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE);
1798         gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0);
1799
1800         vbox = gtk_vbox_new(FALSE, 0);
1801         gtk_container_add(GTK_CONTAINER(pw->window), vbox);
1802         gtk_widget_show(vbox);
1803
1804         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1805
1806         pref_spacer(box, 0);
1807         pref_label_new(box, _("Location:"));
1808         combo = tab_completion_new_with_history(&pw->path_entry, dir_fd->path, "pan_view_path", -1,
1809                                                 pan_window_entry_activate_cb, pw);
1810         g_signal_connect(G_OBJECT(gtk_widget_get_parent(pw->path_entry)), "changed",
1811                          G_CALLBACK(pan_window_entry_change_cb), pw);
1812         gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
1813         gtk_widget_show(combo);
1814
1815         combo = gtk_combo_box_text_new();
1816         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Timeline"));
1817         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Calendar"));
1818         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Folders"));
1819         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Folders (flower)"));
1820         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Grid"));
1821
1822         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout);
1823         g_signal_connect(G_OBJECT(combo), "changed",
1824                          G_CALLBACK(pan_window_layout_change_cb), pw);
1825         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
1826         gtk_widget_show(combo);
1827
1828         combo = gtk_combo_box_text_new();
1829         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Dots"));
1830         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("No Images"));
1831         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Small Thumbnails"));
1832         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Normal Thumbnails"));
1833         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Large Thumbnails"));
1834         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:10 (10%)"));
1835         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:4 (25%)"));
1836         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:3 (33%)"));
1837         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:2 (50%)"));
1838         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:1 (100%)"));
1839
1840         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size);
1841         g_signal_connect(G_OBJECT(combo), "changed",
1842                          G_CALLBACK(pan_window_layout_size_cb), pw);
1843         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
1844         gtk_widget_show(combo);
1845
1846         table = pref_table_new(vbox, 2, 2, FALSE, TRUE);
1847         gtk_table_set_row_spacings(GTK_TABLE(table), 2);
1848         gtk_table_set_col_spacings(GTK_TABLE(table), 2);
1849
1850         pw->imd = image_new(TRUE);
1851         pw->imd_normal = pw->imd;
1852
1853         g_signal_connect(G_OBJECT(pw->imd->pr), "zoom",
1854                          G_CALLBACK(pan_window_image_zoom_cb), pw);
1855         g_signal_connect(G_OBJECT(pw->imd->pr), "scroll_notify",
1856                          G_CALLBACK(pan_window_image_scroll_notify_cb), pw);
1857
1858         gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1,
1859                          GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
1860         gtk_widget_show(GTK_WIDGET(pw->imd->widget));
1861
1862         pan_window_dnd_init(pw);
1863
1864         pan_image_set_buttons(pw, pw->imd);
1865
1866         pw->scrollbar_h = gtk_hscrollbar_new(NULL);
1867         g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed",
1868                          G_CALLBACK(pan_window_scrollbar_h_value_cb), pw);
1869         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2,
1870                          GTK_FILL | GTK_EXPAND, 0, 0, 0);
1871         gtk_widget_show(pw->scrollbar_h);
1872
1873         pw->scrollbar_v = gtk_vscrollbar_new(NULL);
1874         g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed",
1875                          G_CALLBACK(pan_window_scrollbar_v_value_cb), pw);
1876         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1,
1877                          0, GTK_FILL | GTK_EXPAND, 0, 0);
1878         gtk_widget_show(pw->scrollbar_v);
1879
1880         /* find bar */
1881
1882         pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
1883         gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2);
1884
1885         pref_spacer(pw->search_box, 0);
1886         pref_label_new(pw->search_box, _("Find:"));
1887
1888         hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
1889         gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0);
1890         gtk_widget_show(hbox);
1891
1892         combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1,
1893                                                 pan_search_activate_cb, pw);
1894         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
1895         gtk_widget_show(combo);
1896
1897         pw->search_label = gtk_label_new("");
1898         gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0);
1899         gtk_widget_show(pw->search_label);
1900
1901         /* status bar */
1902
1903         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
1904
1905         frame = gtk_frame_new(NULL);
1906         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
1907         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
1908         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
1909         gtk_widget_show(frame);
1910
1911         hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
1912         gtk_container_add(GTK_CONTAINER(frame), hbox);
1913         gtk_widget_show(hbox);
1914
1915         pref_spacer(hbox, 0);
1916         pw->label_message = pref_label_new(hbox, "");
1917
1918         frame = gtk_frame_new(NULL);
1919         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
1920         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
1921         gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0);
1922         gtk_widget_show(frame);
1923
1924         pw->label_zoom = gtk_label_new("");
1925         gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
1926         gtk_widget_show(pw->label_zoom);
1927
1928         pw->search_button = gtk_toggle_button_new();
1929         gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE);
1930         gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE);
1931         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
1932         gtk_container_add(GTK_CONTAINER(pw->search_button), hbox);
1933         gtk_widget_show(hbox);
1934         pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
1935         gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0);
1936         gtk_widget_show(pw->search_button_arrow);
1937         pref_label_new(hbox, _("Find"));
1938
1939         gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0);
1940         gtk_widget_show(pw->search_button);
1941         g_signal_connect(G_OBJECT(pw->search_button), "clicked",
1942                          G_CALLBACK(pan_search_toggle_cb), pw);
1943
1944         g_signal_connect(G_OBJECT(pw->window), "delete_event",
1945                          G_CALLBACK(pan_window_delete_cb), pw);
1946         g_signal_connect(G_OBJECT(pw->window), "key_press_event",
1947                          G_CALLBACK(pan_window_key_press_cb), pw);
1948
1949         gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
1950
1951         pan_layout_update(pw);
1952
1953         gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
1954         gtk_widget_show(pw->window);
1955
1956         pan_window_list = g_list_append(pan_window_list, pw);
1957 }
1958
1959 /*
1960  *-----------------------------------------------------------------------------
1961  * peformance warnings
1962  *-----------------------------------------------------------------------------
1963  */
1964
1965 static void pan_warning_ok_cb(GenericDialog *gd, gpointer data)
1966 {
1967         FileData *dir_fd = data;
1968
1969         generic_dialog_close(gd);
1970
1971         pan_window_new_real(dir_fd);
1972         file_data_unref(dir_fd);
1973 }
1974
1975 static void pan_warning_hide_cb(GtkWidget *button, gpointer data)
1976 {
1977         gboolean hide_dlg;
1978
1979         hide_dlg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
1980         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, hide_dlg);
1981 }
1982
1983 static gboolean pan_warning(FileData *dir_fd)
1984 {
1985         GenericDialog *gd;
1986         GtkWidget *box;
1987         GtkWidget *group;
1988         GtkWidget *button;
1989         GtkWidget *ct_button;
1990         gboolean hide_dlg;
1991
1992         if (dir_fd && strcmp(dir_fd->path, G_DIR_SEPARATOR_S) == 0)
1993                 {
1994                 pan_warning_folder(dir_fd->path, NULL);
1995                 return TRUE;
1996                 }
1997
1998         if (options->thumbnails.enable_caching &&
1999             options->thumbnails.spec_standard) return FALSE;
2000
2001         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, &hide_dlg)) hide_dlg = FALSE;
2002         if (hide_dlg) return FALSE;
2003
2004         gd = generic_dialog_new(_("Pan View Performance"), "pan_view_warning", NULL, FALSE,
2005                                 NULL, NULL);
2006         gd->data = file_data_ref(dir_fd);
2007         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
2008                                   pan_warning_ok_cb, TRUE);
2009
2010         box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO,
2011                                          _("Pan view performance may be poor."),
2012                                          _("To improve performance of thumbnails in the pan view the"
2013                                            " following options can be enabled. Note that both options"
2014                                            " must be enabled to notice a change in performance."));
2015
2016         group = pref_box_new(box, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
2017         pref_spacer(group, PREF_PAD_INDENT);
2018         group = pref_box_new(group, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
2019
2020         ct_button = pref_checkbox_new_int(group, _("Cache thumbnails"),
2021                                           options->thumbnails.enable_caching, &options->thumbnails.enable_caching);
2022         button = pref_checkbox_new_int(group, _("Use shared thumbnail cache"),
2023                                        options->thumbnails.spec_standard, &options->thumbnails.spec_standard);
2024         pref_checkbox_link_sensitivity(ct_button, button);
2025
2026         pref_line(box, 0);
2027
2028         pref_checkbox_new(box, _("Do not show this dialog again"), hide_dlg,
2029                           G_CALLBACK(pan_warning_hide_cb), NULL);
2030
2031         gtk_widget_show(gd->dialog);
2032
2033         return TRUE;
2034 }
2035
2036
2037 /*
2038  *-----------------------------------------------------------------------------
2039  * entry point
2040  *-----------------------------------------------------------------------------
2041  */
2042
2043 void pan_window_new(FileData *dir_fd)
2044 {
2045         if (pan_warning(dir_fd)) return;
2046
2047         pan_window_new_real(dir_fd);
2048 }
2049
2050
2051 /*
2052  *-----------------------------------------------------------------------------
2053  * menus
2054  *-----------------------------------------------------------------------------
2055  */
2056
2057 #define INFO_IMAGE_SIZE_KEY "image_size_data"
2058
2059
2060 static void pan_new_window_cb(GtkWidget *widget, gpointer data)
2061 {
2062         PanWindow *pw = data;
2063         FileData *fd;
2064
2065         fd = pan_menu_click_fd(pw);
2066         if (fd)
2067                 {
2068                 pan_fullscreen_toggle(pw, TRUE);
2069                 view_window_new(fd);
2070                 }
2071 }
2072
2073 static void pan_edit_cb(GtkWidget *widget, gpointer data)
2074 {
2075         PanWindow *pw;
2076         FileData *fd;
2077         const gchar *key = data;
2078
2079         pw = submenu_item_get_data(widget);
2080         if (!pw) return;
2081
2082         fd = pan_menu_click_fd(pw);
2083         if (fd)
2084                 {
2085                 if (!editor_window_flag_set(key))
2086                         {
2087                         pan_fullscreen_toggle(pw, TRUE);
2088                         }
2089                 file_util_start_editor_from_file(key, fd, pw->imd->widget);
2090                 }
2091 }
2092
2093 static void pan_zoom_in_cb(GtkWidget *widget, gpointer data)
2094 {
2095         PanWindow *pw = data;
2096
2097         image_zoom_adjust(pw->imd, ZOOM_INCREMENT);
2098 }
2099
2100 static void pan_zoom_out_cb(GtkWidget *widget, gpointer data)
2101 {
2102         PanWindow *pw = data;
2103
2104         image_zoom_adjust(pw->imd, -ZOOM_INCREMENT);
2105 }
2106
2107 static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data)
2108 {
2109         PanWindow *pw = data;
2110
2111         image_zoom_set(pw->imd, 1.0);
2112 }
2113
2114 static void pan_copy_cb(GtkWidget *widget, gpointer data)
2115 {
2116         PanWindow *pw = data;
2117         FileData *fd;
2118
2119         fd = pan_menu_click_fd(pw);
2120         if (fd) file_util_copy(fd, NULL, NULL, pw->imd->widget);
2121 }
2122
2123 static void pan_move_cb(GtkWidget *widget, gpointer data)
2124 {
2125         PanWindow *pw = data;
2126         FileData *fd;
2127
2128         fd = pan_menu_click_fd(pw);
2129         if (fd) file_util_move(fd, NULL, NULL, pw->imd->widget);
2130 }
2131
2132 static void pan_rename_cb(GtkWidget *widget, gpointer data)
2133 {
2134         PanWindow *pw = data;
2135         FileData *fd;
2136
2137         fd = pan_menu_click_fd(pw);
2138         if (fd) file_util_rename(fd, NULL, pw->imd->widget);
2139 }
2140
2141 static void pan_delete_cb(GtkWidget *widget, gpointer data)
2142 {
2143         PanWindow *pw = data;
2144         FileData *fd;
2145
2146         fd = pan_menu_click_fd(pw);
2147         if (fd) file_util_delete(fd, NULL, pw->imd->widget);
2148 }
2149
2150 static void pan_copy_path_cb(GtkWidget *widget, gpointer data)
2151 {
2152         PanWindow *pw = data;
2153         FileData *fd;
2154
2155         fd = pan_menu_click_fd(pw);
2156         if (fd) file_util_copy_path_to_clipboard(fd);
2157 }
2158
2159 static void pan_exif_date_toggle_cb(GtkWidget *widget, gpointer data)
2160 {
2161         PanWindow *pw = data;
2162
2163         pw->exif_date_enable = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
2164         pan_layout_update(pw);
2165 }
2166
2167 static void pan_info_toggle_exif_cb(GtkWidget *widget, gpointer data)
2168 {
2169         PanWindow *pw = data;
2170
2171         pw->info_includes_exif = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
2172         /* fixme: sync info now */
2173 }
2174
2175 static void pan_info_toggle_image_cb(GtkWidget *widget, gpointer data)
2176 {
2177         PanWindow *pw = data;
2178
2179         pw->info_image_size = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), INFO_IMAGE_SIZE_KEY));
2180         /* fixme: sync info now */
2181 }
2182
2183 static void pan_fullscreen_cb(GtkWidget *widget, gpointer data)
2184 {
2185         PanWindow *pw = data;
2186
2187         pan_fullscreen_toggle(pw, FALSE);
2188 }
2189
2190 static void pan_close_cb(GtkWidget *widget, gpointer data)
2191 {
2192         PanWindow *pw = data;
2193
2194         pan_window_close(pw);
2195 }
2196
2197 static void pan_popup_menu_destroy_cb(GtkWidget *widget, gpointer data)
2198 {
2199         GList *editmenu_fd_list = data;
2200
2201         filelist_free(editmenu_fd_list);
2202 }
2203
2204 static GList *pan_view_get_fd_list(PanWindow *pw)
2205 {
2206         GList *list = NULL;
2207         FileData *fd = pan_menu_click_fd(pw);
2208
2209         if (fd) list = g_list_prepend(filelist_copy(fd->sidecar_files), file_data_ref(fd));
2210
2211         return list;
2212 }
2213
2214 static GtkWidget *pan_popup_menu(PanWindow *pw)
2215 {
2216         GtkWidget *menu;
2217         GtkWidget *submenu;
2218         GtkWidget *item;
2219         gboolean active;
2220         GList *editmenu_fd_list;
2221
2222         active = (pw->click_pi != NULL);
2223
2224         menu = popup_menu_short_lived();
2225
2226         menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
2227                             G_CALLBACK(pan_zoom_in_cb), pw);
2228         menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
2229                             G_CALLBACK(pan_zoom_out_cb), pw);
2230         menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100,
2231                             G_CALLBACK(pan_zoom_1_1_cb), pw);
2232         menu_item_add_divider(menu);
2233
2234         editmenu_fd_list = pan_view_get_fd_list(pw);
2235         g_signal_connect(G_OBJECT(menu), "destroy",
2236                          G_CALLBACK(pan_popup_menu_destroy_cb), editmenu_fd_list);
2237
2238         submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw, editmenu_fd_list);
2239         gtk_widget_set_sensitive(item, active);
2240
2241         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
2242                                       G_CALLBACK(pan_new_window_cb), pw);
2243
2244         menu_item_add_divider(menu);
2245         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
2246                                       G_CALLBACK(pan_copy_cb), pw);
2247         menu_item_add_sensitive(menu, _("_Move..."), active,
2248                                 G_CALLBACK(pan_move_cb), pw);
2249         menu_item_add_sensitive(menu, _("_Rename..."), active,
2250                                 G_CALLBACK(pan_rename_cb), pw);
2251         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
2252                                       G_CALLBACK(pan_delete_cb), pw);
2253         menu_item_add_sensitive(menu, _("_Copy path"), active,
2254                                 G_CALLBACK(pan_copy_path_cb), pw);
2255
2256         menu_item_add_divider(menu);
2257         item = menu_item_add_check(menu, _("Sort by E_xif date"), pw->exif_date_enable,
2258                                    G_CALLBACK(pan_exif_date_toggle_cb), pw);
2259         gtk_widget_set_sensitive(item, (pw->layout == PAN_LAYOUT_TIMELINE || pw->layout == PAN_LAYOUT_CALENDAR));
2260
2261         menu_item_add_divider(menu);
2262
2263         menu_item_add_check(menu, _("_Show Exif information"), pw->info_includes_exif,
2264                             G_CALLBACK(pan_info_toggle_exif_cb), pw);
2265         item = menu_item_add(menu, _("Show im_age"), NULL, NULL);
2266         submenu = gtk_menu_new();
2267         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
2268
2269         item = menu_item_add_check(submenu, _("_None"), (pw->info_image_size == PAN_IMAGE_SIZE_THUMB_NONE),
2270                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2271         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_THUMB_NONE));
2272
2273         item = menu_item_add_check(submenu, _("_Full size"), (pw->info_image_size == PAN_IMAGE_SIZE_100),
2274                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2275         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_100));
2276
2277         item = menu_item_add_check(submenu, _("1:2 (50%)"), (pw->info_image_size == PAN_IMAGE_SIZE_50),
2278                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2279         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_50));
2280
2281         item = menu_item_add_check(submenu, _("1:3 (33%)"), (pw->info_image_size == PAN_IMAGE_SIZE_33),
2282                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2283         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_33));
2284
2285         item = menu_item_add_check(submenu, _("1:4 (25%)"), (pw->info_image_size == PAN_IMAGE_SIZE_25),
2286                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2287         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_25));
2288
2289         item = menu_item_add_check(submenu, _("1:10 (10%)"), (pw->info_image_size == PAN_IMAGE_SIZE_10),
2290                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2291         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_10));
2292
2293
2294
2295         menu_item_add_divider(menu);
2296
2297         if (pw->fs)
2298                 {
2299                 menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
2300                 }
2301         else
2302                 {
2303                 menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
2304                 }
2305
2306         menu_item_add_divider(menu);
2307         menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw);
2308
2309         return menu;
2310 }
2311
2312
2313 /*
2314  *-----------------------------------------------------------------------------
2315  * drag and drop
2316  *-----------------------------------------------------------------------------
2317  */
2318
2319 static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context,
2320                                     gint x, gint y,
2321                                     GtkSelectionData *selection_data, guint info,
2322                                     guint time, gpointer data)
2323 {
2324         PanWindow *pw = data;
2325
2326         if (gtk_drag_get_source_widget(context) == pw->imd->pr) return;
2327
2328         if (info == TARGET_URI_LIST)
2329                 {
2330                 GList *list;
2331
2332                 list = uri_filelist_from_gtk_selection_data(selection_data);
2333                 if (list && isdir(((FileData *)list->data)->path))
2334                         {
2335                         FileData *fd = list->data;
2336
2337                         pan_layout_set_fd(pw, fd);
2338                         }
2339
2340                 filelist_free(list);
2341                 }
2342 }
2343
2344 static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context,
2345                                     GtkSelectionData *selection_data, guint info,
2346                                     guint time, gpointer data)
2347 {
2348         PanWindow *pw = data;
2349         FileData *fd;
2350
2351         fd = pan_menu_click_fd(pw);
2352         if (fd)
2353                 {
2354                 GList *list;
2355
2356                 list = g_list_append(NULL, fd);
2357                 uri_selection_data_set_uris_from_filelist(selection_data, list);
2358                 g_list_free(list);
2359                 }
2360         else
2361                 {
2362                 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2363                                        8, NULL, 0);
2364                 }
2365 }
2366
2367 static void pan_window_dnd_init(PanWindow *pw)
2368 {
2369         GtkWidget *widget;
2370
2371         widget = pw->imd->pr;
2372
2373         gtk_drag_source_set(widget, GDK_BUTTON2_MASK,
2374                             dnd_file_drag_types, dnd_file_drag_types_count,
2375                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
2376         g_signal_connect(G_OBJECT(widget), "drag_data_get",
2377                          G_CALLBACK(pan_window_set_dnd_data), pw);
2378
2379         gtk_drag_dest_set(widget,
2380                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
2381                           dnd_file_drop_types, dnd_file_drop_types_count,
2382                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
2383         g_signal_connect(G_OBJECT(widget), "drag_data_received",
2384                          G_CALLBACK(pan_window_get_dnd_data), pw);
2385 }
2386
2387 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */