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