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