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