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