2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
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.
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.
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.
25 #include <glib-object.h>
28 #include "collect-io.h"
35 #include "fullscreen.h"
36 #include "image-load.h"
37 #include "image-overlay.h"
40 #include "layout-util.h"
42 #include "main-defines.h"
47 #include "pixbuf-util.h"
49 #include "slideshow.h"
51 #include "ui-fileops.h"
53 #include "ui-utildlg.h"
54 #include "uri-utils.h"
70 static GList *view_window_list = nullptr;
73 static GtkWidget *view_popup_menu(ViewWindow *vw);
74 static void view_fullscreen_toggle(ViewWindow *vw, gboolean force_off);
75 static void view_overlay_toggle(ViewWindow *vw);
77 static void view_slideshow_next(ViewWindow *vw);
78 static void view_slideshow_prev(ViewWindow *vw);
79 static void view_slideshow_start(ViewWindow *vw);
80 static void view_slideshow_stop(ViewWindow *vw);
82 static void view_window_close(ViewWindow *vw);
84 static void view_window_dnd_init(ViewWindow *vw);
86 static void view_window_notify_cb(FileData *fd, NotifyType type, gpointer data);
90 * This array must be kept in sync with the contents of:\n
91 * @link view_popup_menu() @endlink \n
92 * @link view_window_key_press_cb() @endlink
94 * See also @link hard_coded_window_keys @endlink
96 hard_coded_window_keys image_window_keys[] = {
97 {GDK_CONTROL_MASK, 'C', N_("Copy")},
98 {GDK_CONTROL_MASK, 'M', N_("Move")},
99 {GDK_CONTROL_MASK, 'R', N_("Rename")},
100 {GDK_CONTROL_MASK, 'D', N_("Move to Trash")},
101 {static_cast<GdkModifierType>(0), GDK_KEY_Delete, N_("Move to Trash")},
102 {GDK_SHIFT_MASK, GDK_KEY_Delete, N_("Delete")},
103 {GDK_CONTROL_MASK, 'W', N_("Close window")},
104 {GDK_SHIFT_MASK, 'R', N_("Rotate 180°")},
105 {GDK_SHIFT_MASK, 'M', N_("Rotate mirror")},
106 {GDK_SHIFT_MASK, 'F', N_("Rotate flip")},
107 {static_cast<GdkModifierType>(0), ']', N_(" Rotate counterclockwise 90°")},
108 {static_cast<GdkModifierType>(0), '[', N_(" Rotate clockwise 90°")},
109 {static_cast<GdkModifierType>(0), GDK_KEY_Page_Up, N_("Previous")},
110 {static_cast<GdkModifierType>(0), GDK_KEY_KP_Page_Up, N_("Previous")},
111 {static_cast<GdkModifierType>(0), GDK_KEY_BackSpace, N_("Previous")},
112 {static_cast<GdkModifierType>(0), 'B', N_("Previous")},
113 {static_cast<GdkModifierType>(0), GDK_KEY_Page_Down, N_("Next")},
114 {static_cast<GdkModifierType>(0), GDK_KEY_KP_Page_Down, N_("Next")},
115 {static_cast<GdkModifierType>(0), GDK_KEY_space, N_("Next")},
116 {static_cast<GdkModifierType>(0), 'N', N_("Next")},
117 {static_cast<GdkModifierType>(0), GDK_KEY_equal, N_("Zoom in")},
118 {static_cast<GdkModifierType>(0), GDK_KEY_plus, N_("Zoom in")},
119 {static_cast<GdkModifierType>(0), GDK_KEY_minus, N_("Zoom out")},
120 {static_cast<GdkModifierType>(0), 'X', N_("Zoom to fit")},
121 {static_cast<GdkModifierType>(0), GDK_KEY_KP_Multiply, N_("Zoom to fit")},
122 {static_cast<GdkModifierType>(0), 'Z', N_("Zoom 1:1")},
123 {static_cast<GdkModifierType>(0), GDK_KEY_KP_Divide, N_("Zoom 1:1")},
124 {static_cast<GdkModifierType>(0), GDK_KEY_1, N_("Zoom 1:1")},
125 {static_cast<GdkModifierType>(0), '2', N_("Zoom 2:1")},
126 {static_cast<GdkModifierType>(0), '3', N_("Zoom 3:1")},
127 {static_cast<GdkModifierType>(0), '4', N_("Zoom 4:1")},
128 {static_cast<GdkModifierType>(0), '7', N_("Zoom 1:4")},
129 {static_cast<GdkModifierType>(0), '8', N_("Zoom 1:3")},
130 {static_cast<GdkModifierType>(0), '9', N_("Zoom 1:2")},
131 {static_cast<GdkModifierType>(0), 'W', N_("Zoom fit window width")},
132 {static_cast<GdkModifierType>(0), 'H', N_("Zoom fit window height")},
133 {static_cast<GdkModifierType>(0), 'S', N_("Toggle slideshow")},
134 {static_cast<GdkModifierType>(0), 'P', N_("Pause slideshow")},
135 {static_cast<GdkModifierType>(0), 'R', N_("Reload image")},
136 {static_cast<GdkModifierType>(0), 'F', N_("Full screen")},
137 {static_cast<GdkModifierType>(0), 'V', N_("Fullscreen")},
138 {static_cast<GdkModifierType>(0), GDK_KEY_F11, N_("Fullscreen")},
139 {static_cast<GdkModifierType>(0), 'I', N_("Image overlay")},
140 {static_cast<GdkModifierType>(0), GDK_KEY_Escape, N_("Exit fullscreen")},
141 {static_cast<GdkModifierType>(0), GDK_KEY_Escape, N_("Close window")},
142 {GDK_SHIFT_MASK, 'G', N_("Desaturate")},
143 {GDK_SHIFT_MASK, 'P', N_("Print")},
144 {static_cast<GdkModifierType>(0), 0, nullptr}
149 *-----------------------------------------------------------------------------
151 *-----------------------------------------------------------------------------
154 static ImageWindow *view_window_active_image(ViewWindow *vw)
156 if (vw->fs) return vw->fs->imd;
161 static void view_window_set_list(ViewWindow *vw, GList *list)
164 filelist_free(vw->list);
166 vw->list_pointer = nullptr;
168 vw->list = filelist_copy(list);
171 static gboolean view_window_contains_collection(ViewWindow *vw)
176 cd = image_get_collection(view_window_active_image(vw), &info);
181 static void view_collection_step(ViewWindow *vw, gboolean next)
183 ImageWindow *imd = view_window_active_image(vw);
186 CollectInfo *read_ahead_info = nullptr;
188 cd = image_get_collection(imd, &info);
190 if (!cd || !info) return;
194 info = collection_next_by_info(cd, info);
195 if (options->image.enable_read_ahead)
197 read_ahead_info = collection_next_by_info(cd, info);
198 if (!read_ahead_info) read_ahead_info = collection_prev_by_info(cd, info);
203 info = collection_prev_by_info(cd, info);
204 if (options->image.enable_read_ahead)
206 read_ahead_info = collection_prev_by_info(cd, info);
207 if (!read_ahead_info) read_ahead_info = collection_next_by_info(cd, info);
213 image_change_from_collection(imd, cd, info, image_zoom_get_default(imd));
215 if (read_ahead_info) image_prebuffer_set(imd, read_ahead_info->fd);
220 static void view_collection_step_to_end(ViewWindow *vw, gboolean last)
222 ImageWindow *imd = view_window_active_image(vw);
225 CollectInfo *read_ahead_info = nullptr;
227 cd = image_get_collection(imd, &info);
229 if (!cd || !info) return;
233 info = collection_get_last(cd);
234 if (options->image.enable_read_ahead) read_ahead_info = collection_prev_by_info(cd, info);
238 info = collection_get_first(cd);
239 if (options->image.enable_read_ahead) read_ahead_info = collection_next_by_info(cd, info);
244 image_change_from_collection(imd, cd, info, image_zoom_get_default(imd));
245 if (read_ahead_info) image_prebuffer_set(imd, read_ahead_info->fd);
249 static void view_list_step(ViewWindow *vw, gboolean next)
251 ImageWindow *imd = view_window_active_image(vw);
256 if (!vw->list) return;
258 fd = image_get_fd(imd);
261 if (g_list_position(vw->list, vw->list_pointer) >= 0)
263 work = vw->list_pointer;
267 gboolean found = FALSE;
270 while (work && !found)
274 temp = static_cast<FileData *>(work->data);
288 work_ahead = nullptr;
292 if (work) work_ahead = work->next;
297 if (work) work_ahead = work->prev;
302 vw->list_pointer = work;
303 fd = static_cast<FileData *>(work->data);
304 image_change_fd(imd, fd, image_zoom_get_default(imd));
306 if (options->image.enable_read_ahead && work_ahead)
308 auto next_fd = static_cast<FileData *>(work_ahead->data);
309 image_prebuffer_set(imd, next_fd);
313 static void view_list_step_to_end(ViewWindow *vw, gboolean last)
315 ImageWindow *imd = view_window_active_image(vw);
320 if (!vw->list) return;
324 work = g_list_last(vw->list);
325 work_ahead = work->prev;
330 work_ahead = work->next;
333 vw->list_pointer = work;
334 fd = static_cast<FileData *>(work->data);
335 image_change_fd(imd, fd, image_zoom_get_default(imd));
337 if (options->image.enable_read_ahead && work_ahead)
339 auto next_fd = static_cast<FileData *>(work_ahead->data);
340 image_prebuffer_set(imd, next_fd);
344 static void view_step_next(ViewWindow *vw)
348 view_slideshow_next(vw);
352 view_list_step(vw, TRUE);
356 view_collection_step(vw, TRUE);
360 static void view_step_prev(ViewWindow *vw)
364 view_slideshow_prev(vw);
368 view_list_step(vw, FALSE);
372 view_collection_step(vw, FALSE);
376 static void view_step_to_end(ViewWindow *vw, gboolean last)
380 view_list_step_to_end(vw, last);
384 view_collection_step_to_end(vw, last);
389 *-----------------------------------------------------------------------------
390 * view window keyboard
391 *-----------------------------------------------------------------------------
394 static void view_window_press_cb(GtkWidget *, GdkEventButton *bevent, gpointer data)
396 auto vw = static_cast<ViewWindow *>(data);
398 switch (bevent->button)
400 case MOUSE_BUTTON_LEFT:
401 if (bevent->type == GDK_2BUTTON_PRESS)
403 view_fullscreen_toggle(vw, TRUE);
411 static gboolean view_window_key_press_cb(GtkWidget * (widget), GdkEventKey *event, gpointer data)
413 auto vw = static_cast<ViewWindow *>(data);
420 imd = view_window_active_image(vw);
423 switch (event->keyval)
425 case GDK_KEY_Left: case GDK_KEY_KP_Left:
428 case GDK_KEY_Right: case GDK_KEY_KP_Right:
431 case GDK_KEY_Up: case GDK_KEY_KP_Up:
434 case GDK_KEY_Down: case GDK_KEY_KP_Down:
444 if (event->state & GDK_SHIFT_MASK)
450 keyboard_scroll_calc(&x, &y, event);
451 image_scroll(imd, x, y);
454 if (stop_signal) return stop_signal;
456 if (event->state & GDK_CONTROL_MASK)
459 switch (event->keyval)
473 file_util_copy(image_get_fd(imd), nullptr, nullptr, imd->widget);
476 file_util_move(image_get_fd(imd), nullptr, nullptr, imd->widget);
479 file_util_rename(image_get_fd(imd), nullptr, imd->widget);
482 options->file_ops.safe_delete_enable = TRUE;
483 file_util_delete(image_get_fd(imd), nullptr, imd->widget);
486 view_window_close(vw);
493 else if (event->state & GDK_SHIFT_MASK)
496 switch (event->keyval)
499 image_alter_orientation(imd, imd->image_fd, ALTER_ROTATE_180);
502 image_alter_orientation(imd, imd->image_fd, ALTER_MIRROR);
505 image_alter_orientation(imd, imd->image_fd, ALTER_FLIP);
508 image_set_desaturate(imd, !image_get_desaturate(imd));
514 view_fullscreen_toggle(vw, TRUE);
515 imd = view_window_active_image(vw);
516 fd = image_get_fd(imd);
518 fd ? g_list_append(nullptr, file_data_ref(fd)) : nullptr,
519 filelist_copy(vw->list), vw->window);
522 case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
523 if (options->file_ops.enable_delete_key)
525 options->file_ops.safe_delete_enable = FALSE;
526 file_util_delete(image_get_fd(imd), nullptr, imd->widget);
537 switch (event->keyval)
539 case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
540 case GDK_KEY_BackSpace:
544 case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
549 case GDK_KEY_Home: case GDK_KEY_KP_Home:
550 view_step_to_end(vw, FALSE);
552 case GDK_KEY_End: case GDK_KEY_KP_End:
553 view_step_to_end(vw, TRUE);
555 case '+': case '=': case GDK_KEY_KP_Add:
556 image_zoom_adjust(imd, get_zoom_increment());
558 case '-': case GDK_KEY_KP_Subtract:
559 image_zoom_adjust(imd, -get_zoom_increment());
561 case 'X': case 'x': case GDK_KEY_KP_Multiply:
562 image_zoom_set(imd, 0.0);
564 case 'Z': case 'z': case GDK_KEY_KP_Divide: case '1':
565 image_zoom_set(imd, 1.0);
568 image_zoom_set(imd, 2.0);
571 image_zoom_set(imd, 3.0);
574 image_zoom_set(imd, 4.0);
577 image_zoom_set(imd, -4.0);
580 image_zoom_set(imd, -3.0);
583 image_zoom_set(imd, -2.0);
586 image_zoom_set_fill_geometry(imd, FALSE);
589 image_zoom_set_fill_geometry(imd, TRUE);
597 view_slideshow_stop(vw);
601 view_slideshow_start(vw);
605 slideshow_pause_toggle(vw->ss);
610 view_fullscreen_toggle(vw, FALSE);
613 view_overlay_toggle(vw);
616 image_alter_orientation(imd, imd->image_fd, ALTER_ROTATE_90);
619 image_alter_orientation(imd, imd->image_fd, ALTER_ROTATE_90_CC);
621 case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
622 if (options->file_ops.enable_delete_key)
624 options->file_ops.safe_delete_enable = TRUE;
625 file_util_delete(image_get_fd(imd), nullptr, imd->widget);
631 view_fullscreen_toggle(vw, TRUE);
635 view_window_close(vw);
640 menu = view_popup_menu(vw);
641 gtk_menu_popup_at_widget(GTK_MENU(menu), widget, GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER, nullptr);
648 if (!stop_signal && is_help_key(event))
650 help_window_show("GuideOtherWindowsImageWindow.html");
658 *-----------------------------------------------------------------------------
659 * view window main routines
660 *-----------------------------------------------------------------------------
662 static void button_cb(ImageWindow *imd, GdkEventButton *event, gpointer data)
664 auto vw = static_cast<ViewWindow *>(data);
667 LayoutWindow *lw_new;
669 switch (event->button)
671 case MOUSE_BUTTON_LEFT:
672 if (options->image_l_click_archive && imd->image_fd->format_class == FORMAT_CLASS_ARCHIVE)
674 dest_dir = open_archive(imd->image_fd);
677 lw_new = layout_new_from_default();
678 layout_set_path(lw_new, dest_dir);
683 warning_dialog(_("Cannot open archive file"), _("See the Log Window"), GQ_ICON_DIALOG_WARNING, nullptr);
686 else if (options->image_l_click_video && options->image_l_click_video_editor && imd->image_fd->format_class == FORMAT_CLASS_VIDEO)
688 start_editor_from_file(options->image_l_click_video_editor, imd->image_fd);
690 else if (options->image_lm_click_nav)
693 case MOUSE_BUTTON_MIDDLE:
694 if (options->image_lm_click_nav)
697 case MOUSE_BUTTON_RIGHT:
698 menu = view_popup_menu(vw);
699 gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
706 static void scroll_cb(ImageWindow *imd, GdkEventScroll *event, gpointer data)
708 auto vw = static_cast<ViewWindow *>(data);
710 if ((event->state & GDK_CONTROL_MASK) ||
711 (imd->mouse_wheel_mode && !options->image_lm_click_nav))
713 switch (event->direction)
716 image_zoom_adjust_at_point(imd, get_zoom_increment(), event->x, event->y);
718 case GDK_SCROLL_DOWN:
719 image_zoom_adjust_at_point(imd, -get_zoom_increment(), event->x, event->y);
725 else if ( (event->state & GDK_SHIFT_MASK) != static_cast<guint>(options->mousewheel_scrolls))
727 switch (event->direction)
730 image_scroll(imd, 0, -MOUSEWHEEL_SCROLL_SIZE);
732 case GDK_SCROLL_DOWN:
733 image_scroll(imd, 0, MOUSEWHEEL_SCROLL_SIZE);
735 case GDK_SCROLL_LEFT:
736 image_scroll(imd, -MOUSEWHEEL_SCROLL_SIZE, 0);
738 case GDK_SCROLL_RIGHT:
739 image_scroll(imd, MOUSEWHEEL_SCROLL_SIZE, 0);
747 switch (event->direction)
752 case GDK_SCROLL_DOWN:
761 static void view_image_set_buttons(ViewWindow *vw, ImageWindow *imd)
763 image_set_button_func(imd, button_cb, vw);
764 image_set_scroll_func(imd, scroll_cb, vw);
767 static void view_fullscreen_stop_func(FullScreenData *, gpointer data)
769 auto vw = static_cast<ViewWindow *>(data);
773 if (vw->ss) vw->ss->imd = vw->imd;
776 static void view_fullscreen_toggle(ViewWindow *vw, gboolean force_off)
778 if (force_off && !vw->fs) return;
782 if (image_osd_get(vw->imd) & OSD_SHOW_INFO)
783 image_osd_set(vw->imd, image_osd_get(vw->fs->imd));
785 fullscreen_stop(vw->fs);
789 vw->fs = fullscreen_start(vw->window, vw->imd, view_fullscreen_stop_func, vw);
791 view_image_set_buttons(vw, vw->fs->imd);
792 g_signal_connect(G_OBJECT(vw->fs->window), "key_press_event",
793 G_CALLBACK(view_window_key_press_cb), vw);
795 if (vw->ss) vw->ss->imd = vw->fs->imd;
797 if (image_osd_get(vw->imd) & OSD_SHOW_INFO)
799 image_osd_set(vw->fs->imd, image_osd_get(vw->imd));
800 image_osd_set(vw->imd, OSD_SHOW_NOTHING);
805 static void view_overlay_toggle(ViewWindow *vw)
809 imd = view_window_active_image(vw);
811 image_osd_toggle(imd);
814 static void view_slideshow_next(ViewWindow *vw)
816 if (vw->ss) slideshow_next(vw->ss);
819 static void view_slideshow_prev(ViewWindow *vw)
821 if (vw->ss) slideshow_prev(vw->ss);
824 static void view_slideshow_stop_func(SlideShowData *, gpointer data)
826 auto vw = static_cast<ViewWindow *>(data);
833 fd = image_get_fd(view_window_active_image(vw));
838 temp = static_cast<FileData *>(work->data);
841 vw->list_pointer = work;
851 static void view_slideshow_start(ViewWindow *vw)
860 vw->ss = slideshow_start_from_filelist(nullptr, view_window_active_image(vw),
861 filelist_copy(vw->list),
862 view_slideshow_stop_func, vw);
863 vw->list_pointer = nullptr;
867 cd = image_get_collection(view_window_active_image(vw), &info);
870 vw->ss = slideshow_start_from_collection(nullptr, view_window_active_image(vw), cd,
871 view_slideshow_stop_func, vw, info);
876 static void view_slideshow_stop(ViewWindow *vw)
878 if (vw->ss) slideshow_free(vw->ss);
881 static void view_window_destroy_cb(GtkWidget *, gpointer data)
883 auto vw = static_cast<ViewWindow *>(data);
885 view_window_list = g_list_remove(view_window_list, vw);
887 view_slideshow_stop(vw);
888 fullscreen_stop(vw->fs);
890 filelist_free(vw->list);
892 file_data_unregister_notify_func(view_window_notify_cb, vw);
897 static void view_window_close(ViewWindow *vw)
899 view_slideshow_stop(vw);
900 view_fullscreen_toggle(vw, TRUE);
901 gq_gtk_widget_destroy(vw->window);
904 static gboolean view_window_delete_cb(GtkWidget *, GdkEventAny *, gpointer data)
906 auto vw = static_cast<ViewWindow *>(data);
908 view_window_close(vw);
912 static ViewWindow *real_view_window_new(FileData *fd, GList *list, CollectionData *cd, CollectInfo *info)
915 GtkAllocation req_size;
916 GdkGeometry geometry;
920 if (!fd && !list && (!cd || !info)) return nullptr;
922 vw = g_new0(ViewWindow, 1);
924 vw->window = window_new("view", PIXBUF_INLINE_ICON_VIEW, nullptr, nullptr);
925 DEBUG_NAME(vw->window);
927 geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
928 geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
929 gtk_window_set_geometry_hints(GTK_WINDOW(vw->window), nullptr, &geometry, GDK_HINT_MIN_SIZE);
931 gtk_window_set_resizable(GTK_WINDOW(vw->window), TRUE);
932 gtk_container_set_border_width(GTK_CONTAINER(vw->window), 0);
934 vw->imd = image_new(FALSE);
935 image_color_profile_set(vw->imd,
936 options->color_profile.input_type,
937 options->color_profile.use_image);
938 image_color_profile_set_use(vw->imd, options->color_profile.enabled);
940 image_background_set_color_from_options(vw->imd, FALSE);
942 image_attach_window(vw->imd, vw->window, nullptr, GQ_APPNAME, TRUE);
944 image_auto_refresh_enable(vw->imd, TRUE);
945 image_top_window_set_sync(vw->imd, TRUE);
947 gq_gtk_container_add(GTK_WIDGET(vw->window), vw->imd->widget);
948 gtk_widget_show(vw->imd->widget);
950 view_window_dnd_init(vw);
952 view_image_set_buttons(vw, vw->imd);
954 g_signal_connect(G_OBJECT(vw->window), "destroy",
955 G_CALLBACK(view_window_destroy_cb), vw);
956 g_signal_connect(G_OBJECT(vw->window), "delete_event",
957 G_CALLBACK(view_window_delete_cb), vw);
958 g_signal_connect(G_OBJECT(vw->window), "key_press_event",
959 G_CALLBACK(view_window_key_press_cb), vw);
960 g_signal_connect(G_OBJECT(vw->window), "button_press_event",
961 G_CALLBACK(view_window_press_cb), vw);
965 image_change_from_collection(vw->imd, cd, info, image_zoom_get_default(nullptr));
966 /* Grab the fd so we can correctly size the window in
967 the call to image_load_dimensions() below. */
969 if (options->image.enable_read_ahead)
971 CollectInfo * r_info = collection_next_by_info(cd, info);
972 if (!r_info) r_info = collection_prev_by_info(cd, info);
973 if (r_info) image_prebuffer_set(vw->imd, r_info->fd);
978 view_window_set_list(vw, list);
979 vw->list_pointer = vw->list;
980 image_change_fd(vw->imd, static_cast<FileData *>(vw->list->data), image_zoom_get_default(nullptr));
981 /* Set fd to first in list */
982 fd = static_cast<FileData *>(vw->list->data);
984 if (options->image.enable_read_ahead)
986 GList *work = vw->list->next;
987 if (work) image_prebuffer_set(vw->imd, static_cast<FileData *>(work->data));
992 image_change_fd(vw->imd, fd, image_zoom_get_default(nullptr));
995 /* Wait until image is loaded otherwise size is not defined */
996 image_load_dimensions(fd, &w, &h);
998 if (options->image.limit_window_size)
1000 gint mw = gdk_screen_width() * options->image.max_window_size / 100;
1001 gint mh = gdk_screen_height() * options->image.max_window_size / 100;
1007 gtk_window_set_default_size(GTK_WINDOW(vw->window), w, h);
1008 req_size.x = req_size.y = 0;
1010 req_size.height = h;
1011 gtk_widget_size_allocate(GTK_WIDGET(vw->window), &req_size);
1013 gtk_window_set_focus_on_map(GTK_WINDOW(vw->window), FALSE);
1014 gtk_widget_show(vw->window);
1016 view_window_list = g_list_append(view_window_list, vw);
1018 file_data_register_notify_func(view_window_notify_cb, vw, NOTIFY_PRIORITY_LOW);
1020 /** @FIXME This is a hack to fix #965 View in new window - blank image
1021 * The problem occurs when zoom is set to Original Size and Preload
1022 * Next Image is set.
1023 * An extra reload is required to force the image to be displayed.
1024 * See also layout-image.cc layout_image_full_screen_start()
1025 * This is probably not the correct solution.
1027 image_reload(vw->imd);
1032 static void view_window_collection_unref_cb(GtkWidget *, gpointer data)
1034 auto cd = static_cast<CollectionData *>(data);
1036 collection_unref(cd);
1039 void view_window_new(FileData *fd)
1045 if (file_extension_match(fd->path, GQ_COLLECTION_EXT))
1051 cd = collection_new(fd->path);
1052 if (collection_load(cd, fd->path, COLLECTION_LOAD_NONE))
1054 info = collection_get_first(cd);
1058 collection_unref(cd);
1062 vw = real_view_window_new(nullptr, nullptr, cd, info);
1065 g_signal_connect(G_OBJECT(vw->window), "destroy",
1066 G_CALLBACK(view_window_collection_unref_cb), cd);
1069 else if (isdir(fd->path) && filelist_read(fd, &list, nullptr))
1071 list = filelist_sort_path(list);
1072 list = filelist_filter(list, FALSE);
1073 real_view_window_new(nullptr, list, nullptr, nullptr);
1074 filelist_free(list);
1078 real_view_window_new(fd, nullptr, nullptr, nullptr);
1083 void view_window_new_from_list(GList *list)
1085 real_view_window_new(nullptr, list, nullptr, nullptr);
1088 void view_window_new_from_collection(CollectionData *cd, CollectInfo *info)
1090 real_view_window_new(nullptr, nullptr, cd, info);
1094 *-----------------------------------------------------------------------------
1096 *-----------------------------------------------------------------------------
1099 void view_window_colors_update()
1103 work = view_window_list;
1106 auto vw = static_cast<ViewWindow *>(work->data);
1109 image_background_set_color_from_options(vw->imd, !!vw->fs);
1113 gboolean view_window_find_image(ImageWindow *imd, gint *index, gint *total)
1117 work = view_window_list;
1120 auto vw = static_cast<ViewWindow *>(work->data);
1123 if (vw->imd == imd ||
1124 (vw->fs && vw->fs->imd == imd))
1131 n = g_list_length(vw->ss->list_done);
1132 t = n + g_list_length(vw->ss->list);
1134 if (index) *index = n - 1;
1135 if (total) *total = t;
1139 if (index) *index = g_list_position(vw->list, vw->list_pointer);
1140 if (total) *total = g_list_length(vw->list);
1150 *-----------------------------------------------------------------------------
1151 * view window menu routines and callbacks
1152 *-----------------------------------------------------------------------------
1155 static void view_new_window_cb(GtkWidget *, gpointer data)
1157 auto vw = static_cast<ViewWindow *>(data);
1161 cd = image_get_collection(vw->imd, &info);
1165 view_window_new_from_collection(cd, info);
1169 view_window_new(image_get_fd(vw->imd));
1173 static void view_edit_cb(GtkWidget *widget, gpointer data)
1177 auto key = static_cast<const gchar *>(data);
1179 vw = static_cast<ViewWindow *>(submenu_item_get_data(widget));
1182 if (!editor_window_flag_set(key))
1184 view_fullscreen_toggle(vw, TRUE);
1187 imd = view_window_active_image(vw);
1188 file_util_start_editor_from_file(key, image_get_fd(imd), imd->widget);
1191 static void view_alter_cb(GtkWidget *widget, gpointer data)
1196 vw = static_cast<ViewWindow *>(submenu_item_get_data(widget));
1197 type = static_cast<AlterType>(GPOINTER_TO_INT(data));
1200 image_alter_orientation(vw->imd, vw->imd->image_fd, type);
1203 static void view_zoom_in_cb(GtkWidget *, gpointer data)
1205 auto vw = static_cast<ViewWindow *>(data);
1207 image_zoom_adjust(view_window_active_image(vw), get_zoom_increment());
1210 static void view_zoom_out_cb(GtkWidget *, gpointer data)
1212 auto vw = static_cast<ViewWindow *>(data);
1214 image_zoom_adjust(view_window_active_image(vw), -get_zoom_increment());
1217 static void view_zoom_1_1_cb(GtkWidget *, gpointer data)
1219 auto vw = static_cast<ViewWindow *>(data);
1221 image_zoom_set(view_window_active_image(vw), 1.0);
1224 static void view_zoom_fit_cb(GtkWidget *, gpointer data)
1226 auto vw = static_cast<ViewWindow *>(data);
1228 image_zoom_set(view_window_active_image(vw), 0.0);
1231 static void view_copy_cb(GtkWidget *, gpointer data)
1233 auto vw = static_cast<ViewWindow *>(data);
1236 imd = view_window_active_image(vw);
1237 file_util_copy(image_get_fd(imd), nullptr, nullptr, imd->widget);
1240 static void view_move_cb(GtkWidget *, gpointer data)
1242 auto vw = static_cast<ViewWindow *>(data);
1245 imd = view_window_active_image(vw);
1246 file_util_move(image_get_fd(imd), nullptr, nullptr, imd->widget);
1249 static void view_rename_cb(GtkWidget *, gpointer data)
1251 auto vw = static_cast<ViewWindow *>(data);
1254 imd = view_window_active_image(vw);
1255 file_util_rename(image_get_fd(imd), nullptr, imd->widget);
1258 static void view_delete_cb(GtkWidget *, gpointer data)
1260 auto vw = static_cast<ViewWindow *>(data);
1263 imd = view_window_active_image(vw);
1264 options->file_ops.safe_delete_enable = FALSE;
1265 file_util_delete(image_get_fd(imd), nullptr, imd->widget);
1268 static void view_move_to_trash_cb(GtkWidget *, gpointer data)
1270 auto vw = static_cast<ViewWindow *>(data);
1273 imd = view_window_active_image(vw);
1274 options->file_ops.safe_delete_enable = TRUE;
1275 file_util_delete(image_get_fd(imd), nullptr, imd->widget);
1278 static void view_copy_path_cb(GtkWidget *, gpointer data)
1280 auto vw = static_cast<ViewWindow *>(data);
1283 imd = view_window_active_image(vw);
1284 file_util_copy_path_to_clipboard(image_get_fd(imd), TRUE);
1287 static void view_copy_path_unquoted_cb(GtkWidget *, gpointer data)
1289 auto vw = static_cast<ViewWindow *>(data);
1292 imd = view_window_active_image(vw);
1293 file_util_copy_path_to_clipboard(image_get_fd(imd), FALSE);
1296 static void view_fullscreen_cb(GtkWidget *, gpointer data)
1298 auto vw = static_cast<ViewWindow *>(data);
1300 view_fullscreen_toggle(vw, FALSE);
1303 static void view_slideshow_start_cb(GtkWidget *, gpointer data)
1305 auto vw = static_cast<ViewWindow *>(data);
1307 view_slideshow_start(vw);
1310 static void view_slideshow_stop_cb(GtkWidget *, gpointer data)
1312 auto vw = static_cast<ViewWindow *>(data);
1314 view_slideshow_stop(vw);
1317 static void view_slideshow_pause_cb(GtkWidget *, gpointer data)
1319 auto vw = static_cast<ViewWindow *>(data);
1321 slideshow_pause_toggle(vw->ss);
1324 static void view_close_cb(GtkWidget *, gpointer data)
1326 auto vw = static_cast<ViewWindow *>(data);
1328 view_window_close(vw);
1331 static LayoutWindow *view_new_layout_with_fd(FileData *fd)
1335 nw = layout_new(nullptr, nullptr);
1336 layout_sort_set_files(nw, nw->options.file_view_list_sort.method, nw->options.file_view_list_sort.ascend, nw->options.file_view_list_sort.case_sensitive);
1337 layout_set_fd(nw, fd);
1342 static void view_set_layout_path_cb(GtkWidget *, gpointer data)
1344 auto vw = static_cast<ViewWindow *>(data);
1348 imd = view_window_active_image(vw);
1350 if (!imd || !imd->image_fd) return;
1352 lw = layout_find_by_image_fd(imd);
1354 layout_set_fd(lw, imd->image_fd);
1356 view_new_layout_with_fd(imd->image_fd);
1357 view_window_close(vw);
1360 static void view_popup_menu_destroy_cb(GtkWidget *, gpointer data)
1362 auto editmenu_fd_list = static_cast<GList *>(data);
1364 filelist_free(editmenu_fd_list);
1367 static GList *view_window_get_fd_list(ViewWindow *vw)
1369 GList *list = nullptr;
1370 ImageWindow *imd = view_window_active_image(vw);
1374 FileData *fd = image_get_fd(imd);
1375 if (fd) list = g_list_append(nullptr, file_data_ref(fd));
1382 * @brief Add file selection list to a collection
1384 * @param[in] data Index to the collection list menu item selected, or -1 for new collection
1388 static void image_pop_menu_collections_cb(GtkWidget *widget, gpointer data)
1393 GList *selection_list = nullptr;
1395 vw = static_cast<ViewWindow *>(submenu_item_get_data(widget));
1396 imd = view_window_active_image(vw);
1397 fd = image_get_fd(imd);
1398 selection_list = g_list_append(selection_list, fd);
1399 pop_menu_collections(selection_list, data);
1401 filelist_free(selection_list);
1404 static GtkWidget *view_popup_menu(ViewWindow *vw)
1408 GList *editmenu_fd_list;
1409 GtkAccelGroup *accel_group;
1411 menu = popup_menu_short_lived();
1413 accel_group = gtk_accel_group_new();
1414 gtk_menu_set_accel_group(GTK_MENU(menu), accel_group);
1416 g_object_set_data(G_OBJECT(menu), "window_keys", image_window_keys);
1417 g_object_set_data(G_OBJECT(menu), "accel_group", accel_group);
1419 menu_item_add_icon(menu, _("Zoom _in"), GQ_ICON_ZOOM_IN, G_CALLBACK(view_zoom_in_cb), vw);
1420 menu_item_add_icon(menu, _("Zoom _out"), GQ_ICON_ZOOM_OUT, G_CALLBACK(view_zoom_out_cb), vw);
1421 menu_item_add_icon(menu, _("Zoom _1:1"), GQ_ICON_ZOOM_100, G_CALLBACK(view_zoom_1_1_cb), vw);
1422 menu_item_add_icon(menu, _("Zoom to fit"), GQ_ICON_ZOOM_FIT, G_CALLBACK(view_zoom_fit_cb), vw);
1423 menu_item_add_divider(menu);
1425 editmenu_fd_list = view_window_get_fd_list(vw);
1426 g_signal_connect(G_OBJECT(menu), "destroy",
1427 G_CALLBACK(view_popup_menu_destroy_cb), editmenu_fd_list);
1428 item = submenu_add_edit(menu, nullptr, G_CALLBACK(view_edit_cb), vw, editmenu_fd_list);
1429 menu_item_add_divider(item);
1431 submenu_add_alter(menu, G_CALLBACK(view_alter_cb), vw);
1433 menu_item_add_icon(menu, _("View in _new window"), GQ_ICON_NEW, G_CALLBACK(view_new_window_cb), vw);
1434 item = menu_item_add(menu, _("_Go to directory view"), G_CALLBACK(view_set_layout_path_cb), vw);
1436 menu_item_add_divider(menu);
1437 menu_item_add_icon(menu, _("_Copy..."), GQ_ICON_COPY, G_CALLBACK(view_copy_cb), vw);
1438 menu_item_add(menu, _("_Move..."), G_CALLBACK(view_move_cb), vw);
1439 menu_item_add(menu, _("_Rename..."), G_CALLBACK(view_rename_cb), vw);
1440 menu_item_add(menu, _("_Copy path"), G_CALLBACK(view_copy_path_cb), vw);
1441 menu_item_add(menu, _("_Copy path unquoted"), G_CALLBACK(view_copy_path_unquoted_cb), vw);
1443 menu_item_add_divider(menu);
1444 menu_item_add_icon(menu,
1445 options->file_ops.confirm_move_to_trash ? _("Move to Trash...") :
1446 _("Move to Trash"), GQ_ICON_DELETE,
1447 G_CALLBACK(view_move_to_trash_cb), vw);
1448 menu_item_add_icon(menu,
1449 options->file_ops.confirm_delete ? _("_Delete...") :
1450 _("_Delete"), GQ_ICON_DELETE_SHRED,
1451 G_CALLBACK(view_delete_cb), vw);
1453 menu_item_add_divider(menu);
1455 submenu_add_collections(menu, &item,
1456 G_CALLBACK(image_pop_menu_collections_cb), vw);
1457 gtk_widget_set_sensitive(item, TRUE);
1458 menu_item_add_divider(menu);
1462 menu_item_add(menu, _("Toggle _slideshow"), G_CALLBACK(view_slideshow_stop_cb), vw);
1463 if (slideshow_paused(vw->ss))
1465 item = menu_item_add(menu, _("Continue slides_how"),
1466 G_CALLBACK(view_slideshow_pause_cb), vw);
1470 item = menu_item_add(menu, _("Pause slides_how"),
1471 G_CALLBACK(view_slideshow_pause_cb), vw);
1476 item = menu_item_add(menu, _("Toggle _slideshow"), G_CALLBACK(view_slideshow_start_cb), vw);
1477 gtk_widget_set_sensitive(item, (vw->list != nullptr) || view_window_contains_collection(vw));
1478 item = menu_item_add(menu, _("Pause slides_how"), G_CALLBACK(view_slideshow_pause_cb), vw);
1479 gtk_widget_set_sensitive(item, FALSE);
1484 menu_item_add_icon(menu, _("Exit _full screen"), GQ_ICON_LEAVE_FULLSCREEN, G_CALLBACK(view_fullscreen_cb), vw);
1488 menu_item_add_icon(menu, _("_Full screen"), GQ_ICON_FULLSCREEN, G_CALLBACK(view_fullscreen_cb), vw);
1491 menu_item_add_divider(menu);
1492 menu_item_add_icon(menu, _("C_lose window"), GQ_ICON_CLOSE, G_CALLBACK(view_close_cb), vw);
1498 *-------------------------------------------------------------------
1500 *-------------------------------------------------------------------
1503 struct CViewConfirmD {
1508 static void view_dir_list_cancel(GtkWidget *, gpointer)
1513 static void view_dir_list_do(ViewWindow *vw, GList *list, gboolean skip, gboolean recurse)
1517 view_window_set_list(vw, nullptr);
1522 auto fd = static_cast<FileData *>(work->data);
1525 if (isdir(fd->path))
1529 GList *list = nullptr;
1533 list = filelist_recursive(fd);
1537 filelist_read(fd, &list, nullptr);
1538 list = filelist_sort_path(list);
1539 list = filelist_filter(list, FALSE);
1541 if (list) vw->list = g_list_concat(vw->list, list);
1546 /** @FIXME no filtering here */
1547 vw->list = g_list_append(vw->list, file_data_ref(fd));
1555 vw->list_pointer = vw->list;
1556 fd = static_cast<FileData *>(vw->list->data);
1557 image_change_fd(vw->imd, fd, image_zoom_get_default(vw->imd));
1559 work = vw->list->next;
1560 if (options->image.enable_read_ahead && work)
1562 fd = static_cast<FileData *>(work->data);
1563 image_prebuffer_set(vw->imd, fd);
1568 image_change_fd(vw->imd, nullptr, image_zoom_get_default(vw->imd));
1572 static void view_dir_list_add(GtkWidget *, gpointer data)
1574 auto d = static_cast<CViewConfirmD *>(data);
1575 view_dir_list_do(d->vw, d->list, FALSE, FALSE);
1578 static void view_dir_list_recurse(GtkWidget *, gpointer data)
1580 auto d = static_cast<CViewConfirmD *>(data);
1581 view_dir_list_do(d->vw, d->list, FALSE, TRUE);
1584 static void view_dir_list_skip(GtkWidget *, gpointer data)
1586 auto d = static_cast<CViewConfirmD *>(data);
1587 view_dir_list_do(d->vw, d->list, TRUE, FALSE);
1590 static void view_dir_list_destroy(GtkWidget *, gpointer data)
1592 auto d = static_cast<CViewConfirmD *>(data);
1593 filelist_free(d->list);
1597 static GtkWidget *view_confirm_dir_list(ViewWindow *vw, GList *list)
1602 d = g_new(CViewConfirmD, 1);
1606 menu = popup_menu_short_lived();
1607 g_signal_connect(G_OBJECT(menu), "destroy",
1608 G_CALLBACK(view_dir_list_destroy), d);
1610 menu_item_add_stock(menu, _("Dropped list includes folders."), GQ_ICON_DND, nullptr, nullptr);
1611 menu_item_add_divider(menu);
1612 menu_item_add_icon(menu, _("_Add contents"), GQ_ICON_OK, G_CALLBACK(view_dir_list_add), d);
1613 menu_item_add_icon(menu, _("Add contents _recursive"), GQ_ICON_ADD, G_CALLBACK(view_dir_list_recurse), d);
1614 menu_item_add_icon(menu, _("_Skip folders"), GQ_ICON_REMOVE, G_CALLBACK(view_dir_list_skip), d);
1615 menu_item_add_divider(menu);
1616 menu_item_add_icon(menu, _("Cancel"), GQ_ICON_CANCEL, G_CALLBACK(view_dir_list_cancel), d);
1622 *-----------------------------------------------------------------------------
1623 * image drag and drop routines
1624 *-----------------------------------------------------------------------------
1627 static void view_window_get_dnd_data(GtkWidget *, GdkDragContext *context,
1629 GtkSelectionData *selection_data, guint info,
1630 guint, gpointer data)
1632 auto vw = static_cast<ViewWindow *>(data);
1635 if (gtk_drag_get_source_widget(context) == vw->imd->pr) return;
1639 if (info == TARGET_URI_LIST || info == TARGET_APP_COLLECTION_MEMBER)
1641 CollectionData *source;
1645 if (info == TARGET_URI_LIST)
1649 list = uri_filelist_from_gtk_selection_data(selection_data);
1654 auto fd = static_cast<FileData *>(work->data);
1655 if (isdir(fd->path))
1658 menu = view_confirm_dir_list(vw, list);
1659 gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
1665 list = filelist_filter(list, FALSE);
1668 info_list = nullptr;
1672 source = collection_from_dnd_data(reinterpret_cast<const gchar *>(gtk_selection_data_get_data(selection_data)), &list, &info_list);
1679 fd = static_cast<FileData *>(list->data);
1680 if (isfile(fd->path))
1682 view_slideshow_stop(vw);
1683 view_window_set_list(vw, nullptr);
1685 if (source && info_list)
1687 image_change_from_collection(imd, source, static_cast<CollectInfo *>(info_list->data), image_zoom_get_default(imd));
1696 vw->list_pointer = vw->list;
1698 image_change_fd(imd, fd, image_zoom_get_default(imd));
1702 filelist_free(list);
1703 g_list_free(info_list);
1707 static void view_window_set_dnd_data(GtkWidget *, GdkDragContext *,
1708 GtkSelectionData *selection_data, guint,
1709 guint, gpointer data)
1711 auto vw = static_cast<ViewWindow *>(data);
1714 fd = image_get_fd(vw->imd);
1720 list = g_list_append(nullptr, fd);
1721 uri_selection_data_set_uris_from_filelist(selection_data, list);
1726 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
1731 static void view_window_dnd_init(ViewWindow *vw)
1737 gtk_drag_source_set(imd->pr, GDK_BUTTON2_MASK,
1738 dnd_file_drag_types, dnd_file_drag_types_count,
1739 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
1740 g_signal_connect(G_OBJECT(imd->pr), "drag_data_get",
1741 G_CALLBACK(view_window_set_dnd_data), vw);
1743 gtk_drag_dest_set(imd->pr,
1744 static_cast<GtkDestDefaults>(GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP),
1745 dnd_file_drop_types, dnd_file_drop_types_count,
1746 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
1747 g_signal_connect(G_OBJECT(imd->pr), "drag_data_received",
1748 G_CALLBACK(view_window_get_dnd_data), vw);
1752 *-----------------------------------------------------------------------------
1753 * maintenance (for rename, move, remove)
1754 *-----------------------------------------------------------------------------
1757 static void view_real_removed(ViewWindow *vw, FileData *fd)
1762 imd = view_window_active_image(vw);
1763 image_fd = image_get_fd(imd);
1765 if (image_fd && image_fd == fd)
1769 view_list_step(vw, TRUE);
1770 if (image_get_fd(imd) == image_fd)
1772 view_list_step(vw, FALSE);
1775 else if (view_window_contains_collection(vw))
1777 view_collection_step(vw, TRUE);
1778 if (image_get_fd(imd) == image_fd)
1780 view_collection_step(vw, FALSE);
1783 if (image_get_fd(imd) == image_fd)
1785 image_change_fd(imd, nullptr, image_zoom_get_default(imd));
1794 old = vw->list_pointer;
1802 chk_fd = static_cast<FileData *>(work->data);
1808 if (vw->list_pointer == chk_link)
1810 vw->list_pointer = (chk_link->next) ? chk_link->next : chk_link->prev;
1812 vw->list = g_list_remove(vw->list, chk_fd);
1813 file_data_unref(chk_fd);
1817 /* handles stepping correctly when same image is in the list more than once */
1818 if (old && old != vw->list_pointer)
1822 if (vw->list_pointer)
1824 fd = static_cast<FileData *>(vw->list_pointer->data);
1831 image_change_fd(imd, fd, image_zoom_get_default(imd));
1835 image_osd_update(imd);
1838 static void view_window_notify_cb(FileData *fd, NotifyType type, gpointer data)
1840 auto vw = static_cast<ViewWindow *>(data);
1842 if (!(type & NOTIFY_CHANGE) || !fd->change) return;
1844 DEBUG_1("Notify view_window: %s %04x", fd->path, type);
1846 switch (fd->change->type)
1848 case FILEDATA_CHANGE_MOVE:
1849 case FILEDATA_CHANGE_RENAME:
1851 case FILEDATA_CHANGE_COPY:
1853 case FILEDATA_CHANGE_DELETE:
1854 view_real_removed(vw, fd);
1856 case FILEDATA_CHANGE_UNSPECIFIED:
1857 case FILEDATA_CHANGE_WRITE_METADATA:
1861 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */