Sort headers using clang-tidy
[geeqie.git] / src / img-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 "img-view.h"
23
24 #include <config.h>
25
26 #include "collect-io.h"
27 #include "collect.h"
28 #include "compat.h"
29 #include "debug.h"
30 #include "dnd.h"
31 #include "editors.h"
32 #include "filedata.h"
33 #include "fullscreen.h"
34 #include "image-load.h"
35 #include "image-overlay.h"
36 #include "image.h"
37 #include "intl.h"
38 #include "layout-util.h"
39 #include "main-defines.h"
40 #include "main.h"
41 #include "menu.h"
42 #include "misc.h"
43 #include "pixbuf-util.h"
44 #include "print.h"
45 #include "slideshow.h"
46 #include "ui-fileops.h"
47 #include "ui-menu.h"
48 #include "uri-utils.h"
49 #include "utilops.h"
50 #include "window.h"
51
52 struct ViewWindow
53 {
54         GtkWidget *window;
55         ImageWindow *imd;
56         FullScreenData *fs;
57         SlideShowData *ss;
58
59         GList *list;
60         GList *list_pointer;
61 };
62
63
64 static GList *view_window_list = nullptr;
65
66
67 static GtkWidget *view_popup_menu(ViewWindow *vw);
68 static void view_fullscreen_toggle(ViewWindow *vw, gboolean force_off);
69 static void view_overlay_toggle(ViewWindow *vw);
70
71 static void view_slideshow_next(ViewWindow *vw);
72 static void view_slideshow_prev(ViewWindow *vw);
73 static void view_slideshow_start(ViewWindow *vw);
74 static void view_slideshow_stop(ViewWindow *vw);
75
76 static void view_window_close(ViewWindow *vw);
77
78 static void view_window_dnd_init(ViewWindow *vw);
79
80 static void view_window_notify_cb(FileData *fd, NotifyType type, gpointer data);
81
82
83 /**
84  * This array must be kept in sync with the contents of:\n
85  *  @link view_popup_menu() @endlink \n
86  *  @link view_window_key_press_cb() @endlink
87  *
88  * See also @link hard_coded_window_keys @endlink
89  **/
90 hard_coded_window_keys image_window_keys[] = {
91         {GDK_CONTROL_MASK, 'C', N_("Copy")},
92         {GDK_CONTROL_MASK, 'M', N_("Move")},
93         {GDK_CONTROL_MASK, 'R', N_("Rename")},
94         {GDK_CONTROL_MASK, 'D', N_("Move to Trash")},
95         {static_cast<GdkModifierType>(0), GDK_KEY_Delete, N_("Move to Trash")},
96         {GDK_SHIFT_MASK, GDK_KEY_Delete, N_("Delete")},
97         {GDK_CONTROL_MASK, 'W', N_("Close window")},
98         {GDK_SHIFT_MASK, 'R', N_("Rotate 180°")},
99         {GDK_SHIFT_MASK, 'M', N_("Rotate mirror")},
100         {GDK_SHIFT_MASK, 'F', N_("Rotate flip")},
101         {static_cast<GdkModifierType>(0), ']', N_(" Rotate counterclockwise 90°")},
102         {static_cast<GdkModifierType>(0), '[', N_(" Rotate clockwise 90°")},
103         {static_cast<GdkModifierType>(0), GDK_KEY_Page_Up, N_("Previous")},
104         {static_cast<GdkModifierType>(0), GDK_KEY_KP_Page_Up, N_("Previous")},
105         {static_cast<GdkModifierType>(0), GDK_KEY_BackSpace, N_("Previous")},
106         {static_cast<GdkModifierType>(0), 'B', N_("Previous")},
107         {static_cast<GdkModifierType>(0), GDK_KEY_Page_Down, N_("Next")},
108         {static_cast<GdkModifierType>(0), GDK_KEY_KP_Page_Down, N_("Next")},
109         {static_cast<GdkModifierType>(0), GDK_KEY_space, N_("Next")},
110         {static_cast<GdkModifierType>(0), 'N', N_("Next")},
111         {static_cast<GdkModifierType>(0), GDK_KEY_equal, N_("Zoom in")},
112         {static_cast<GdkModifierType>(0), GDK_KEY_plus, N_("Zoom in")},
113         {static_cast<GdkModifierType>(0), GDK_KEY_minus, N_("Zoom out")},
114         {static_cast<GdkModifierType>(0), 'X', N_("Zoom to fit")},
115         {static_cast<GdkModifierType>(0), GDK_KEY_KP_Multiply, N_("Zoom to fit")},
116         {static_cast<GdkModifierType>(0), 'Z', N_("Zoom 1:1")},
117         {static_cast<GdkModifierType>(0), GDK_KEY_KP_Divide, N_("Zoom 1:1")},
118         {static_cast<GdkModifierType>(0), GDK_KEY_1, N_("Zoom 1:1")},
119         {static_cast<GdkModifierType>(0), '2', N_("Zoom 2:1")},
120         {static_cast<GdkModifierType>(0), '3', N_("Zoom 3:1")},
121         {static_cast<GdkModifierType>(0), '4', N_("Zoom 4:1")},
122         {static_cast<GdkModifierType>(0), '7', N_("Zoom 1:4")},
123         {static_cast<GdkModifierType>(0), '8', N_("Zoom 1:3")},
124         {static_cast<GdkModifierType>(0), '9', N_("Zoom 1:2")},
125         {static_cast<GdkModifierType>(0), 'W', N_("Zoom fit window width")},
126         {static_cast<GdkModifierType>(0), 'H', N_("Zoom fit window height")},
127         {static_cast<GdkModifierType>(0), 'S', N_("Toggle slideshow")},
128         {static_cast<GdkModifierType>(0), 'P', N_("Pause slideshow")},
129         {static_cast<GdkModifierType>(0), 'R', N_("Reload image")},
130         {static_cast<GdkModifierType>(0), 'F', N_("Full screen")},
131         {static_cast<GdkModifierType>(0), 'V', N_("Fullscreen")},
132         {static_cast<GdkModifierType>(0), GDK_KEY_F11, N_("Fullscreen")},
133         {static_cast<GdkModifierType>(0), 'I', N_("Image overlay")},
134         {static_cast<GdkModifierType>(0), GDK_KEY_Escape, N_("Exit fullscreen")},
135         {static_cast<GdkModifierType>(0), GDK_KEY_Escape, N_("Close window")},
136         {GDK_SHIFT_MASK, 'G', N_("Desaturate")},
137         {GDK_SHIFT_MASK, 'P', N_("Print")},
138         {static_cast<GdkModifierType>(0), 0, nullptr}
139 };
140
141
142 /*
143  *-----------------------------------------------------------------------------
144  * misc
145  *-----------------------------------------------------------------------------
146  */
147
148 static ImageWindow *view_window_active_image(ViewWindow *vw)
149 {
150         if (vw->fs) return vw->fs->imd;
151
152         return vw->imd;
153 }
154
155 static void view_window_set_list(ViewWindow *vw, GList *list)
156 {
157
158         filelist_free(vw->list);
159         vw->list = nullptr;
160         vw->list_pointer = nullptr;
161
162         vw->list = filelist_copy(list);
163 }
164
165 static gboolean view_window_contains_collection(ViewWindow *vw)
166 {
167         CollectionData *cd;
168         CollectInfo *info;
169
170         cd = image_get_collection(view_window_active_image(vw), &info);
171
172         return (cd && info);
173 }
174
175 static void view_collection_step(ViewWindow *vw, gboolean next)
176 {
177         ImageWindow *imd = view_window_active_image(vw);
178         CollectionData *cd;
179         CollectInfo *info;
180         CollectInfo *read_ahead_info = nullptr;
181
182         cd = image_get_collection(imd, &info);
183
184         if (!cd || !info) return;
185
186         if (next)
187                 {
188                 info = collection_next_by_info(cd, info);
189                 if (options->image.enable_read_ahead)
190                         {
191                         read_ahead_info = collection_next_by_info(cd, info);
192                         if (!read_ahead_info) read_ahead_info = collection_prev_by_info(cd, info);
193                         }
194                 }
195         else
196                 {
197                 info = collection_prev_by_info(cd, info);
198                 if (options->image.enable_read_ahead)
199                         {
200                         read_ahead_info = collection_prev_by_info(cd, info);
201                         if (!read_ahead_info) read_ahead_info = collection_next_by_info(cd, info);
202                         }
203                 }
204
205         if (info)
206                 {
207                 image_change_from_collection(imd, cd, info, image_zoom_get_default(imd));
208
209                 if (read_ahead_info) image_prebuffer_set(imd, read_ahead_info->fd);
210                 }
211
212 }
213
214 static void view_collection_step_to_end(ViewWindow *vw, gboolean last)
215 {
216         ImageWindow *imd = view_window_active_image(vw);
217         CollectionData *cd;
218         CollectInfo *info;
219         CollectInfo *read_ahead_info = nullptr;
220
221         cd = image_get_collection(imd, &info);
222
223         if (!cd || !info) return;
224
225         if (last)
226                 {
227                 info = collection_get_last(cd);
228                 if (options->image.enable_read_ahead) read_ahead_info = collection_prev_by_info(cd, info);
229                 }
230         else
231                 {
232                 info = collection_get_first(cd);
233                 if (options->image.enable_read_ahead) read_ahead_info = collection_next_by_info(cd, info);
234                 }
235
236         if (info)
237                 {
238                 image_change_from_collection(imd, cd, info, image_zoom_get_default(imd));
239                 if (read_ahead_info) image_prebuffer_set(imd, read_ahead_info->fd);
240                 }
241 }
242
243 static void view_list_step(ViewWindow *vw, gboolean next)
244 {
245         ImageWindow *imd = view_window_active_image(vw);
246         FileData *fd;
247         GList *work;
248         GList *work_ahead;
249
250         if (!vw->list) return;
251
252         fd = image_get_fd(imd);
253         if (!fd) return;
254
255         if (g_list_position(vw->list, vw->list_pointer) >= 0)
256                 {
257                 work = vw->list_pointer;
258                 }
259         else
260                 {
261                 gboolean found = FALSE;
262
263                 work = vw->list;
264                 while (work && !found)
265                         {
266                         FileData *temp;
267
268                         temp = static_cast<FileData *>(work->data);
269
270                         if (fd == temp)
271                                 {
272                                 found = TRUE;
273                                 }
274                         else
275                                 {
276                                 work = work->next;
277                                 }
278                         }
279                 }
280         if (!work) return;
281
282         work_ahead = nullptr;
283         if (next)
284                 {
285                 work = work->next;
286                 if (work) work_ahead = work->next;
287                 }
288         else
289                 {
290                 work = work->prev;
291                 if (work) work_ahead = work->prev;
292                 }
293
294         if (!work) return;
295
296         vw->list_pointer = work;
297         fd = static_cast<FileData *>(work->data);
298         image_change_fd(imd, fd, image_zoom_get_default(imd));
299
300         if (options->image.enable_read_ahead && work_ahead)
301                 {
302                 auto next_fd = static_cast<FileData *>(work_ahead->data);
303                 image_prebuffer_set(imd, next_fd);
304                 }
305 }
306
307 static void view_list_step_to_end(ViewWindow *vw, gboolean last)
308 {
309         ImageWindow *imd = view_window_active_image(vw);
310         FileData *fd;
311         GList *work;
312         GList *work_ahead;
313
314         if (!vw->list) return;
315
316         if (last)
317                 {
318                 work = g_list_last(vw->list);
319                 work_ahead = work->prev;
320                 }
321         else
322                 {
323                 work = vw->list;
324                 work_ahead = work->next;
325                 }
326
327         vw->list_pointer = work;
328         fd = static_cast<FileData *>(work->data);
329         image_change_fd(imd, fd, image_zoom_get_default(imd));
330
331         if (options->image.enable_read_ahead && work_ahead)
332                 {
333                 auto next_fd = static_cast<FileData *>(work_ahead->data);
334                 image_prebuffer_set(imd, next_fd);
335                 }
336 }
337
338 static void view_step_next(ViewWindow *vw)
339 {
340         if (vw->ss)
341                 {
342                 view_slideshow_next(vw);
343                 }
344         else if (vw->list)
345                 {
346                 view_list_step(vw, TRUE);
347                 }
348         else
349                 {
350                 view_collection_step(vw, TRUE);
351                 }
352 }
353
354 static void view_step_prev(ViewWindow *vw)
355 {
356         if (vw->ss)
357                 {
358                 view_slideshow_prev(vw);
359                 }
360         else if (vw->list)
361                 {
362                 view_list_step(vw, FALSE);
363                 }
364         else
365                 {
366                 view_collection_step(vw, FALSE);
367                 }
368 }
369
370 static void view_step_to_end(ViewWindow *vw, gboolean last)
371 {
372         if (vw->list)
373                 {
374                 view_list_step_to_end(vw, last);
375                 }
376         else
377                 {
378                 view_collection_step_to_end(vw, last);
379                 }
380 }
381
382 /*
383  *-----------------------------------------------------------------------------
384  * view window keyboard
385  *-----------------------------------------------------------------------------
386  */
387
388 static void view_window_press_cb(GtkWidget *, GdkEventButton *bevent, gpointer data)
389 {
390         auto vw = static_cast<ViewWindow *>(data);
391
392         switch (bevent->button)
393                 {
394                 case MOUSE_BUTTON_LEFT:
395                         if (bevent->type == GDK_2BUTTON_PRESS)
396                                 {
397                                 view_fullscreen_toggle(vw, TRUE);
398                                 }
399                         break;
400                 default:
401                         break;
402                 }
403 }
404
405 static gboolean view_window_key_press_cb(GtkWidget * (widget), GdkEventKey *event, gpointer data)
406 {
407         auto vw = static_cast<ViewWindow *>(data);
408         ImageWindow *imd;
409         gint stop_signal;
410         GtkWidget *menu;
411         gint x = 0;
412         gint y = 0;
413
414         imd = view_window_active_image(vw);
415
416         stop_signal = TRUE;
417         switch (event->keyval)
418                 {
419                 case GDK_KEY_Left: case GDK_KEY_KP_Left:
420                         x -= 1;
421                         break;
422                 case GDK_KEY_Right: case GDK_KEY_KP_Right:
423                         x += 1;
424                         break;
425                 case GDK_KEY_Up: case GDK_KEY_KP_Up:
426                         y -= 1;
427                         break;
428                 case GDK_KEY_Down: case GDK_KEY_KP_Down:
429                         y += 1;
430                         break;
431                 default:
432                         stop_signal = FALSE;
433                         break;
434                 }
435
436         if (x != 0 || y!= 0)
437                 {
438                 if (event->state & GDK_SHIFT_MASK)
439                         {
440                         x *= 3;
441                         y *= 3;
442                         }
443
444                 keyboard_scroll_calc(&x, &y, event);
445                 image_scroll(imd, x, y);
446                 }
447
448         if (stop_signal) return stop_signal;
449
450         if (event->state & GDK_CONTROL_MASK)
451                 {
452                 stop_signal = TRUE;
453                 switch (event->keyval)
454                         {
455                         case '1':
456                         case '2':
457                         case '3':
458                         case '4':
459                         case '5':
460                         case '6':
461                         case '7':
462                         case '8':
463                         case '9':
464                         case '0':
465                                 break;
466                         case 'C': case 'c':
467                                 file_util_copy(image_get_fd(imd), nullptr, nullptr, imd->widget);
468                                 break;
469                         case 'M': case 'm':
470                                 file_util_move(image_get_fd(imd), nullptr, nullptr, imd->widget);
471                                 break;
472                         case 'R': case 'r':
473                                 file_util_rename(image_get_fd(imd), nullptr, imd->widget);
474                                 break;
475                         case 'D': case 'd':
476                                 options->file_ops.safe_delete_enable = TRUE;
477                                 file_util_delete(image_get_fd(imd), nullptr, imd->widget);
478                                 break;
479                         case 'W': case 'w':
480                                 view_window_close(vw);
481                                 break;
482                         default:
483                                 stop_signal = FALSE;
484                                 break;
485                         }
486                 }
487         else if (event->state & GDK_SHIFT_MASK)
488                 {
489                 stop_signal = TRUE;
490                 switch (event->keyval)
491                         {
492                         case 'R': case 'r':
493                                 image_alter_orientation(imd, imd->image_fd, ALTER_ROTATE_180);
494                                 break;
495                         case 'M': case 'm':
496                                 image_alter_orientation(imd, imd->image_fd, ALTER_MIRROR);
497                                 break;
498                         case 'F': case 'f':
499                                 image_alter_orientation(imd, imd->image_fd, ALTER_FLIP);
500                                 break;
501                         case 'G': case 'g':
502                                 image_set_desaturate(imd, !image_get_desaturate(imd));
503                                 break;
504                         case 'P': case 'p':
505                                 {
506                                 FileData *fd;
507
508                                 view_fullscreen_toggle(vw, TRUE);
509                                 imd = view_window_active_image(vw);
510                                 fd = image_get_fd(imd);
511                                 print_window_new(fd,
512                                                  fd ? g_list_append(nullptr, file_data_ref(fd)) : nullptr,
513                                                  filelist_copy(vw->list), vw->window);
514                                 }
515                                 break;
516                         case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
517                                 if (options->file_ops.enable_delete_key)
518                                         {
519                                         options->file_ops.safe_delete_enable = FALSE;
520                                         file_util_delete(image_get_fd(imd), nullptr, imd->widget);
521                                         }
522                                 break;
523                         default:
524                                 stop_signal = FALSE;
525                                 break;
526                         }
527                 }
528         else
529                 {
530                 stop_signal = TRUE;
531                 switch (event->keyval)
532                         {
533                         case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
534                         case GDK_KEY_BackSpace:
535                         case 'B': case 'b':
536                                 view_step_prev(vw);
537                                 break;
538                         case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
539                         case GDK_KEY_space:
540                         case 'N': case 'n':
541                                 view_step_next(vw);
542                                 break;
543                         case GDK_KEY_Home: case GDK_KEY_KP_Home:
544                                 view_step_to_end(vw, FALSE);
545                                 break;
546                         case GDK_KEY_End: case GDK_KEY_KP_End:
547                                 view_step_to_end(vw, TRUE);
548                                 break;
549                         case '+': case '=': case GDK_KEY_KP_Add:
550                                 image_zoom_adjust(imd, get_zoom_increment());
551                                 break;
552                         case '-': case GDK_KEY_KP_Subtract:
553                                 image_zoom_adjust(imd, -get_zoom_increment());
554                                 break;
555                         case 'X': case 'x': case GDK_KEY_KP_Multiply:
556                                 image_zoom_set(imd, 0.0);
557                                 break;
558                         case 'Z': case 'z': case GDK_KEY_KP_Divide: case '1':
559                                 image_zoom_set(imd, 1.0);
560                                 break;
561                         case '2':
562                                 image_zoom_set(imd, 2.0);
563                                 break;
564                         case '3':
565                                 image_zoom_set(imd, 3.0);
566                                 break;
567                         case '4':
568                                 image_zoom_set(imd, 4.0);
569                                 break;
570                         case '7':
571                                 image_zoom_set(imd, -4.0);
572                                 break;
573                         case '8':
574                                 image_zoom_set(imd, -3.0);
575                                 break;
576                         case '9':
577                                 image_zoom_set(imd, -2.0);
578                                 break;
579                         case 'W': case 'w':
580                                 image_zoom_set_fill_geometry(imd, FALSE);
581                                 break;
582                         case 'H': case 'h':
583                                 image_zoom_set_fill_geometry(imd, TRUE);
584                                 break;
585                         case 'R': case 'r':
586                                 image_reload(imd);
587                                 break;
588                         case 'S': case 's':
589                                 if (vw->ss)
590                                         {
591                                         view_slideshow_stop(vw);
592                                         }
593                                 else
594                                         {
595                                         view_slideshow_start(vw);
596                                         }
597                                 break;
598                         case 'P': case 'p':
599                                 slideshow_pause_toggle(vw->ss);
600                                 break;
601                         case 'F': case 'f':
602                         case 'V': case 'v':
603                         case GDK_KEY_F11:
604                                 view_fullscreen_toggle(vw, FALSE);
605                                 break;
606                         case 'I': case 'i':
607                                 view_overlay_toggle(vw);
608                                 break;
609                         case ']':
610                                 image_alter_orientation(imd, imd->image_fd, ALTER_ROTATE_90);
611                                 break;
612                         case '[':
613                                 image_alter_orientation(imd, imd->image_fd, ALTER_ROTATE_90_CC);
614                                 break;
615                         case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
616                                 if (options->file_ops.enable_delete_key)
617                                         {
618                                         options->file_ops.safe_delete_enable = TRUE;
619                                         file_util_delete(image_get_fd(imd), nullptr, imd->widget);
620                                         }
621                                 break;
622                         case GDK_KEY_Escape:
623                                 if (vw->fs)
624                                         {
625                                         view_fullscreen_toggle(vw, TRUE);
626                                         }
627                                 else
628                                         {
629                                         view_window_close(vw);
630                                         }
631                                 break;
632                         case GDK_KEY_Menu:
633                         case GDK_KEY_F10:
634                                 menu = view_popup_menu(vw);
635                                 gtk_menu_popup_at_widget(GTK_MENU(menu), widget, GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER, nullptr);
636                                 break;
637                         default:
638                                 stop_signal = FALSE;
639                                 break;
640                         }
641                 }
642         if (!stop_signal && is_help_key(event))
643                 {
644                 help_window_show("GuideOtherWindowsImageWindow.html");
645                 stop_signal = TRUE;
646                 }
647
648         return stop_signal;
649 }
650
651 /*
652  *-----------------------------------------------------------------------------
653  * view window main routines
654  *-----------------------------------------------------------------------------
655  */
656 static void button_cb(ImageWindow *imd, GdkEventButton *event, gpointer data)
657 {
658         auto vw = static_cast<ViewWindow *>(data);
659         GtkWidget *menu;
660         gchar *dest_dir;
661         LayoutWindow *lw_new;
662
663         switch (event->button)
664                 {
665                 case MOUSE_BUTTON_LEFT:
666                         if (options->image_l_click_archive && imd->image_fd->format_class == FORMAT_CLASS_ARCHIVE)
667                                 {
668                                 dest_dir = open_archive(imd->image_fd);
669                                 if (dest_dir)
670                                         {
671                                         lw_new = layout_new_from_default();
672                                         layout_set_path(lw_new, dest_dir);
673                                         g_free(dest_dir);
674                                         }
675                                 else
676                                         {
677                                         warning_dialog(_("Cannot open archive file"), _("See the Log Window"), GQ_ICON_DIALOG_WARNING, nullptr);
678                                         }
679                                 }
680                         else if (options->image_l_click_video && options->image_l_click_video_editor && imd->image_fd->format_class == FORMAT_CLASS_VIDEO)
681                                 {
682                                 start_editor_from_file(options->image_l_click_video_editor, imd->image_fd);
683                                 }
684                         else if (options->image_lm_click_nav)
685                                 view_step_next(vw);
686                         break;
687                 case MOUSE_BUTTON_MIDDLE:
688                         if (options->image_lm_click_nav)
689                                 view_step_prev(vw);
690                         break;
691                 case MOUSE_BUTTON_RIGHT:
692                         menu = view_popup_menu(vw);
693                         gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
694                         break;
695                 default:
696                         break;
697                 }
698 }
699
700 static void scroll_cb(ImageWindow *imd, GdkEventScroll *event, gpointer data)
701 {
702         auto vw = static_cast<ViewWindow *>(data);
703
704         if ((event->state & GDK_CONTROL_MASK) ||
705                                 (imd->mouse_wheel_mode && !options->image_lm_click_nav))
706                 {
707                 switch (event->direction)
708                         {
709                         case GDK_SCROLL_UP:
710                                 image_zoom_adjust_at_point(imd, get_zoom_increment(), event->x, event->y);
711                                 break;
712                         case GDK_SCROLL_DOWN:
713                                 image_zoom_adjust_at_point(imd, -get_zoom_increment(), event->x, event->y);
714                                 break;
715                         default:
716                                 break;
717                         }
718                 }
719         else if ( (event->state & GDK_SHIFT_MASK) != static_cast<guint>(options->mousewheel_scrolls))
720                 {
721                 switch (event->direction)
722                         {
723                         case GDK_SCROLL_UP:
724                                 image_scroll(imd, 0, -MOUSEWHEEL_SCROLL_SIZE);
725                                 break;
726                         case GDK_SCROLL_DOWN:
727                                 image_scroll(imd, 0, MOUSEWHEEL_SCROLL_SIZE);
728                                 break;
729                         case GDK_SCROLL_LEFT:
730                                 image_scroll(imd, -MOUSEWHEEL_SCROLL_SIZE, 0);
731                                 break;
732                         case GDK_SCROLL_RIGHT:
733                                 image_scroll(imd, MOUSEWHEEL_SCROLL_SIZE, 0);
734                                 break;
735                         default:
736                                 break;
737                         }
738                 }
739         else
740                 {
741                 switch (event->direction)
742                         {
743                         case GDK_SCROLL_UP:
744                                 view_step_prev(vw);
745                                 break;
746                         case GDK_SCROLL_DOWN:
747                                 view_step_next(vw);
748                                 break;
749                         default:
750                                 break;
751                         }
752                 }
753 }
754
755 static void view_image_set_buttons(ViewWindow *vw, ImageWindow *imd)
756 {
757         image_set_button_func(imd, button_cb, vw);
758         image_set_scroll_func(imd, scroll_cb, vw);
759 }
760
761 static void view_fullscreen_stop_func(FullScreenData *, gpointer data)
762 {
763         auto vw = static_cast<ViewWindow *>(data);
764
765         vw->fs = nullptr;
766
767         if (vw->ss) vw->ss->imd = vw->imd;
768 }
769
770 static void view_fullscreen_toggle(ViewWindow *vw, gboolean force_off)
771 {
772         if (force_off && !vw->fs) return;
773
774         if (vw->fs)
775                 {
776                 if (image_osd_get(vw->imd) & OSD_SHOW_INFO)
777                         image_osd_set(vw->imd, image_osd_get(vw->fs->imd));
778
779                 fullscreen_stop(vw->fs);
780                 }
781         else
782                 {
783                 vw->fs = fullscreen_start(vw->window, vw->imd, view_fullscreen_stop_func, vw);
784
785                 view_image_set_buttons(vw, vw->fs->imd);
786                 g_signal_connect(G_OBJECT(vw->fs->window), "key_press_event",
787                                  G_CALLBACK(view_window_key_press_cb), vw);
788
789                 if (vw->ss) vw->ss->imd = vw->fs->imd;
790
791                 if (image_osd_get(vw->imd) & OSD_SHOW_INFO)
792                         {
793                         image_osd_set(vw->fs->imd, image_osd_get(vw->imd));
794                         image_osd_set(vw->imd, OSD_SHOW_NOTHING);
795                         }
796                 }
797 }
798
799 static void view_overlay_toggle(ViewWindow *vw)
800 {
801         ImageWindow *imd;
802
803         imd = view_window_active_image(vw);
804
805         image_osd_toggle(imd);
806 }
807
808 static void view_slideshow_next(ViewWindow *vw)
809 {
810         if (vw->ss) slideshow_next(vw->ss);
811 }
812
813 static void view_slideshow_prev(ViewWindow *vw)
814 {
815         if (vw->ss) slideshow_prev(vw->ss);
816 }
817
818 static void view_slideshow_stop_func(SlideShowData *, gpointer data)
819 {
820         auto vw = static_cast<ViewWindow *>(data);
821         GList *work;
822         FileData *fd;
823
824         vw->ss = nullptr;
825
826         work = vw->list;
827         fd = image_get_fd(view_window_active_image(vw));
828         while (work)
829                 {
830                 FileData *temp;
831
832                 temp = static_cast<FileData *>(work->data);
833                 if (fd == temp)
834                         {
835                         vw->list_pointer = work;
836                         work = nullptr;
837                         }
838                 else
839                         {
840                         work = work->next;
841                         }
842                 }
843 }
844
845 static void view_slideshow_start(ViewWindow *vw)
846 {
847         if (!vw->ss)
848                 {
849                 CollectionData *cd;
850                 CollectInfo *info;
851
852                 if (vw->list)
853                         {
854                         vw->ss = slideshow_start_from_filelist(nullptr, view_window_active_image(vw),
855                                                                 filelist_copy(vw->list),
856                                                                 view_slideshow_stop_func, vw);
857                         vw->list_pointer = nullptr;
858                         return;
859                         }
860
861                 cd = image_get_collection(view_window_active_image(vw), &info);
862                 if (cd && info)
863                         {
864                         vw->ss = slideshow_start_from_collection(nullptr, view_window_active_image(vw), cd,
865                                                                  view_slideshow_stop_func, vw, info);
866                         }
867                 }
868 }
869
870 static void view_slideshow_stop(ViewWindow *vw)
871 {
872         if (vw->ss) slideshow_free(vw->ss);
873 }
874
875 static void view_window_destroy_cb(GtkWidget *, gpointer data)
876 {
877         auto vw = static_cast<ViewWindow *>(data);
878
879         view_window_list = g_list_remove(view_window_list, vw);
880
881         view_slideshow_stop(vw);
882         fullscreen_stop(vw->fs);
883
884         filelist_free(vw->list);
885
886         file_data_unregister_notify_func(view_window_notify_cb, vw);
887
888         g_free(vw);
889 }
890
891 static void view_window_close(ViewWindow *vw)
892 {
893         view_slideshow_stop(vw);
894         view_fullscreen_toggle(vw, TRUE);
895         gq_gtk_widget_destroy(vw->window);
896 }
897
898 static gboolean view_window_delete_cb(GtkWidget *, GdkEventAny *, gpointer data)
899 {
900         auto vw = static_cast<ViewWindow *>(data);
901
902         view_window_close(vw);
903         return TRUE;
904 }
905
906 static ViewWindow *real_view_window_new(FileData *fd, GList *list, CollectionData *cd, CollectInfo *info)
907 {
908         ViewWindow *vw;
909         GtkAllocation req_size;
910         GdkGeometry geometry;
911         gint w;
912         gint h;
913
914         if (!fd && !list && (!cd || !info)) return nullptr;
915
916         vw = g_new0(ViewWindow, 1);
917
918         vw->window = window_new("view", PIXBUF_INLINE_ICON_VIEW, nullptr, nullptr);
919         DEBUG_NAME(vw->window);
920
921         geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
922         geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
923         gtk_window_set_geometry_hints(GTK_WINDOW(vw->window), nullptr, &geometry, GDK_HINT_MIN_SIZE);
924
925         gtk_window_set_resizable(GTK_WINDOW(vw->window), TRUE);
926         gtk_container_set_border_width(GTK_CONTAINER(vw->window), 0);
927
928         vw->imd = image_new(FALSE);
929         image_color_profile_set(vw->imd,
930                                 options->color_profile.input_type,
931                                 options->color_profile.use_image);
932         image_color_profile_set_use(vw->imd, options->color_profile.enabled);
933
934         image_background_set_color_from_options(vw->imd, FALSE);
935
936         image_attach_window(vw->imd, vw->window, nullptr, GQ_APPNAME, TRUE);
937
938         image_auto_refresh_enable(vw->imd, TRUE);
939         image_top_window_set_sync(vw->imd, TRUE);
940
941         gq_gtk_container_add(GTK_WIDGET(vw->window), vw->imd->widget);
942         gtk_widget_show(vw->imd->widget);
943
944         view_window_dnd_init(vw);
945
946         view_image_set_buttons(vw, vw->imd);
947
948         g_signal_connect(G_OBJECT(vw->window), "destroy",
949                          G_CALLBACK(view_window_destroy_cb), vw);
950         g_signal_connect(G_OBJECT(vw->window), "delete_event",
951                          G_CALLBACK(view_window_delete_cb), vw);
952         g_signal_connect(G_OBJECT(vw->window), "key_press_event",
953                          G_CALLBACK(view_window_key_press_cb), vw);
954         g_signal_connect(G_OBJECT(vw->window), "button_press_event",
955                          G_CALLBACK(view_window_press_cb), vw);
956
957         if (cd && info)
958                 {
959                 image_change_from_collection(vw->imd, cd, info, image_zoom_get_default(nullptr));
960                 /* Grab the fd so we can correctly size the window in
961                    the call to image_load_dimensions() below. */
962                 fd = info->fd;
963                 if (options->image.enable_read_ahead)
964                         {
965                         CollectInfo * r_info = collection_next_by_info(cd, info);
966                         if (!r_info) r_info = collection_prev_by_info(cd, info);
967                         if (r_info) image_prebuffer_set(vw->imd, r_info->fd);
968                         }
969                 }
970         else if (list)
971                 {
972                 view_window_set_list(vw, list);
973                 vw->list_pointer = vw->list;
974                 image_change_fd(vw->imd, static_cast<FileData *>(vw->list->data), image_zoom_get_default(nullptr));
975                 /* Set fd to first in list */
976                 fd = static_cast<FileData *>(vw->list->data);
977
978                 if (options->image.enable_read_ahead)
979                         {
980                         GList *work = vw->list->next;
981                         if (work) image_prebuffer_set(vw->imd, static_cast<FileData *>(work->data));
982                         }
983                 }
984         else
985                 {
986                 image_change_fd(vw->imd, fd, image_zoom_get_default(nullptr));
987                 }
988
989         /* Wait until image is loaded otherwise size is not defined */
990         image_load_dimensions(fd, &w, &h);
991
992         if (options->image.limit_window_size)
993                 {
994                 gint mw = gdk_screen_width() * options->image.max_window_size / 100;
995                 gint mh = gdk_screen_height() * options->image.max_window_size / 100;
996
997                 if (w > mw) w = mw;
998                 if (h > mh) h = mh;
999                 }
1000
1001         gtk_window_set_default_size(GTK_WINDOW(vw->window), w, h);
1002         req_size.x = req_size.y = 0;
1003         req_size.width = w;
1004         req_size.height = h;
1005         gtk_widget_size_allocate(GTK_WIDGET(vw->window), &req_size);
1006
1007         gtk_window_set_focus_on_map(GTK_WINDOW(vw->window), FALSE);
1008         gtk_widget_show(vw->window);
1009
1010         view_window_list = g_list_append(view_window_list, vw);
1011
1012         file_data_register_notify_func(view_window_notify_cb, vw, NOTIFY_PRIORITY_LOW);
1013
1014         /** @FIXME This is a hack to fix #965 View in new window - blank image
1015          * The problem occurs when zoom is set to Original Size and Preload
1016          * Next Image is set.
1017          * An extra reload is required to force the image to be displayed.
1018          * See also layout-image.cc layout_image_full_screen_start()
1019          * This is probably not the correct solution.
1020          **/
1021         image_reload(vw->imd);
1022
1023         return vw;
1024 }
1025
1026 static void view_window_collection_unref_cb(GtkWidget *, gpointer data)
1027 {
1028         auto cd = static_cast<CollectionData *>(data);
1029
1030         collection_unref(cd);
1031 }
1032
1033 void view_window_new(FileData *fd)
1034 {
1035         GList *list;
1036
1037         if (fd)
1038                 {
1039                 if (file_extension_match(fd->path, GQ_COLLECTION_EXT))
1040                         {
1041                         ViewWindow *vw;
1042                         CollectionData *cd;
1043                         CollectInfo *info;
1044
1045                         cd = collection_new(fd->path);
1046                         if (collection_load(cd, fd->path, COLLECTION_LOAD_NONE))
1047                                 {
1048                                 info = collection_get_first(cd);
1049                                 }
1050                         else
1051                                 {
1052                                 collection_unref(cd);
1053                                 cd = nullptr;
1054                                 info = nullptr;
1055                                 }
1056                         vw = real_view_window_new(nullptr, nullptr, cd, info);
1057                         if (vw && cd)
1058                                 {
1059                                 g_signal_connect(G_OBJECT(vw->window), "destroy",
1060                                                  G_CALLBACK(view_window_collection_unref_cb), cd);
1061                                 }
1062                         }
1063                 else if (isdir(fd->path) && filelist_read(fd, &list, nullptr))
1064                         {
1065                         list = filelist_sort_path(list);
1066                         list = filelist_filter(list, FALSE);
1067                         real_view_window_new(nullptr, list, nullptr, nullptr);
1068                         filelist_free(list);
1069                         }
1070                 else
1071                         {
1072                         real_view_window_new(fd, nullptr, nullptr, nullptr);
1073                         }
1074                 }
1075 }
1076
1077 void view_window_new_from_list(GList *list)
1078 {
1079         real_view_window_new(nullptr, list, nullptr, nullptr);
1080 }
1081
1082 void view_window_new_from_collection(CollectionData *cd, CollectInfo *info)
1083 {
1084         real_view_window_new(nullptr, nullptr, cd, info);
1085 }
1086
1087 /*
1088  *-----------------------------------------------------------------------------
1089  * public
1090  *-----------------------------------------------------------------------------
1091  */
1092
1093 void view_window_colors_update()
1094 {
1095         GList *work;
1096
1097         work = view_window_list;
1098         while (work)
1099                 {
1100                 auto vw = static_cast<ViewWindow *>(work->data);
1101                 work = work->next;
1102
1103                 image_background_set_color_from_options(vw->imd, !!vw->fs);
1104                 }
1105 }
1106
1107 gboolean view_window_find_image(ImageWindow *imd, gint *index, gint *total)
1108 {
1109         GList *work;
1110
1111         work = view_window_list;
1112         while (work)
1113                 {
1114                 auto vw = static_cast<ViewWindow *>(work->data);
1115                 work = work->next;
1116
1117                 if (vw->imd == imd ||
1118                     (vw->fs && vw->fs->imd == imd))
1119                         {
1120                         if (vw->ss)
1121                                 {
1122                                 gint n;
1123                                 gint t;
1124
1125                                 n = g_list_length(vw->ss->list_done);
1126                                 t = n + g_list_length(vw->ss->list);
1127                                 if (n == 0) n = t;
1128                                 if (index) *index = n - 1;
1129                                 if (total) *total = t;
1130                                 }
1131                         else
1132                                 {
1133                                 if (index) *index = g_list_position(vw->list, vw->list_pointer);
1134                                 if (total) *total = g_list_length(vw->list);
1135                                 }
1136                         return TRUE;
1137                         }
1138                 }
1139
1140         return FALSE;
1141 }
1142
1143 /*
1144  *-----------------------------------------------------------------------------
1145  * view window menu routines and callbacks
1146  *-----------------------------------------------------------------------------
1147  */
1148
1149 static void view_new_window_cb(GtkWidget *, gpointer data)
1150 {
1151         auto vw = static_cast<ViewWindow *>(data);
1152         CollectionData *cd;
1153         CollectInfo *info;
1154
1155         cd = image_get_collection(vw->imd, &info);
1156
1157         if (cd && info)
1158                 {
1159                 view_window_new_from_collection(cd, info);
1160                 }
1161         else
1162                 {
1163                 view_window_new(image_get_fd(vw->imd));
1164                 }
1165 }
1166
1167 static void view_edit_cb(GtkWidget *widget, gpointer data)
1168 {
1169         ViewWindow *vw;
1170         ImageWindow *imd;
1171         auto key = static_cast<const gchar *>(data);
1172
1173         vw = static_cast<ViewWindow *>(submenu_item_get_data(widget));
1174         if (!vw) return;
1175
1176         if (!editor_window_flag_set(key))
1177                 {
1178                 view_fullscreen_toggle(vw, TRUE);
1179                 }
1180
1181         imd = view_window_active_image(vw);
1182         file_util_start_editor_from_file(key, image_get_fd(imd), imd->widget);
1183 }
1184
1185 static void view_alter_cb(GtkWidget *widget, gpointer data)
1186 {
1187         ViewWindow *vw;
1188         AlterType type;
1189
1190         vw = static_cast<ViewWindow *>(submenu_item_get_data(widget));
1191         type = static_cast<AlterType>(GPOINTER_TO_INT(data));
1192
1193         if (!vw) return;
1194         image_alter_orientation(vw->imd, vw->imd->image_fd, type);
1195 }
1196
1197 static void view_zoom_in_cb(GtkWidget *, gpointer data)
1198 {
1199         auto vw = static_cast<ViewWindow *>(data);
1200
1201         image_zoom_adjust(view_window_active_image(vw), get_zoom_increment());
1202 }
1203
1204 static void view_zoom_out_cb(GtkWidget *, gpointer data)
1205 {
1206         auto vw = static_cast<ViewWindow *>(data);
1207
1208         image_zoom_adjust(view_window_active_image(vw), -get_zoom_increment());
1209 }
1210
1211 static void view_zoom_1_1_cb(GtkWidget *, gpointer data)
1212 {
1213         auto vw = static_cast<ViewWindow *>(data);
1214
1215         image_zoom_set(view_window_active_image(vw), 1.0);
1216 }
1217
1218 static void view_zoom_fit_cb(GtkWidget *, gpointer data)
1219 {
1220         auto vw = static_cast<ViewWindow *>(data);
1221
1222         image_zoom_set(view_window_active_image(vw), 0.0);
1223 }
1224
1225 static void view_copy_cb(GtkWidget *, gpointer data)
1226 {
1227         auto vw = static_cast<ViewWindow *>(data);
1228         ImageWindow *imd;
1229
1230         imd = view_window_active_image(vw);
1231         file_util_copy(image_get_fd(imd), nullptr, nullptr, imd->widget);
1232 }
1233
1234 static void view_move_cb(GtkWidget *, gpointer data)
1235 {
1236         auto vw = static_cast<ViewWindow *>(data);
1237         ImageWindow *imd;
1238
1239         imd = view_window_active_image(vw);
1240         file_util_move(image_get_fd(imd), nullptr, nullptr, imd->widget);
1241 }
1242
1243 static void view_rename_cb(GtkWidget *, gpointer data)
1244 {
1245         auto vw = static_cast<ViewWindow *>(data);
1246         ImageWindow *imd;
1247
1248         imd = view_window_active_image(vw);
1249         file_util_rename(image_get_fd(imd), nullptr, imd->widget);
1250 }
1251
1252 static void view_delete_cb(GtkWidget *, gpointer data)
1253 {
1254         auto vw = static_cast<ViewWindow *>(data);
1255         ImageWindow *imd;
1256
1257         imd = view_window_active_image(vw);
1258         options->file_ops.safe_delete_enable = FALSE;
1259         file_util_delete(image_get_fd(imd), nullptr, imd->widget);
1260 }
1261
1262 static void view_move_to_trash_cb(GtkWidget *, gpointer data)
1263 {
1264         auto vw = static_cast<ViewWindow *>(data);
1265         ImageWindow *imd;
1266
1267         imd = view_window_active_image(vw);
1268         options->file_ops.safe_delete_enable = TRUE;
1269         file_util_delete(image_get_fd(imd), nullptr, imd->widget);
1270 }
1271
1272 static void view_copy_path_cb(GtkWidget *, gpointer data)
1273 {
1274         auto vw = static_cast<ViewWindow *>(data);
1275         ImageWindow *imd;
1276
1277         imd = view_window_active_image(vw);
1278         file_util_copy_path_to_clipboard(image_get_fd(imd), TRUE);
1279 }
1280
1281 static void view_copy_path_unquoted_cb(GtkWidget *, gpointer data)
1282 {
1283         auto vw = static_cast<ViewWindow *>(data);
1284         ImageWindow *imd;
1285
1286         imd = view_window_active_image(vw);
1287         file_util_copy_path_to_clipboard(image_get_fd(imd), FALSE);
1288 }
1289
1290 static void view_fullscreen_cb(GtkWidget *, gpointer data)
1291 {
1292         auto vw = static_cast<ViewWindow *>(data);
1293
1294         view_fullscreen_toggle(vw, FALSE);
1295 }
1296
1297 static void view_slideshow_start_cb(GtkWidget *, gpointer data)
1298 {
1299         auto vw = static_cast<ViewWindow *>(data);
1300
1301         view_slideshow_start(vw);
1302 }
1303
1304 static void view_slideshow_stop_cb(GtkWidget *, gpointer data)
1305 {
1306         auto vw = static_cast<ViewWindow *>(data);
1307
1308         view_slideshow_stop(vw);
1309 }
1310
1311 static void view_slideshow_pause_cb(GtkWidget *, gpointer data)
1312 {
1313         auto vw = static_cast<ViewWindow *>(data);
1314
1315         slideshow_pause_toggle(vw->ss);
1316 }
1317
1318 static void view_close_cb(GtkWidget *, gpointer data)
1319 {
1320         auto vw = static_cast<ViewWindow *>(data);
1321
1322         view_window_close(vw);
1323 }
1324
1325 static LayoutWindow *view_new_layout_with_fd(FileData *fd)
1326 {
1327         LayoutWindow *nw;
1328
1329         nw = layout_new(nullptr, nullptr);
1330         layout_sort_set_files(nw, nw->options.file_view_list_sort.method, nw->options.file_view_list_sort.ascend, nw->options.file_view_list_sort.case_sensitive);
1331         layout_set_fd(nw, fd);
1332         return nw;
1333 }
1334
1335
1336 static void view_set_layout_path_cb(GtkWidget *, gpointer data)
1337 {
1338         auto vw = static_cast<ViewWindow *>(data);
1339         LayoutWindow *lw;
1340         ImageWindow *imd;
1341
1342         imd = view_window_active_image(vw);
1343
1344         if (!imd || !imd->image_fd) return;
1345
1346         lw = layout_find_by_image_fd(imd);
1347         if (lw)
1348                 layout_set_fd(lw, imd->image_fd);
1349         else
1350                 view_new_layout_with_fd(imd->image_fd);
1351         view_window_close(vw);
1352 }
1353
1354 static void view_popup_menu_destroy_cb(GtkWidget *, gpointer data)
1355 {
1356         auto editmenu_fd_list = static_cast<GList *>(data);
1357
1358         filelist_free(editmenu_fd_list);
1359 }
1360
1361 static GList *view_window_get_fd_list(ViewWindow *vw)
1362 {
1363         GList *list = nullptr;
1364         ImageWindow *imd = view_window_active_image(vw);
1365
1366         if (imd)
1367                 {
1368                 FileData *fd = image_get_fd(imd);
1369                 if (fd) list = g_list_append(nullptr, file_data_ref(fd));
1370                 }
1371
1372         return list;
1373 }
1374
1375 /**
1376  * @brief Add file selection list to a collection
1377  * @param[in] widget
1378  * @param[in] data Index to the collection list menu item selected, or -1 for new collection
1379  *
1380  *
1381  */
1382 static void image_pop_menu_collections_cb(GtkWidget *widget, gpointer data)
1383 {
1384         ViewWindow *vw;
1385         ImageWindow *imd;
1386         FileData *fd;
1387         GList *selection_list = nullptr;
1388
1389         vw = static_cast<ViewWindow *>(submenu_item_get_data(widget));
1390         imd = view_window_active_image(vw);
1391         fd = image_get_fd(imd);
1392         selection_list = g_list_append(selection_list, fd);
1393         pop_menu_collections(selection_list, data);
1394
1395         filelist_free(selection_list);
1396 }
1397
1398 static GtkWidget *view_popup_menu(ViewWindow *vw)
1399 {
1400         GtkWidget *menu;
1401         GtkWidget *item;
1402         GList *editmenu_fd_list;
1403         GtkAccelGroup *accel_group;
1404
1405         menu = popup_menu_short_lived();
1406
1407         accel_group = gtk_accel_group_new();
1408         gtk_menu_set_accel_group(GTK_MENU(menu), accel_group);
1409
1410         g_object_set_data(G_OBJECT(menu), "window_keys", image_window_keys);
1411         g_object_set_data(G_OBJECT(menu), "accel_group", accel_group);
1412
1413         menu_item_add_icon(menu, _("Zoom _in"), GQ_ICON_ZOOM_IN, G_CALLBACK(view_zoom_in_cb), vw);
1414         menu_item_add_icon(menu, _("Zoom _out"), GQ_ICON_ZOOM_OUT, G_CALLBACK(view_zoom_out_cb), vw);
1415         menu_item_add_icon(menu, _("Zoom _1:1"), GQ_ICON_ZOOM_100, G_CALLBACK(view_zoom_1_1_cb), vw);
1416         menu_item_add_icon(menu, _("Zoom to fit"), GQ_ICON_ZOOM_FIT, G_CALLBACK(view_zoom_fit_cb), vw);
1417         menu_item_add_divider(menu);
1418
1419         editmenu_fd_list = view_window_get_fd_list(vw);
1420         g_signal_connect(G_OBJECT(menu), "destroy",
1421                          G_CALLBACK(view_popup_menu_destroy_cb), editmenu_fd_list);
1422         item = submenu_add_edit(menu, nullptr, G_CALLBACK(view_edit_cb), vw, editmenu_fd_list);
1423         menu_item_add_divider(item);
1424
1425         submenu_add_alter(menu, G_CALLBACK(view_alter_cb), vw);
1426
1427         menu_item_add_icon(menu, _("View in _new window"), GQ_ICON_NEW, G_CALLBACK(view_new_window_cb), vw);
1428         item = menu_item_add(menu, _("_Go to directory view"), G_CALLBACK(view_set_layout_path_cb), vw);
1429
1430         menu_item_add_divider(menu);
1431         menu_item_add_icon(menu, _("_Copy..."), GQ_ICON_COPY, G_CALLBACK(view_copy_cb), vw);
1432         menu_item_add(menu, _("_Move..."), G_CALLBACK(view_move_cb), vw);
1433         menu_item_add(menu, _("_Rename..."), G_CALLBACK(view_rename_cb), vw);
1434         menu_item_add(menu, _("_Copy path"), G_CALLBACK(view_copy_path_cb), vw);
1435         menu_item_add(menu, _("_Copy path unquoted"), G_CALLBACK(view_copy_path_unquoted_cb), vw);
1436
1437         menu_item_add_divider(menu);
1438         menu_item_add_icon(menu,
1439                                 options->file_ops.confirm_move_to_trash ? _("Move to Trash...") :
1440                                         _("Move to Trash"), GQ_ICON_DELETE,
1441                                 G_CALLBACK(view_move_to_trash_cb), vw);
1442         menu_item_add_icon(menu,
1443                                 options->file_ops.confirm_delete ? _("_Delete...") :
1444                                         _("_Delete"), GQ_ICON_DELETE_SHRED,
1445                                 G_CALLBACK(view_delete_cb), vw);
1446
1447         menu_item_add_divider(menu);
1448
1449         submenu_add_collections(menu, &item,
1450                                 G_CALLBACK(image_pop_menu_collections_cb), vw);
1451         gtk_widget_set_sensitive(item, TRUE);
1452         menu_item_add_divider(menu);
1453
1454         if (vw->ss)
1455                 {
1456                 menu_item_add(menu, _("Toggle _slideshow"), G_CALLBACK(view_slideshow_stop_cb), vw);
1457                 if (slideshow_paused(vw->ss))
1458                         {
1459                         item = menu_item_add(menu, _("Continue slides_how"),
1460                                              G_CALLBACK(view_slideshow_pause_cb), vw);
1461                         }
1462                 else
1463                         {
1464                         item = menu_item_add(menu, _("Pause slides_how"),
1465                                              G_CALLBACK(view_slideshow_pause_cb), vw);
1466                         }
1467                 }
1468         else
1469                 {
1470                 item = menu_item_add(menu, _("Toggle _slideshow"), G_CALLBACK(view_slideshow_start_cb), vw);
1471                 gtk_widget_set_sensitive(item, (vw->list != nullptr) || view_window_contains_collection(vw));
1472                 item = menu_item_add(menu, _("Pause slides_how"), G_CALLBACK(view_slideshow_pause_cb), vw);
1473                 gtk_widget_set_sensitive(item, FALSE);
1474                 }
1475
1476         if (vw->fs)
1477                 {
1478                 menu_item_add_icon(menu, _("Exit _full screen"), GQ_ICON_LEAVE_FULLSCREEN, G_CALLBACK(view_fullscreen_cb), vw);
1479                 }
1480         else
1481                 {
1482                 menu_item_add_icon(menu, _("_Full screen"), GQ_ICON_FULLSCREEN, G_CALLBACK(view_fullscreen_cb), vw);
1483                 }
1484
1485         menu_item_add_divider(menu);
1486         menu_item_add_icon(menu, _("C_lose window"), GQ_ICON_CLOSE, G_CALLBACK(view_close_cb), vw);
1487
1488         return menu;
1489 }
1490
1491 /*
1492  *-------------------------------------------------------------------
1493  * dnd confirm dir
1494  *-------------------------------------------------------------------
1495  */
1496
1497 struct CViewConfirmD {
1498         ViewWindow *vw;
1499         GList *list;
1500 };
1501
1502 static void view_dir_list_cancel(GtkWidget *, gpointer)
1503 {
1504         /* do nothing */
1505 }
1506
1507 static void view_dir_list_do(ViewWindow *vw, GList *list, gboolean skip, gboolean recurse)
1508 {
1509         GList *work;
1510
1511         view_window_set_list(vw, nullptr);
1512
1513         work = list;
1514         while (work)
1515                 {
1516                 auto fd = static_cast<FileData *>(work->data);
1517                 work = work->next;
1518
1519                 if (isdir(fd->path))
1520                         {
1521                         if (!skip)
1522                                 {
1523                                 GList *list = nullptr;
1524
1525                                 if (recurse)
1526                                         {
1527                                         list = filelist_recursive(fd);
1528                                         }
1529                                 else
1530                                         { /** @FIXME ?? */
1531                                         filelist_read(fd, &list, nullptr);
1532                                         list = filelist_sort_path(list);
1533                                         list = filelist_filter(list, FALSE);
1534                                         }
1535                                 if (list) vw->list = g_list_concat(vw->list, list);
1536                                 }
1537                         }
1538                 else
1539                         {
1540                         /** @FIXME no filtering here */
1541                         vw->list = g_list_append(vw->list, file_data_ref(fd));
1542                         }
1543                 }
1544
1545         if (vw->list)
1546                 {
1547                 FileData *fd;
1548
1549                 vw->list_pointer = vw->list;
1550                 fd = static_cast<FileData *>(vw->list->data);
1551                 image_change_fd(vw->imd, fd, image_zoom_get_default(vw->imd));
1552
1553                 work = vw->list->next;
1554                 if (options->image.enable_read_ahead && work)
1555                         {
1556                         fd = static_cast<FileData *>(work->data);
1557                         image_prebuffer_set(vw->imd, fd);
1558                         }
1559                 }
1560         else
1561                 {
1562                 image_change_fd(vw->imd, nullptr, image_zoom_get_default(vw->imd));
1563                 }
1564 }
1565
1566 static void view_dir_list_add(GtkWidget *, gpointer data)
1567 {
1568         auto d = static_cast<CViewConfirmD *>(data);
1569         view_dir_list_do(d->vw, d->list, FALSE, FALSE);
1570 }
1571
1572 static void view_dir_list_recurse(GtkWidget *, gpointer data)
1573 {
1574         auto d = static_cast<CViewConfirmD *>(data);
1575         view_dir_list_do(d->vw, d->list, FALSE, TRUE);
1576 }
1577
1578 static void view_dir_list_skip(GtkWidget *, gpointer data)
1579 {
1580         auto d = static_cast<CViewConfirmD *>(data);
1581         view_dir_list_do(d->vw, d->list, TRUE, FALSE);
1582 }
1583
1584 static void view_dir_list_destroy(GtkWidget *, gpointer data)
1585 {
1586         auto d = static_cast<CViewConfirmD *>(data);
1587         filelist_free(d->list);
1588         g_free(d);
1589 }
1590
1591 static GtkWidget *view_confirm_dir_list(ViewWindow *vw, GList *list)
1592 {
1593         GtkWidget *menu;
1594         CViewConfirmD *d;
1595
1596         d = g_new(CViewConfirmD, 1);
1597         d->vw = vw;
1598         d->list = list;
1599
1600         menu = popup_menu_short_lived();
1601         g_signal_connect(G_OBJECT(menu), "destroy",
1602                          G_CALLBACK(view_dir_list_destroy), d);
1603
1604         menu_item_add_stock(menu, _("Dropped list includes folders."), GQ_ICON_DND, nullptr, nullptr);
1605         menu_item_add_divider(menu);
1606         menu_item_add_icon(menu, _("_Add contents"), GQ_ICON_OK, G_CALLBACK(view_dir_list_add), d);
1607         menu_item_add_icon(menu, _("Add contents _recursive"), GQ_ICON_ADD, G_CALLBACK(view_dir_list_recurse), d);
1608         menu_item_add_icon(menu, _("_Skip folders"), GQ_ICON_REMOVE, G_CALLBACK(view_dir_list_skip), d);
1609         menu_item_add_divider(menu);
1610         menu_item_add_icon(menu, _("Cancel"), GQ_ICON_CANCEL, G_CALLBACK(view_dir_list_cancel), d);
1611
1612         return menu;
1613 }
1614
1615 /*
1616  *-----------------------------------------------------------------------------
1617  * image drag and drop routines
1618  *-----------------------------------------------------------------------------
1619  */
1620
1621 static void view_window_get_dnd_data(GtkWidget *, GdkDragContext *context,
1622                                      gint, gint,
1623                                      GtkSelectionData *selection_data, guint info,
1624                                      guint, gpointer data)
1625 {
1626         auto vw = static_cast<ViewWindow *>(data);
1627         ImageWindow *imd;
1628
1629         if (gtk_drag_get_source_widget(context) == vw->imd->pr) return;
1630
1631         imd = vw->imd;
1632
1633         if (info == TARGET_URI_LIST || info == TARGET_APP_COLLECTION_MEMBER)
1634                 {
1635                 CollectionData *source;
1636                 GList *list;
1637                 GList *info_list;
1638
1639                 if (info == TARGET_URI_LIST)
1640                         {
1641                         GList *work;
1642
1643                         list = uri_filelist_from_gtk_selection_data(selection_data);
1644
1645                         work = list;
1646                         while (work)
1647                                 {
1648                                 auto fd = static_cast<FileData *>(work->data);
1649                                 if (isdir(fd->path))
1650                                         {
1651                                         GtkWidget *menu;
1652                                         menu = view_confirm_dir_list(vw, list);
1653                                         gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
1654                                         return;
1655                                         }
1656                                 work = work->next;
1657                                 }
1658
1659                         list = filelist_filter(list, FALSE);
1660
1661                         source = nullptr;
1662                         info_list = nullptr;
1663                         }
1664                 else
1665                         {
1666                         source = collection_from_dnd_data(reinterpret_cast<const gchar *>(gtk_selection_data_get_data(selection_data)), &list, &info_list);
1667                         }
1668
1669                 if (list)
1670                         {
1671                         FileData *fd;
1672
1673                         fd = static_cast<FileData *>(list->data);
1674                         if (isfile(fd->path))
1675                                 {
1676                                 view_slideshow_stop(vw);
1677                                 view_window_set_list(vw, nullptr);
1678
1679                                 if (source && info_list)
1680                                         {
1681                                         image_change_from_collection(imd, source, static_cast<CollectInfo *>(info_list->data), image_zoom_get_default(imd));
1682                                         }
1683                                 else
1684                                         {
1685                                         if (list->next)
1686                                                 {
1687                                                 vw->list = list;
1688                                                 list = nullptr;
1689
1690                                                 vw->list_pointer = vw->list;
1691                                                 }
1692                                         image_change_fd(imd, fd, image_zoom_get_default(imd));
1693                                         }
1694                                 }
1695                         }
1696                 filelist_free(list);
1697                 g_list_free(info_list);
1698                 }
1699 }
1700
1701 static void view_window_set_dnd_data(GtkWidget *, GdkDragContext *,
1702                                      GtkSelectionData *selection_data, guint,
1703                                      guint, gpointer data)
1704 {
1705         auto vw = static_cast<ViewWindow *>(data);
1706         FileData *fd;
1707
1708         fd = image_get_fd(vw->imd);
1709
1710         if (fd)
1711                 {
1712                 GList *list;
1713
1714                 list = g_list_append(nullptr, fd);
1715                 uri_selection_data_set_uris_from_filelist(selection_data, list);
1716                 g_list_free(list);
1717                 }
1718         else
1719                 {
1720                 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
1721                                        8, nullptr, 0);
1722                 }
1723 }
1724
1725 static void view_window_dnd_init(ViewWindow *vw)
1726 {
1727         ImageWindow *imd;
1728
1729         imd = vw->imd;
1730
1731         gtk_drag_source_set(imd->pr, GDK_BUTTON2_MASK,
1732                             dnd_file_drag_types, dnd_file_drag_types_count,
1733                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
1734         g_signal_connect(G_OBJECT(imd->pr), "drag_data_get",
1735                          G_CALLBACK(view_window_set_dnd_data), vw);
1736
1737         gtk_drag_dest_set(imd->pr,
1738                           static_cast<GtkDestDefaults>(GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP),
1739                           dnd_file_drop_types, dnd_file_drop_types_count,
1740                           static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
1741         g_signal_connect(G_OBJECT(imd->pr), "drag_data_received",
1742                          G_CALLBACK(view_window_get_dnd_data), vw);
1743 }
1744
1745 /*
1746  *-----------------------------------------------------------------------------
1747  * maintenance (for rename, move, remove)
1748  *-----------------------------------------------------------------------------
1749  */
1750
1751 static void view_real_removed(ViewWindow *vw, FileData *fd)
1752 {
1753         ImageWindow *imd;
1754         FileData *image_fd;
1755
1756         imd = view_window_active_image(vw);
1757         image_fd = image_get_fd(imd);
1758
1759         if (image_fd && image_fd == fd)
1760                 {
1761                 if (vw->list)
1762                         {
1763                         view_list_step(vw, TRUE);
1764                         if (image_get_fd(imd) == image_fd)
1765                                 {
1766                                 view_list_step(vw, FALSE);
1767                                 }
1768                         }
1769                 else if (view_window_contains_collection(vw))
1770                         {
1771                         view_collection_step(vw, TRUE);
1772                         if (image_get_fd(imd) == image_fd)
1773                                 {
1774                                 view_collection_step(vw, FALSE);
1775                                 }
1776                         }
1777                 if (image_get_fd(imd) == image_fd)
1778                         {
1779                         image_change_fd(imd, nullptr, image_zoom_get_default(imd));
1780                         }
1781                 }
1782
1783         if (vw->list)
1784                 {
1785                 GList *work;
1786                 GList *old;
1787
1788                 old = vw->list_pointer;
1789
1790                 work = vw->list;
1791                 while (work)
1792                         {
1793                         FileData *chk_fd;
1794                         GList *chk_link;
1795
1796                         chk_fd = static_cast<FileData *>(work->data);
1797                         chk_link = work;
1798                         work = work->next;
1799
1800                         if (chk_fd == fd)
1801                                 {
1802                                 if (vw->list_pointer == chk_link)
1803                                         {
1804                                         vw->list_pointer = (chk_link->next) ? chk_link->next : chk_link->prev;
1805                                         }
1806                                 vw->list = g_list_remove(vw->list, chk_fd);
1807                                 file_data_unref(chk_fd);
1808                                 }
1809                         }
1810
1811                 /* handles stepping correctly when same image is in the list more than once */
1812                 if (old && old != vw->list_pointer)
1813                         {
1814                         FileData *fd;
1815
1816                         if (vw->list_pointer)
1817                                 {
1818                                 fd = static_cast<FileData *>(vw->list_pointer->data);
1819                                 }
1820                         else
1821                                 {
1822                                 fd = nullptr;
1823                                 }
1824
1825                         image_change_fd(imd, fd, image_zoom_get_default(imd));
1826                         }
1827                 }
1828
1829         image_osd_update(imd);
1830 }
1831
1832 static void view_window_notify_cb(FileData *fd, NotifyType type, gpointer data)
1833 {
1834         auto vw = static_cast<ViewWindow *>(data);
1835
1836         if (!(type & NOTIFY_CHANGE) || !fd->change) return;
1837
1838         DEBUG_1("Notify view_window: %s %04x", fd->path, type);
1839
1840         switch (fd->change->type)
1841                 {
1842                 case FILEDATA_CHANGE_MOVE:
1843                 case FILEDATA_CHANGE_RENAME:
1844                         break;
1845                 case FILEDATA_CHANGE_COPY:
1846                         break;
1847                 case FILEDATA_CHANGE_DELETE:
1848                         view_real_removed(vw, fd);
1849                         break;
1850                 case FILEDATA_CHANGE_UNSPECIFIED:
1851                 case FILEDATA_CHANGE_WRITE_METADATA:
1852                         break;
1853                 }
1854 }
1855 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */