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