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