Pull the search UI construction code out into a distinct function.
[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_ui->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                 // Don't steal characters from entry boxes.
1250                 if (!on_entry)
1251                         {
1252                         stop_signal = TRUE;
1253                         switch (event->keyval)
1254                                 {
1255                                 case '+': case '=': case GDK_KEY_KP_Add:
1256                                         pixbuf_renderer_zoom_adjust(pr, ZOOM_INCREMENT);
1257                                         break;
1258                                 case '-': case GDK_KEY_KP_Subtract:
1259                                         pixbuf_renderer_zoom_adjust(pr, -ZOOM_INCREMENT);
1260                                         break;
1261                                 case 'Z': case 'z': case GDK_KEY_KP_Divide: case '1':
1262                                         pixbuf_renderer_zoom_set(pr, 1.0);
1263                                         break;
1264                                 case '2':
1265                                         pixbuf_renderer_zoom_set(pr, 2.0);
1266                                         break;
1267                                 case '3':
1268                                         pixbuf_renderer_zoom_set(pr, 3.0);
1269                                         break;
1270                                 case '4':
1271                                         pixbuf_renderer_zoom_set(pr, 4.0);
1272                                         break;
1273                                 case '7':
1274                                         pixbuf_renderer_zoom_set(pr, -4.0);
1275                                         break;
1276                                 case '8':
1277                                         pixbuf_renderer_zoom_set(pr, -3.0);
1278                                         break;
1279                                 case '9':
1280                                         pixbuf_renderer_zoom_set(pr, -2.0);
1281                                         break;
1282                                 case 'F': case 'f':
1283                                 case 'V': case 'v':
1284                                 case GDK_KEY_F11:
1285                                         pan_fullscreen_toggle(pw, FALSE);
1286                                         break;
1287                                 case 'I': case 'i':
1288                                         break;
1289                                 case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
1290                                         break;
1291                                 case GDK_KEY_Menu:
1292                                 case GDK_KEY_F10:
1293                                         menu = pan_popup_menu(pw);
1294                                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1295                                                        pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME);
1296                                         break;
1297                                 case '/':
1298                                         pan_search_toggle_visible(pw, TRUE);
1299                                         break;
1300                                 case GDK_KEY_F1:
1301                                         help_window_show("GuideReferenceKeyboardShortcuts.html#PanViewKeyboardShortcuts");
1302                                         break;
1303                                 default:
1304                                         stop_signal = FALSE;
1305                                         break;
1306                                 }
1307                         }
1308                 }
1309
1310         return stop_signal;
1311 }
1312
1313 /*
1314  *-----------------------------------------------------------------------------
1315  * info popup
1316  *-----------------------------------------------------------------------------
1317  */
1318
1319 static void pan_info_add_exif(PanTextAlignment *ta, FileData *fd)
1320 {
1321
1322         if (!fd) return;
1323
1324         pan_text_alignment_add(ta, NULL, NULL);
1325 }
1326
1327
1328 void pan_info_update(PanWindow *pw, PanItem *pi)
1329 {
1330         PanTextAlignment *ta;
1331         PanItem *pbox;
1332         PanItem *p;
1333         gchar *buf;
1334         gint x1, y1, x2, y2, x3, y3;
1335         gint x, y, w, h;
1336
1337         if (pw->click_pi == pi) return;
1338         if (pi && !pi->fd) pi = NULL;
1339
1340         while ((p = pan_item_find_by_key(pw, PAN_ITEM_NONE, "info"))) pan_item_remove(pw, p);
1341         pw->click_pi = pi;
1342
1343         if (!pi) return;
1344
1345         DEBUG_1("info set to %s", pi->fd->path);
1346
1347         pbox = pan_item_box_new(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10,
1348                                 PAN_POPUP_BORDER,
1349                                 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
1350                                 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1351         pan_item_set_key(pbox, "info");
1352
1353         if (pi->type == PAN_ITEM_THUMB && pi->pixbuf)
1354                 {
1355                 w = gdk_pixbuf_get_width(pi->pixbuf);
1356                 h = gdk_pixbuf_get_height(pi->pixbuf);
1357
1358                 x1 = pi->x + pi->width - (pi->width - w) / 2 - 8;
1359                 y1 = pi->y + (pi->height - h) / 2 + 8;
1360                 }
1361         else
1362                 {
1363                 x1 = pi->x + pi->width - 8;
1364                 y1 = pi->y + 8;
1365                 }
1366
1367         x2 = pbox->x + 1;
1368         y2 = pbox->y + 36;
1369         x3 = pbox->x + 1;
1370         y3 = pbox->y + 12;
1371         util_clip_triangle(x1, y1, x2, y2, x3, y3,
1372                            &x, &y, &w, &h);
1373
1374         p = pan_item_tri_new(pw, NULL, x, y, w, h,
1375                              x1, y1, x2, y2, x3, y3,
1376                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA);
1377         pan_item_tri_border(p, PAN_BORDER_1 | PAN_BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1378         pan_item_set_key(p, "info");
1379         pan_item_added(pw, p);
1380
1381         ta = pan_text_alignment_new(pw, pbox->x + PREF_PAD_BORDER, pbox->y + PREF_PAD_BORDER, "info");
1382
1383         pan_text_alignment_add(ta, _("Filename:"), pi->fd->name);
1384         buf = remove_level_from_path(pi->fd->path);
1385         pan_text_alignment_add(ta, _("Location:"), buf);
1386         g_free(buf);
1387         pan_text_alignment_add(ta, _("Date:"), text_from_time(pi->fd->date));
1388         buf = text_from_size(pi->fd->size);
1389         pan_text_alignment_add(ta, _("Size:"), buf);
1390         g_free(buf);
1391
1392         if (pw->info_includes_exif)
1393                 {
1394                 pan_info_add_exif(ta, pi->fd);
1395                 }
1396
1397         pan_text_alignment_calc(ta, pbox);
1398         pan_text_alignment_free(ta);
1399
1400         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
1401         pan_item_added(pw, pbox);
1402
1403         if (pw->info_image_size > PAN_IMAGE_SIZE_THUMB_NONE)
1404                 {
1405                 gint iw, ih;
1406                 if (image_load_dimensions(pi->fd, &iw, &ih))
1407                         {
1408                         gint scale = 25;
1409
1410                         switch (pw->info_image_size)
1411                                 {
1412                                 case PAN_IMAGE_SIZE_10:
1413                                         scale = 10;
1414                                         break;
1415                                 case PAN_IMAGE_SIZE_25:
1416                                         scale = 25;
1417                                         break;
1418                                 case PAN_IMAGE_SIZE_33:
1419                                         scale = 33;
1420                                         break;
1421                                 case PAN_IMAGE_SIZE_50:
1422                                         scale = 50;
1423                                         break;
1424                                 case PAN_IMAGE_SIZE_100:
1425                                         scale = 100;
1426                                         break;
1427                                 }
1428
1429                         iw = MAX(1, iw * scale / 100);
1430                         ih = MAX(1, ih * scale / 100);
1431
1432                         pbox = pan_item_box_new(pw, NULL, pbox->x, pbox->y + pbox->height + 8, 10, 10,
1433                                                 PAN_POPUP_BORDER,
1434                                                 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
1435                                                 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1436                         pan_item_set_key(pbox, "info");
1437
1438                         p = pan_item_image_new(pw, file_data_new_group(pi->fd->path),
1439                                                pbox->x + PREF_PAD_BORDER, pbox->y + PREF_PAD_BORDER, iw, ih);
1440                         pan_item_set_key(p, "info");
1441                         pan_item_size_by_item(pbox, p, PREF_PAD_BORDER);
1442
1443                         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
1444                         pan_item_added(pw, pbox);
1445                         }
1446                 }
1447
1448         pan_layout_resize(pw);
1449 }
1450
1451
1452 /*
1453  *-----------------------------------------------------------------------------
1454  * main window
1455  *-----------------------------------------------------------------------------
1456  */
1457
1458 static void button_cb(PixbufRenderer *pr, GdkEventButton *event, gpointer data)
1459 {
1460         PanWindow *pw = data;
1461         PanItem *pi = NULL;
1462         GtkWidget *menu;
1463         gint rx, ry;
1464
1465         rx = ry = 0;
1466         if (pr->scale)
1467                 {
1468                 rx = (gdouble)(pr->x_scroll + event->x - pr->x_offset) / pr->scale;
1469                 ry = (gdouble)(pr->y_scroll + event->y - pr->y_offset) / pr->scale;
1470                 }
1471
1472         pi = pan_item_find_by_coord(pw, PAN_ITEM_BOX, rx, ry, "info");
1473         if (pi && event->button == MOUSE_BUTTON_LEFT)
1474                 {
1475                 pan_info_update(pw, NULL);
1476                 return;
1477                 }
1478
1479         pi = pan_item_find_by_coord(pw, (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB,
1480                                     rx, ry, NULL);
1481
1482         switch (event->button)
1483                 {
1484                 case MOUSE_BUTTON_LEFT:
1485                         pan_info_update(pw, pi);
1486
1487                         if (!pi && pw->layout == PAN_LAYOUT_CALENDAR)
1488                                 {
1489                                 pi = pan_item_find_by_coord(pw, PAN_ITEM_BOX, rx, ry, "day");
1490                                 pan_calendar_update(pw, pi);
1491                                 }
1492                         break;
1493                 case MOUSE_BUTTON_MIDDLE:
1494                         break;
1495                 case MOUSE_BUTTON_RIGHT:
1496                         pan_info_update(pw, pi);
1497                         menu = pan_popup_menu(pw);
1498                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
1499                         break;
1500                 default:
1501                         break;
1502                 }
1503 }
1504
1505 static void scroll_cb(PixbufRenderer *pr, GdkEventScroll *event, gpointer data)
1506 {
1507         gint w, h;
1508
1509         w = pr->vis_width;
1510         h = pr->vis_height;
1511
1512         if (!(event->state & GDK_SHIFT_MASK))
1513                 {
1514                 w /= 3;
1515                 h /= 3;
1516                 }
1517
1518         if (event->state & GDK_CONTROL_MASK)
1519                 {
1520                 switch (event->direction)
1521                         {
1522                         case GDK_SCROLL_UP:
1523                                 pixbuf_renderer_zoom_adjust_at_point(pr, ZOOM_INCREMENT,
1524                                                                      (gint)event->x, (gint)event->y);
1525                                 break;
1526                         case GDK_SCROLL_DOWN:
1527                                 pixbuf_renderer_zoom_adjust_at_point(pr, -ZOOM_INCREMENT,
1528                                                                      (gint)event->x, (gint)event->y);
1529                                 break;
1530                         default:
1531                                 break;
1532                         }
1533                 }
1534         else
1535                 {
1536                 switch (event->direction)
1537                         {
1538                         case GDK_SCROLL_UP:
1539                                 pixbuf_renderer_scroll(pr, 0, -h);
1540                                 break;
1541                         case GDK_SCROLL_DOWN:
1542                                 pixbuf_renderer_scroll(pr, 0, h);
1543                                 break;
1544                         case GDK_SCROLL_LEFT:
1545                                 pixbuf_renderer_scroll(pr, -w, 0);
1546                                 break;
1547                         case GDK_SCROLL_RIGHT:
1548                                 pixbuf_renderer_scroll(pr, w, 0);
1549                                 break;
1550                         default:
1551                                 break;
1552                         }
1553                 }
1554 }
1555
1556 static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd)
1557 {
1558         g_signal_connect(G_OBJECT(imd->pr), "clicked",
1559                          G_CALLBACK(button_cb), pw);
1560         g_signal_connect(G_OBJECT(imd->pr), "scroll_event",
1561                          G_CALLBACK(scroll_cb), pw);
1562 }
1563
1564 static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data)
1565 {
1566         PanWindow *pw = data;
1567
1568         pw->fs = NULL;
1569         pw->imd = pw->imd_normal;
1570 }
1571
1572 static void pan_fullscreen_toggle(PanWindow *pw, gboolean force_off)
1573 {
1574         if (force_off && !pw->fs) return;
1575
1576         if (pw->fs)
1577                 {
1578                 fullscreen_stop(pw->fs);
1579                 }
1580         else
1581                 {
1582                 pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw);
1583                 pan_image_set_buttons(pw, pw->fs->imd);
1584                 g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event",
1585                                  G_CALLBACK(pan_window_key_press_cb), pw);
1586
1587                 pw->imd = pw->fs->imd;
1588                 }
1589 }
1590
1591 static void pan_window_image_zoom_cb(PixbufRenderer *pr, gdouble zoom, gpointer data)
1592 {
1593         PanWindow *pw = data;
1594         gchar *text;
1595
1596         text = image_zoom_get_as_text(pw->imd);
1597         gtk_label_set_text(GTK_LABEL(pw->label_zoom), text);
1598         g_free(text);
1599 }
1600
1601 static void pan_window_image_scroll_notify_cb(PixbufRenderer *pr, gpointer data)
1602 {
1603         PanWindow *pw = data;
1604         GtkAdjustment *adj;
1605         GdkRectangle rect;
1606         gint width, height;
1607
1608         if (pr->scale == 0.0) return;
1609
1610         pixbuf_renderer_get_visible_rect(pr, &rect);
1611         pixbuf_renderer_get_image_size(pr, &width, &height);
1612
1613         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h));
1614         gtk_adjustment_set_page_size(adj, rect.width);
1615         gtk_adjustment_set_page_increment(adj, gtk_adjustment_get_page_size(adj) / 2.0);
1616         gtk_adjustment_set_step_increment(adj, 48.0 / pr->scale);
1617         gtk_adjustment_set_lower(adj, 0.0);
1618         gtk_adjustment_set_upper(adj, MAX((gdouble)width, 1.0));
1619         gtk_adjustment_set_value(adj, (gdouble)rect.x);
1620
1621         pref_signal_block_data(pw->scrollbar_h, pw);
1622         gtk_adjustment_changed(adj);
1623         gtk_adjustment_value_changed(adj);
1624         pref_signal_unblock_data(pw->scrollbar_h, pw);
1625
1626         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v));
1627         gtk_adjustment_set_page_size(adj, rect.height);
1628         gtk_adjustment_set_page_increment(adj, gtk_adjustment_get_page_size(adj) / 2.0);
1629         gtk_adjustment_set_step_increment(adj, 48.0 / pr->scale);
1630         gtk_adjustment_set_lower(adj, 0.0);
1631         gtk_adjustment_set_upper(adj, MAX((gdouble)height, 1.0));
1632         gtk_adjustment_set_value(adj, (gdouble)rect.y);
1633
1634         pref_signal_block_data(pw->scrollbar_v, pw);
1635         gtk_adjustment_changed(adj);
1636         gtk_adjustment_value_changed(adj);
1637         pref_signal_unblock_data(pw->scrollbar_v, pw);
1638 }
1639
1640 static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data)
1641 {
1642         PanWindow *pw = data;
1643         PixbufRenderer *pr;
1644         gint x;
1645
1646         pr = PIXBUF_RENDERER(pw->imd_normal->pr);
1647
1648         if (!pr->scale) return;
1649
1650         x = (gint)gtk_range_get_value(range);
1651
1652         pixbuf_renderer_scroll_to_point(pr, x, (gint)((gdouble)pr->y_scroll / pr->scale), 0.0, 0.0);
1653 }
1654
1655 static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data)
1656 {
1657         PanWindow *pw = data;
1658         PixbufRenderer *pr;
1659         gint y;
1660
1661         pr = PIXBUF_RENDERER(pw->imd_normal->pr);
1662
1663         if (!pr->scale) return;
1664
1665         y = (gint)gtk_range_get_value(range);
1666
1667         pixbuf_renderer_scroll_to_point(pr, (gint)((gdouble)pr->x_scroll / pr->scale), y, 0.0, 0.0);
1668 }
1669
1670 static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data)
1671 {
1672         PanWindow *pw = data;
1673
1674         pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
1675         pan_layout_update(pw);
1676 }
1677
1678 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
1679 {
1680         PanWindow *pw = data;
1681
1682         pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
1683         pan_layout_update(pw);
1684 }
1685
1686 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
1687 {
1688         PanWindow *pw = data;
1689         gchar *path;
1690
1691         path = remove_trailing_slash(new_text);
1692         parse_out_relatives(path);
1693
1694         if (!isdir(path))
1695                 {
1696                 warning_dialog(_("Folder not found"),
1697                                _("The entered path is not a folder"),
1698                                GTK_STOCK_DIALOG_WARNING, pw->path_entry);
1699                 }
1700         else
1701                 {
1702                 FileData *dir_fd = file_data_new_dir(path);
1703                 tab_completion_append_to_history(pw->path_entry, path);
1704
1705                 pan_layout_set_fd(pw, dir_fd);
1706                 file_data_unref(dir_fd);
1707                 }
1708
1709         g_free(path);
1710 }
1711
1712 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
1713 {
1714         PanWindow *pw = data;
1715         gchar *text;
1716
1717         if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return;
1718
1719         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
1720         pan_window_entry_activate_cb(text, pw);
1721         g_free(text);
1722 }
1723
1724 static void pan_window_close(PanWindow *pw)
1725 {
1726         pan_window_list = g_list_remove(pan_window_list, pw);
1727
1728         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_EXIF_PAN_DATE, pw->exif_date_enable);
1729         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_INFO_IMAGE, pw->info_image_size);
1730         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_INFO_EXIF, pw->info_includes_exif);
1731
1732         if (pw->idle_id)
1733                 {
1734                 g_source_remove(pw->idle_id);
1735                 }
1736
1737         pan_fullscreen_toggle(pw, TRUE);
1738         gtk_widget_destroy(pw->window);
1739
1740         pan_window_items_free(pw);
1741         pan_cache_free(pw);
1742
1743         file_data_unref(pw->dir_fd);
1744
1745         g_free(pw);
1746 }
1747
1748 static gboolean pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
1749 {
1750         PanWindow *pw = data;
1751
1752         pan_window_close(pw);
1753         return TRUE;
1754 }
1755
1756 static void pan_window_new_real(FileData *dir_fd)
1757 {
1758         PanWindow *pw;
1759         GtkWidget *vbox;
1760         GtkWidget *box;
1761         GtkWidget *combo;
1762         GtkWidget *hbox;
1763         GtkWidget *frame;
1764         GtkWidget *table;
1765         GdkGeometry geometry;
1766
1767         pw = g_new0(PanWindow, 1);
1768
1769         pw->dir_fd = file_data_ref(dir_fd);
1770         pw->layout = PAN_LAYOUT_TIMELINE;
1771         pw->size = PAN_IMAGE_SIZE_THUMB_NORMAL;
1772         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
1773         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
1774
1775         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_EXIF_PAN_DATE, &pw->exif_date_enable))
1776                 {
1777                 pw->exif_date_enable = FALSE;
1778                 }
1779         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_INFO_IMAGE, &pw->info_image_size))
1780                 {
1781                 pw->info_image_size = PAN_IMAGE_SIZE_THUMB_NONE;
1782                 }
1783         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_INFO_EXIF, &pw->info_includes_exif))
1784                 {
1785                 pw->info_includes_exif = TRUE;
1786                 }
1787
1788         pw->ignore_symlinks = TRUE;
1789
1790         pw->idle_id = 0;
1791
1792         pw->window = window_new(GTK_WINDOW_TOPLEVEL, "panview", NULL, NULL, _("Pan View"));
1793
1794         geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
1795         geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
1796         gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE);
1797
1798         gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE);
1799         gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0);
1800
1801         vbox = gtk_vbox_new(FALSE, 0);
1802         gtk_container_add(GTK_CONTAINER(pw->window), vbox);
1803         gtk_widget_show(vbox);
1804
1805         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1806
1807         pref_spacer(box, 0);
1808         pref_label_new(box, _("Location:"));
1809         combo = tab_completion_new_with_history(&pw->path_entry, dir_fd->path, "pan_view_path", -1,
1810                                                 pan_window_entry_activate_cb, pw);
1811         g_signal_connect(G_OBJECT(gtk_widget_get_parent(pw->path_entry)), "changed",
1812                          G_CALLBACK(pan_window_entry_change_cb), pw);
1813         gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
1814         gtk_widget_show(combo);
1815
1816         combo = gtk_combo_box_text_new();
1817         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Timeline"));
1818         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Calendar"));
1819         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Folders"));
1820         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Folders (flower)"));
1821         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Grid"));
1822
1823         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout);
1824         g_signal_connect(G_OBJECT(combo), "changed",
1825                          G_CALLBACK(pan_window_layout_change_cb), pw);
1826         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
1827         gtk_widget_show(combo);
1828
1829         combo = gtk_combo_box_text_new();
1830         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Dots"));
1831         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("No Images"));
1832         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Small Thumbnails"));
1833         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Normal Thumbnails"));
1834         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Large Thumbnails"));
1835         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:10 (10%)"));
1836         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:4 (25%)"));
1837         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:3 (33%)"));
1838         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:2 (50%)"));
1839         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:1 (100%)"));
1840
1841         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size);
1842         g_signal_connect(G_OBJECT(combo), "changed",
1843                          G_CALLBACK(pan_window_layout_size_cb), pw);
1844         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
1845         gtk_widget_show(combo);
1846
1847         table = pref_table_new(vbox, 2, 2, FALSE, TRUE);
1848         gtk_table_set_row_spacings(GTK_TABLE(table), 2);
1849         gtk_table_set_col_spacings(GTK_TABLE(table), 2);
1850
1851         pw->imd = image_new(TRUE);
1852         pw->imd_normal = pw->imd;
1853
1854         g_signal_connect(G_OBJECT(pw->imd->pr), "zoom",
1855                          G_CALLBACK(pan_window_image_zoom_cb), pw);
1856         g_signal_connect(G_OBJECT(pw->imd->pr), "scroll_notify",
1857                          G_CALLBACK(pan_window_image_scroll_notify_cb), pw);
1858
1859         gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1,
1860                          GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
1861         gtk_widget_show(GTK_WIDGET(pw->imd->widget));
1862
1863         pan_window_dnd_init(pw);
1864
1865         pan_image_set_buttons(pw, pw->imd);
1866
1867         pw->scrollbar_h = gtk_hscrollbar_new(NULL);
1868         g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed",
1869                          G_CALLBACK(pan_window_scrollbar_h_value_cb), pw);
1870         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2,
1871                          GTK_FILL | GTK_EXPAND, 0, 0, 0);
1872         gtk_widget_show(pw->scrollbar_h);
1873
1874         pw->scrollbar_v = gtk_vscrollbar_new(NULL);
1875         g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed",
1876                          G_CALLBACK(pan_window_scrollbar_v_value_cb), pw);
1877         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1,
1878                          0, GTK_FILL | GTK_EXPAND, 0, 0);
1879         gtk_widget_show(pw->scrollbar_v);
1880
1881         /* find bar */
1882
1883         pw->search_ui = pan_search_ui_new(pw);
1884         gtk_box_pack_start(GTK_BOX(vbox), pw->search_ui->search_box, FALSE, FALSE, 2);
1885
1886         /* status bar */
1887
1888         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
1889
1890         frame = gtk_frame_new(NULL);
1891         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
1892         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
1893         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
1894         gtk_widget_show(frame);
1895
1896         hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
1897         gtk_container_add(GTK_CONTAINER(frame), hbox);
1898         gtk_widget_show(hbox);
1899
1900         pref_spacer(hbox, 0);
1901         pw->label_message = pref_label_new(hbox, "");
1902
1903         frame = gtk_frame_new(NULL);
1904         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
1905         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
1906         gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0);
1907         gtk_widget_show(frame);
1908
1909         pw->label_zoom = gtk_label_new("");
1910         gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
1911         gtk_widget_show(pw->label_zoom);
1912
1913         // Add the "Find" button to the status bar area.
1914         gtk_box_pack_end(GTK_BOX(box), pw->search_ui->search_button, FALSE, FALSE, 0);
1915         gtk_widget_show(pw->search_ui->search_button);
1916
1917         g_signal_connect(G_OBJECT(pw->window), "delete_event",
1918                          G_CALLBACK(pan_window_delete_cb), pw);
1919         g_signal_connect(G_OBJECT(pw->window), "key_press_event",
1920                          G_CALLBACK(pan_window_key_press_cb), pw);
1921
1922         gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
1923
1924         pan_layout_update(pw);
1925
1926         gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
1927         gtk_widget_show(pw->window);
1928
1929         pan_window_list = g_list_append(pan_window_list, pw);
1930 }
1931
1932 /*
1933  *-----------------------------------------------------------------------------
1934  * peformance warnings
1935  *-----------------------------------------------------------------------------
1936  */
1937
1938 static void pan_warning_ok_cb(GenericDialog *gd, gpointer data)
1939 {
1940         FileData *dir_fd = data;
1941
1942         generic_dialog_close(gd);
1943
1944         pan_window_new_real(dir_fd);
1945         file_data_unref(dir_fd);
1946 }
1947
1948 static void pan_warning_hide_cb(GtkWidget *button, gpointer data)
1949 {
1950         gboolean hide_dlg;
1951
1952         hide_dlg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
1953         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, hide_dlg);
1954 }
1955
1956 static gboolean pan_warning(FileData *dir_fd)
1957 {
1958         GenericDialog *gd;
1959         GtkWidget *box;
1960         GtkWidget *group;
1961         GtkWidget *button;
1962         GtkWidget *ct_button;
1963         gboolean hide_dlg;
1964
1965         if (dir_fd && strcmp(dir_fd->path, G_DIR_SEPARATOR_S) == 0)
1966                 {
1967                 pan_warning_folder(dir_fd->path, NULL);
1968                 return TRUE;
1969                 }
1970
1971         if (options->thumbnails.enable_caching &&
1972             options->thumbnails.spec_standard) return FALSE;
1973
1974         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, &hide_dlg)) hide_dlg = FALSE;
1975         if (hide_dlg) return FALSE;
1976
1977         gd = generic_dialog_new(_("Pan View Performance"), "pan_view_warning", NULL, FALSE,
1978                                 NULL, NULL);
1979         gd->data = file_data_ref(dir_fd);
1980         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
1981                                   pan_warning_ok_cb, TRUE);
1982
1983         box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO,
1984                                          _("Pan view performance may be poor."),
1985                                          _("To improve performance of thumbnails in the pan view the"
1986                                            " following options can be enabled. Note that both options"
1987                                            " must be enabled to notice a change in performance."));
1988
1989         group = pref_box_new(box, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
1990         pref_spacer(group, PREF_PAD_INDENT);
1991         group = pref_box_new(group, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1992
1993         ct_button = pref_checkbox_new_int(group, _("Cache thumbnails"),
1994                                           options->thumbnails.enable_caching, &options->thumbnails.enable_caching);
1995         button = pref_checkbox_new_int(group, _("Use shared thumbnail cache"),
1996                                        options->thumbnails.spec_standard, &options->thumbnails.spec_standard);
1997         pref_checkbox_link_sensitivity(ct_button, button);
1998
1999         pref_line(box, 0);
2000
2001         pref_checkbox_new(box, _("Do not show this dialog again"), hide_dlg,
2002                           G_CALLBACK(pan_warning_hide_cb), NULL);
2003
2004         gtk_widget_show(gd->dialog);
2005
2006         return TRUE;
2007 }
2008
2009
2010 /*
2011  *-----------------------------------------------------------------------------
2012  * entry point
2013  *-----------------------------------------------------------------------------
2014  */
2015
2016 void pan_window_new(FileData *dir_fd)
2017 {
2018         if (pan_warning(dir_fd)) return;
2019
2020         pan_window_new_real(dir_fd);
2021 }
2022
2023
2024 /*
2025  *-----------------------------------------------------------------------------
2026  * menus
2027  *-----------------------------------------------------------------------------
2028  */
2029
2030 #define INFO_IMAGE_SIZE_KEY "image_size_data"
2031
2032
2033 static void pan_new_window_cb(GtkWidget *widget, gpointer data)
2034 {
2035         PanWindow *pw = data;
2036         FileData *fd;
2037
2038         fd = pan_menu_click_fd(pw);
2039         if (fd)
2040                 {
2041                 pan_fullscreen_toggle(pw, TRUE);
2042                 view_window_new(fd);
2043                 }
2044 }
2045
2046 static void pan_edit_cb(GtkWidget *widget, gpointer data)
2047 {
2048         PanWindow *pw;
2049         FileData *fd;
2050         const gchar *key = data;
2051
2052         pw = submenu_item_get_data(widget);
2053         if (!pw) return;
2054
2055         fd = pan_menu_click_fd(pw);
2056         if (fd)
2057                 {
2058                 if (!editor_window_flag_set(key))
2059                         {
2060                         pan_fullscreen_toggle(pw, TRUE);
2061                         }
2062                 file_util_start_editor_from_file(key, fd, pw->imd->widget);
2063                 }
2064 }
2065
2066 static void pan_zoom_in_cb(GtkWidget *widget, gpointer data)
2067 {
2068         PanWindow *pw = data;
2069
2070         image_zoom_adjust(pw->imd, ZOOM_INCREMENT);
2071 }
2072
2073 static void pan_zoom_out_cb(GtkWidget *widget, gpointer data)
2074 {
2075         PanWindow *pw = data;
2076
2077         image_zoom_adjust(pw->imd, -ZOOM_INCREMENT);
2078 }
2079
2080 static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data)
2081 {
2082         PanWindow *pw = data;
2083
2084         image_zoom_set(pw->imd, 1.0);
2085 }
2086
2087 static void pan_copy_cb(GtkWidget *widget, gpointer data)
2088 {
2089         PanWindow *pw = data;
2090         FileData *fd;
2091
2092         fd = pan_menu_click_fd(pw);
2093         if (fd) file_util_copy(fd, NULL, NULL, pw->imd->widget);
2094 }
2095
2096 static void pan_move_cb(GtkWidget *widget, gpointer data)
2097 {
2098         PanWindow *pw = data;
2099         FileData *fd;
2100
2101         fd = pan_menu_click_fd(pw);
2102         if (fd) file_util_move(fd, NULL, NULL, pw->imd->widget);
2103 }
2104
2105 static void pan_rename_cb(GtkWidget *widget, gpointer data)
2106 {
2107         PanWindow *pw = data;
2108         FileData *fd;
2109
2110         fd = pan_menu_click_fd(pw);
2111         if (fd) file_util_rename(fd, NULL, pw->imd->widget);
2112 }
2113
2114 static void pan_delete_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_delete(fd, NULL, pw->imd->widget);
2121 }
2122
2123 static void pan_copy_path_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_copy_path_to_clipboard(fd);
2130 }
2131
2132 static void pan_exif_date_toggle_cb(GtkWidget *widget, gpointer data)
2133 {
2134         PanWindow *pw = data;
2135
2136         pw->exif_date_enable = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
2137         pan_layout_update(pw);
2138 }
2139
2140 static void pan_info_toggle_exif_cb(GtkWidget *widget, gpointer data)
2141 {
2142         PanWindow *pw = data;
2143
2144         pw->info_includes_exif = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
2145         /* fixme: sync info now */
2146 }
2147
2148 static void pan_info_toggle_image_cb(GtkWidget *widget, gpointer data)
2149 {
2150         PanWindow *pw = data;
2151
2152         pw->info_image_size = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), INFO_IMAGE_SIZE_KEY));
2153         /* fixme: sync info now */
2154 }
2155
2156 static void pan_fullscreen_cb(GtkWidget *widget, gpointer data)
2157 {
2158         PanWindow *pw = data;
2159
2160         pan_fullscreen_toggle(pw, FALSE);
2161 }
2162
2163 static void pan_close_cb(GtkWidget *widget, gpointer data)
2164 {
2165         PanWindow *pw = data;
2166
2167         pan_window_close(pw);
2168 }
2169
2170 static void pan_popup_menu_destroy_cb(GtkWidget *widget, gpointer data)
2171 {
2172         GList *editmenu_fd_list = data;
2173
2174         filelist_free(editmenu_fd_list);
2175 }
2176
2177 static GList *pan_view_get_fd_list(PanWindow *pw)
2178 {
2179         GList *list = NULL;
2180         FileData *fd = pan_menu_click_fd(pw);
2181
2182         if (fd) list = g_list_prepend(filelist_copy(fd->sidecar_files), file_data_ref(fd));
2183
2184         return list;
2185 }
2186
2187 static GtkWidget *pan_popup_menu(PanWindow *pw)
2188 {
2189         GtkWidget *menu;
2190         GtkWidget *submenu;
2191         GtkWidget *item;
2192         gboolean active;
2193         GList *editmenu_fd_list;
2194
2195         active = (pw->click_pi != NULL);
2196
2197         menu = popup_menu_short_lived();
2198
2199         menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
2200                             G_CALLBACK(pan_zoom_in_cb), pw);
2201         menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
2202                             G_CALLBACK(pan_zoom_out_cb), pw);
2203         menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100,
2204                             G_CALLBACK(pan_zoom_1_1_cb), pw);
2205         menu_item_add_divider(menu);
2206
2207         editmenu_fd_list = pan_view_get_fd_list(pw);
2208         g_signal_connect(G_OBJECT(menu), "destroy",
2209                          G_CALLBACK(pan_popup_menu_destroy_cb), editmenu_fd_list);
2210
2211         submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw, editmenu_fd_list);
2212         gtk_widget_set_sensitive(item, active);
2213
2214         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
2215                                       G_CALLBACK(pan_new_window_cb), pw);
2216
2217         menu_item_add_divider(menu);
2218         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
2219                                       G_CALLBACK(pan_copy_cb), pw);
2220         menu_item_add_sensitive(menu, _("_Move..."), active,
2221                                 G_CALLBACK(pan_move_cb), pw);
2222         menu_item_add_sensitive(menu, _("_Rename..."), active,
2223                                 G_CALLBACK(pan_rename_cb), pw);
2224         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
2225                                       G_CALLBACK(pan_delete_cb), pw);
2226         menu_item_add_sensitive(menu, _("_Copy path"), active,
2227                                 G_CALLBACK(pan_copy_path_cb), pw);
2228
2229         menu_item_add_divider(menu);
2230         item = menu_item_add_check(menu, _("Sort by E_xif date"), pw->exif_date_enable,
2231                                    G_CALLBACK(pan_exif_date_toggle_cb), pw);
2232         gtk_widget_set_sensitive(item, (pw->layout == PAN_LAYOUT_TIMELINE || pw->layout == PAN_LAYOUT_CALENDAR));
2233
2234         menu_item_add_divider(menu);
2235
2236         menu_item_add_check(menu, _("_Show Exif information"), pw->info_includes_exif,
2237                             G_CALLBACK(pan_info_toggle_exif_cb), pw);
2238         item = menu_item_add(menu, _("Show im_age"), NULL, NULL);
2239         submenu = gtk_menu_new();
2240         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
2241
2242         item = menu_item_add_check(submenu, _("_None"), (pw->info_image_size == PAN_IMAGE_SIZE_THUMB_NONE),
2243                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2244         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_THUMB_NONE));
2245
2246         item = menu_item_add_check(submenu, _("_Full size"), (pw->info_image_size == PAN_IMAGE_SIZE_100),
2247                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2248         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_100));
2249
2250         item = menu_item_add_check(submenu, _("1:2 (50%)"), (pw->info_image_size == PAN_IMAGE_SIZE_50),
2251                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2252         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_50));
2253
2254         item = menu_item_add_check(submenu, _("1:3 (33%)"), (pw->info_image_size == PAN_IMAGE_SIZE_33),
2255                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2256         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_33));
2257
2258         item = menu_item_add_check(submenu, _("1:4 (25%)"), (pw->info_image_size == PAN_IMAGE_SIZE_25),
2259                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2260         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_25));
2261
2262         item = menu_item_add_check(submenu, _("1:10 (10%)"), (pw->info_image_size == PAN_IMAGE_SIZE_10),
2263                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2264         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_10));
2265
2266
2267
2268         menu_item_add_divider(menu);
2269
2270         if (pw->fs)
2271                 {
2272                 menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
2273                 }
2274         else
2275                 {
2276                 menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
2277                 }
2278
2279         menu_item_add_divider(menu);
2280         menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw);
2281
2282         return menu;
2283 }
2284
2285
2286 /*
2287  *-----------------------------------------------------------------------------
2288  * drag and drop
2289  *-----------------------------------------------------------------------------
2290  */
2291
2292 static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context,
2293                                     gint x, gint y,
2294                                     GtkSelectionData *selection_data, guint info,
2295                                     guint time, gpointer data)
2296 {
2297         PanWindow *pw = data;
2298
2299         if (gtk_drag_get_source_widget(context) == pw->imd->pr) return;
2300
2301         if (info == TARGET_URI_LIST)
2302                 {
2303                 GList *list;
2304
2305                 list = uri_filelist_from_gtk_selection_data(selection_data);
2306                 if (list && isdir(((FileData *)list->data)->path))
2307                         {
2308                         FileData *fd = list->data;
2309
2310                         pan_layout_set_fd(pw, fd);
2311                         }
2312
2313                 filelist_free(list);
2314                 }
2315 }
2316
2317 static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context,
2318                                     GtkSelectionData *selection_data, guint info,
2319                                     guint time, gpointer data)
2320 {
2321         PanWindow *pw = data;
2322         FileData *fd;
2323
2324         fd = pan_menu_click_fd(pw);
2325         if (fd)
2326                 {
2327                 GList *list;
2328
2329                 list = g_list_append(NULL, fd);
2330                 uri_selection_data_set_uris_from_filelist(selection_data, list);
2331                 g_list_free(list);
2332                 }
2333         else
2334                 {
2335                 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2336                                        8, NULL, 0);
2337                 }
2338 }
2339
2340 static void pan_window_dnd_init(PanWindow *pw)
2341 {
2342         GtkWidget *widget;
2343
2344         widget = pw->imd->pr;
2345
2346         gtk_drag_source_set(widget, GDK_BUTTON2_MASK,
2347                             dnd_file_drag_types, dnd_file_drag_types_count,
2348                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
2349         g_signal_connect(G_OBJECT(widget), "drag_data_get",
2350                          G_CALLBACK(pan_window_set_dnd_data), pw);
2351
2352         gtk_drag_dest_set(widget,
2353                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
2354                           dnd_file_drop_types, dnd_file_drop_types_count,
2355                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
2356         g_signal_connect(G_OBJECT(widget), "drag_data_received",
2357                          G_CALLBACK(pan_window_get_dnd_data), pw);
2358 }
2359
2360 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */