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