__version__ = "0.11.1"

__license__ = """
Mirage, a fast GTK+ Image Viewer
Copyright 2007 Scott Horowitz <stonecrest@gmail.com>
Copyright 2020 Thomas Ross <thomasross@thomasross.io>

This file is part of Mirage.

Mirage is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

Mirage is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import gi


gi.require_version("Gtk", "3.0")
gi.require_version("GExiv2", "0.10")
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, Gio, GExiv2
import cairo
import os, sys, getopt, configparser, string, gc
import random, urllib.request, locale
import stat, time, subprocess, shutil, filecmp
import tempfile, socket, threading
import traceback

from .fullscreen_controls import FullscreenControls

try:
    import hashlib

    HAS_HASHLIB = True
except:
    HAS_HASHLIB = False
    import md5

try:
    import mirage.imgfuncs as imgfuncs

    HAS_IMGFUNCS = True
except:
    HAS_IMGFUNCS = False
    print("imgfuncs.so module not found, rotating/flipping images will be disabled.")

try:
    import mirage.xmouse as xmouse

    HAS_XMOUSE = True
except:
    HAS_XMOUSE = False
    print("xmouse.so module not found, some screenshot capabilities will be disabled.")


def valid_int(inputstring):
    try:
        x = int(inputstring)
        return True
    except:
        return False


class Base(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            application_id="io.thomasross.mirage",
            flags=Gio.ApplicationFlags.NON_UNIQUE
        )

    def do_activate(self):
        # Constants
        self.open_mode_smart = 0
        self.open_mode_fit = 1
        self.open_mode_1to1 = 2
        self.open_mode_last = 3
        self.min_zoomratio = 0.02

        # Initialize vars:
        width = 600
        height = 400
        bgcolor_found = False
        self.simple_bgcolor = False
        # Current image:
        self.curr_img_in_list = 0
        self.currimg_name = ""
        self.currimg_width = 0
        self.currimg_height = 0
        self.currimg_pixbuf = None
        self.currimg_pixbuf_original = None
        self.currimg_zoomratio = 1
        self.currimg_is_animation = False
        # This is the actual pixbuf that is loaded in Mirage. This will
        # usually be the same as self.curr_img_in_list except for scenarios
        # like when the user presses 'next image' multiple times in a row.
        # In this case, self.curr_img_in_list will increment while
        # self.loaded_img_in_list will retain the current loaded image.
        self.loaded_img_in_list = 0
        # Next preloaded image:
        self.preloadimg_next_in_list = -1
        self.preloadimg_next_name = ""
        self.preloadimg_next_width = 0
        self.preloadimg_next_height = 0
        self.preloadimg_next_pixbuf = None
        self.preloadimg_next_pixbuf_original = None
        self.preloadimg_next_zoomratio = 1
        self.preloadimg_next_is_animation = False
        # Previous preloaded image:
        self.preloadimg_prev_in_list = -1
        self.preloadimg_prev_name = ""
        self.preloadimg_prev_width = 0
        self.preloadimg_prev_height = 0
        self.preloadimg_prev_pixbuf = None
        self.preloadimg_prev_pixbuf_original = None
        self.preloadimg_prev_zoomratio = 1
        self.preloadimg_prev_is_animation = False
        # Settings, misc:
        self.toolbar_show = True
        self.thumbpane_show = True
        self.statusbar_show = True
        self.fullscreen_mode = False
        self.opendialogpath = ""
        self.zoom_quality = GdkPixbuf.InterpType.BILINEAR
        self.recursive = False
        self.verbose = False
        self.image_loaded = False
        self.open_all_images = True  # open all images in the directory(ies)
        self.use_last_dir = True
        self.last_dir = os.path.expanduser("~")
        self.fixed_dir = os.path.expanduser("~")
        self.image_list = []
        self.open_mode = self.open_mode_smart
        self.last_mode = self.open_mode_smart
        self.listwrap_mode = 0  # 0=no, 1=yes, 2=ask
        self.user_prompt_visible = False  # the "wrap?" prompt
        self.slideshow_delay = 1  # seconds
        self.slideshow_mode = False
        self.slideshow_random = False
        self.slideshow_controls_visible = False  # fullscreen slideshow controls
        self.zoomvalue = 2
        self.quality_save = 90
        self.updating_adjustments = False
        self.disable_screensaver = False
        self.slideshow_in_fullscreen = False
        self.closing_app = False
        self.confirm_delete = True
        self.preloading_images = True
        self.action_names = [
            "Open in GIMP",
            "Create Thumbnail",
            "Create Thumbnails",
            "Move to Favorites",
        ]
        self.action_hashes = [
            "685216c7b02d1b9895c50514e505e25c73d3c128",
            "99a359757a6c96868215441ae2d3084c1682119a",
            "05a71d588cdb5d6dabf4f5a27df9b6cc38b9aa1f",
            "451c30956ab6ba54cea0d2bcb82491bd4e97910d"
        ]
        self.action_shortcuts = [
            "<Control>e",
            "<Alt>t",
            "<Control><Alt>t",
            "<Control><Alt>f",
        ]
        self.action_commands = [
            "gimp %F &",
            "convert %F -thumbnail 150x150 %Pt_%N.jpg",
            "convert %F -thumbnail 150x150 %Pt_%N.jpg",
            "mkdir -p ~/mirage-favs; mv %F ~/mirage-favs; [NEXT]",
        ]
        self.action_batch = [False, False, True, False]
        self.onload_cmd = None
        self.searching_for_images = False
        self.preserve_aspect = True
        self.ignore_preserve_aspect_callback = False
        self.savemode = 2
        self.image_modified = False
        self.image_zoomed = False
        self.start_in_fullscreen = False
        self.running_custom_actions = False
        self.merge_id_recent = None
        self.actionGroupRecent = None
        self.open_hidden_files = False
        self.thumbnail_sizes = ["128", "96", "72", "64", "48", "32"]
        self.thumbnail_size = 128  # Default to 128 x 128
        self.thumbnail_loaded = []
        self.thumbpane_stop_updating = {}
        self.thumbpane_stop_updating_lock = threading.Lock()
        self.thumbpane_update_lock = threading.Lock()
        self.recentfiles = ["", "", "", "", ""]
        self.screenshot_delay = 2
        self.thumbpane_bottom_coord_loaded = 0

        # Read any passed options/arguments:
        try:
            opts, args = getopt.getopt(
                sys.argv[1:],
                "hRvVsfo:",
                [
                    "help",
                    "version",
                    "recursive",
                    "verbose",
                    "slideshow",
                    "fullscreen",
                    "onload=",
                ],
            )
        except getopt.GetoptError:
            # print help information and exit:
            self.print_usage()
            sys.exit(2)
        # If options were passed, perform action on them.
        if opts != []:
            for o, a in opts:
                if o in ("-v", "--version"):
                    self.print_version()
                    sys.exit(2)
                elif o in ("-h", "--help"):
                    self.print_usage()
                    sys.exit(2)
                elif o in ("-R", "--recursive"):
                    self.recursive = True
                elif o in ("-V", "--verbose"):
                    self.verbose = True
                elif o in ("-s", "--slideshow", "-f", "--fullscreen"):
                    # This will be handled later
                    None
                elif o in ("-o", "--onload"):
                    self.onload_cmd = a
                else:
                    self.print_usage()
                    sys.exit(2)

        # Determine config dir, first try the environment variable XDG_CONFIG_HOME
        # according to XDG specification and as a fallback use ~/.config/mirage
        self.config_dir = (
            os.getenv("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
        ) + "/mirage"
        # Load config from disk:
        conf = configparser.ConfigParser(interpolation=None)
        if os.path.isfile(self.config_dir + "/miragerc"):
            conf.read(self.config_dir + "/miragerc")
        if conf.has_option("window", "w"):
            width = conf.getint("window", "w")
        if conf.has_option("window", "h"):
            height = conf.getint("window", "h")
        if conf.has_option("window", "toolbar"):
            self.toolbar_show = conf.getboolean("window", "toolbar")
        if conf.has_option("window", "statusbar"):
            self.statusbar_show = conf.getboolean("window", "statusbar")
        if conf.has_option("window", "thumbpane"):
            self.thumbpane_show = conf.getboolean("window", "thumbpane")
        if conf.has_option("prefs", "simple-bgcolor"):
            self.simple_bgcolor = conf.getboolean("prefs", "simple-bgcolor")
        if conf.has_option("prefs", "bgcolor-red"):
            bgr = conf.getint("prefs", "bgcolor-red")
            bgg = conf.getint("prefs", "bgcolor-green")
            bgb = conf.getint("prefs", "bgcolor-blue")
            bgcolor_found = True
            self.bgcolor = Gdk.Color(red=bgr, green=bgg, blue=bgb)
        if conf.has_option("prefs", "use_last_dir"):
            self.use_last_dir = conf.getboolean("prefs", "use_last_dir")
        if conf.has_option("prefs", "last_dir"):
            self.last_dir = conf.get("prefs", "last_dir")
        if conf.has_option("prefs", "fixed_dir"):
            self.fixed_dir = conf.get("prefs", "fixed_dir")
        if conf.has_option("prefs", "open_all"):
            self.open_all_images = conf.getboolean("prefs", "open_all")
        if conf.has_option("prefs", "hidden"):
            self.open_hidden_files = conf.getboolean("prefs", "hidden")
        if conf.has_option("prefs", "open_mode"):
            self.open_mode = conf.getint("prefs", "open_mode")
        if conf.has_option("prefs", "last_mode"):
            self.last_mode = conf.getint("prefs", "last_mode")
        if conf.has_option("prefs", "listwrap_mode"):
            self.listwrap_mode = conf.getint("prefs", "listwrap_mode")
        if conf.has_option("prefs", "slideshow_delay"):
            self.slideshow_delay = conf.getint("prefs", "slideshow_delay")
        if conf.has_option("prefs", "slideshow_random"):
            self.slideshow_random = conf.getboolean("prefs", "slideshow_random")
        if conf.has_option("prefs", "zoomquality"):
            self.zoomvalue = conf.getint("prefs", "zoomquality")
            if int(round(self.zoomvalue, 0)) == 0:
                self.zoom_quality = GdkPixbuf.InterpType.NEAREST
            elif int(round(self.zoomvalue, 0)) == 1:
                self.zoom_quality = GdkPixbuf.InterpType.TILES
            elif int(round(self.zoomvalue, 0)) == 2:
                self.zoom_quality = GdkPixbuf.InterpType.BILINEAR
            elif int(round(self.zoomvalue, 0)) == 3:
                self.zoom_quality = GdkPixbuf.InterpType.HYPER
        if conf.has_option("prefs", "quality_save"):
            self.quality_save = conf.getint("prefs", "quality_save")
        if conf.has_option("prefs", "disable_screensaver"):
            self.disable_screensaver = conf.getboolean("prefs", "disable_screensaver")
        if conf.has_option("prefs", "slideshow_in_fullscreen"):
            self.slideshow_in_fullscreen = conf.getboolean(
                "prefs", "slideshow_in_fullscreen"
            )
        if conf.has_option("prefs", "preloading_images"):
            self.preloading_images = conf.getboolean("prefs", "preloading_images")
        if conf.has_option("prefs", "thumbsize"):
            self.thumbnail_size = conf.getint("prefs", "thumbsize")
        if conf.has_option("prefs", "screenshot_delay"):
            self.screenshot_delay = conf.getint("prefs", "screenshot_delay")
        if conf.has_option("actions", "num_actions"):
            num_actions = conf.getint("actions", "num_actions")
            self.action_names = []
            self.action_commands = []
            self.action_shortcuts = []
            self.action_batch = []
            for i in range(num_actions):
                if (
                    conf.has_option("actions", "names[" + str(i) + "]")
                    and conf.has_option("actions", "commands[" + str(i) + "]")
                    and conf.has_option("actions", "shortcuts[" + str(i) + "]")
                    and conf.has_option("actions", "batch[" + str(i) + "]")
                ):
                    name = conf.get("actions", "names[" + str(i) + "]")
                    command = conf.get("actions", "commands[" + str(i) + "]")
                    shortcut = conf.get("actions", "shortcuts[" + str(i) + "]")
                    batch = conf.getboolean("actions", "batch[" + str(i) + "]")
                    self.action_names.append(name)
                    self.action_hashes.append(
                        self.hash_for_custom_action(name, command, shortcut, batch)
                    )
                    self.action_commands.append(command)
                    self.action_shortcuts.append(shortcut)
                    self.action_batch.append(batch)
        if conf.has_option("prefs", "savemode"):
            self.savemode = conf.getint("prefs", "savemode")
        if conf.has_option("prefs", "start_in_fullscreen"):
            self.start_in_fullscreen = conf.getboolean("prefs", "start_in_fullscreen")
        if conf.has_option("prefs", "confirm_delete"):
            self.confirm_delete = conf.getboolean("prefs", "confirm_delete")
        self.recentfiles = []
        if conf.has_option("recent", "num_recent"):
            num_recent = conf.getint("recent", "num_recent")
            for i in range(num_recent):
                self.recentfiles.append("")
                if conf.has_option("recent", "urls[" + str(i) + ",0]"):
                    self.recentfiles[i] = conf.get("recent", "urls[" + str(i) + ",0]")
        # slideshow_delay is the user's preference, whereas curr_slideshow_delay is
        # the current delay (which can be changed without affecting the 'default')
        self.curr_slideshow_delay = self.slideshow_delay
        # Same for randomization:
        self.curr_slideshow_random = self.slideshow_random

        # Read accel_map file, if it exists
        if os.path.isfile(self.config_dir + "/accel_map"):
            Gtk.AccelMap.load(self.config_dir + "/accel_map")

        # Directory/ies in which to find application images/pixmaps
        self.resource_path_list = False

        self.blank_image = GdkPixbuf.Pixbuf.new_from_file(
            self.find_path("mirage_blank.png")
        )

        # Define the actions
        self.action_group = Gio.SimpleActionGroup()

        actions = [
            ## Main Menubar
            # File
            ("open-file", self.open_file),
            ("open-folder", self.open_folder),
            ("open-file-remote", self.open_file_remote),
            ("save-image", self.save_image),
            ("save-image-as", self.save_image_as),
            ("screenshot", self.screenshot),
            ("show-properties", self.show_properties),
            ("exit-app", self.exit_app),
            # Edit
            ("rotate-left", self.rotate_left),
            ("rotate-right", self.rotate_right),
            ("flip-image-vert", self.flip_image_vert),
            ("flip-image-horiz", self.flip_image_horiz),
            ("crop-image", self.crop_image),
            ("resize-image", self.resize_image),
            ("saturation", self.saturation),
            ("rename-image", self.rename_image),
            ("delete-image", self.delete_image),
            ("show-custom-actions", self.show_custom_actions),
            ("show-prefs", self.show_prefs),
            # View
            ("zoom-out", self.zoom_out),
            ("zoom-in", self.zoom_in),
            ("zoom-1-to-1", self.zoom_1_to_1_action),
            ("zoom-to-fit-window", self.zoom_to_fit_window_action),
            ("toggle-toolbar", None, None, "true", self.toggle_toolbar),
            ("toggle-thumbpane", None, None, "true", self.toggle_thumbpane),
            ("toggle-status-bar", None, None, "true", self.toggle_status_bar),
            ("enter-fullscreen", self.enter_fullscreen),
            # Go
            ("goto-next-image", self.goto_next_image),
            ("goto-prev-image", self.goto_prev_image),
            ("goto-random-image", self.goto_random_image),
            ("goto-first-image", self.goto_first_image),
            ("goto-last-image", self.goto_last_image),
            ("start-slideshow", self.toggle_slideshow),
            ("stop-slideshow", self.toggle_slideshow),
            # Help
            ("show-about", self.show_about),
            # Other
            (
                "toggle-slideshow-shuffle",
                None,
                None,
                "false",
                self.toggle_slideshow_shuffle,
            ),
        ]
        self.action_group.add_action_entries(actions)

        # Populate keys[]:
        self.keys = []
        for i in range(len(actions)):
            if len(actions[i]) > 3:
                if actions[i][3] != None:
                    self.keys.append([actions[i][4], actions[i][3]])

        # Create interface
        self.window = Gtk.ApplicationWindow.new(self)
        self.window.insert_action_group("app", self.action_group)
        self.popup_menu.insert_action_group("app", self.action_group)
        self.fullscreen_controls.insert_action_group("app", self.action_group)
        self.update_title()
        icon_path = self.find_path("mirage.png")
        try:
            Gtk.Window.set_default_icon_from_file(icon_path)
        except:
            print(traceback.format_exc(), file=sys.stderr)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)

        # Hidden hotkeys
        hotkeys = (
            ("app.zoom-out", "<Ctrl>KP_Subtract"),
            ("app.zoom-out", "minus"),
            ("app.zoom-out", "KP_Subtract"),
            ("app.zoom-in", "plus"),
            ("app.zoom-in", "equal"),
            ("app.zoom-in", "KP_Add"),
            ("app.zoom-in", "<Ctrl>KP_Add"),
            ("app.zoom-1-to-1", "1"),
            ("app.zoom-1-to-1", "<Ctrl>KP_End"),
            ("app.zoom-1-to-1", "<Ctrl>KP_1"),
            ("app.zoom-to-fit-window", "<Ctrl>KP_Insert"),
            ("app.zoom-to-fit-window", "<Ctrl>KP_0"),
            ("app.enter-fullscreen", "<Shift>Return"),
            ("app.leave-fullscreen", "Escape"),
            ("app.goto-next-image", "space"),
            ("app.goto-next-image", "Down"),
            ("app.goto-next-image", "Page_Down"),
            ("app.goto-prev-image", "BackSpace"),
            ("app.goto-prev-image", "Up"),
            ("app.goto-prev-image", "Page_Up"),
        )
        for action_name, hotkey in hotkeys:
            self.set_accels_for_action(
                action_name, self.get_accels_for_action(action_name) + [hotkey]
            )

        self.refresh_custom_actions_menu()
        self.refresh_recent_files_menu()

        vbox.pack_start(self.toolbar, False, False, 0)

        self.layout = Gtk.Layout.new(None, None)
        self.vscroll = Gtk.Scrollbar.new(
            Gtk.Orientation.VERTICAL, self.layout.get_vadjustment()
        )
        self.hscroll = Gtk.Scrollbar.new(
            Gtk.Orientation.HORIZONTAL, self.layout.get_hadjustment()
        )
        self.table = Gtk.Table.new(3, 2, False)

        self.thumblist = Gtk.ListStore.new([GdkPixbuf.Pixbuf])
        self.thumbpane = Gtk.TreeView.new_with_model(self.thumblist)
        self.thumbcolumn = Gtk.TreeViewColumn.new()
        self.thumbcell = Gtk.CellRendererPixbuf.new()
        self.thumbcolumn.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
        self.thumbpane_set_size()
        self.thumbpane.append_column(self.thumbcolumn)
        self.thumbcolumn.pack_start(self.thumbcell, True)
        self.thumbcolumn.set_attributes(self.thumbcell, pixbuf=0)
        self.thumbpane.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
        self.thumbpane.set_headers_visible(False)
        self.thumbpane.set_property("can-focus", False)
        self.thumbscroll = Gtk.ScrolledWindow.new(None, None)
        self.thumbscroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS)
        self.thumbscroll.add(self.thumbpane)

        self.table.attach(
            self.thumbscroll,
            0,
            1,
            0,
            1,
            0,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        self.table.attach(
            self.layout,
            1,
            2,
            0,
            1,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        self.table.attach(
            self.hscroll,
            1,
            2,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK,
            0,
            0,
        )
        self.table.attach(
            self.vscroll,
            2,
            3,
            0,
            1,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK,
            0,
            0,
        )
        vbox.pack_start(self.table, True, True, 0)
        if not bgcolor_found:
            self.bgcolor = Gdk.Color(0, 0, 0)  # Default to black
        self.imageview = Gtk.Image.new()
        self.layout.add(self.imageview)

        self.statusbar = Gtk.Statusbar.new()
        self.statusbar2 = Gtk.Statusbar.new()
        self.statusbar2.set_size_request(200, -1)
        hbox_statusbar = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_statusbar.pack_start(self.statusbar, True, True, 0)
        hbox_statusbar.pack_start(self.statusbar2, False, True, 0)
        vbox.pack_start(hbox_statusbar, False, False, 0)
        self.window.add(vbox)
        self.window.set_default_size(width, height)

        if self.simple_bgcolor:
            self.fullscreen_controls.modify_bg(Gtk.StateType.NORMAL, None)
        else:
            self.fullscreen_controls.modify_bg(Gtk.StateType.NORMAL, self.bgcolor)

        # Connect signals
        self.window.connect("delete_event", self.delete_event)
        self.window.connect("destroy", self.destroy)
        self.window.connect("size-allocate", self.window_resized)
        self.window.connect("key-press-event", self.topwindow_keypress)
        self.toolbar.connect("focus", self.toolbar_focused)
        self.layout.drag_dest_set(
            Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
            [Gtk.TargetEntry.new("text/uri-list", 0, 80)],
            Gdk.DragAction.DEFAULT,
        )
        self.layout.connect("drag_motion", self.motion_cb)
        self.layout.connect("drag_data_received", self.drop_cb)
        self.layout.add_events(
            Gdk.EventMask.KEY_PRESS_MASK
            | Gdk.EventMask.POINTER_MOTION_MASK
            | Gdk.EventMask.BUTTON_PRESS_MASK
            | Gdk.EventMask.BUTTON_MOTION_MASK
            | Gdk.EventMask.SCROLL_MASK
        )
        self.layout.connect("scroll-event", self.mousewheel_scrolled)
        self.layout.add_events(
            Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.KEY_PRESS_MASK
        )
        self.layout.connect("button_press_event", self.button_pressed)
        self.layout.add_events(
            Gdk.EventMask.POINTER_MOTION_MASK
            | Gdk.EventMask.POINTER_MOTION_HINT_MASK
            | Gdk.EventMask.BUTTON_RELEASE_MASK
        )
        self.layout.connect("motion-notify-event", self.mouse_moved)
        self.layout.connect("button-release-event", self.button_released)
        self.layout.connect("draw", self.draw_background)
        self.imageview.connect("draw", self.draw_image)
        self.thumb_sel_handler = self.thumbpane.get_selection().connect(
            "changed", self.thumbpane_selection_changed
        )
        self.thumb_scroll_handler = self.thumbscroll.get_vscrollbar().connect(
            "value-changed", self.thumbpane_scrolled
        )
        self.fullscreen_controls.slideshow_delay_adjustment.connect(
            "value-changed", self.slideshow_delay_changed
        )

        # Show GUI:
        if not self.toolbar_show:
            self.toolbar.set_property("visible", False)
            self.toolbar.set_no_show_all(True)
        if not self.statusbar_show:
            self.statusbar.set_property("visible", False)
            self.statusbar.set_no_show_all(True)
            self.statusbar2.set_property("visible", False)
            self.statusbar2.set_no_show_all(True)
        if not self.thumbpane_show:
            self.thumbscroll.set_property("visible", False)
            self.thumbscroll.set_no_show_all(True)
        self.hscroll.set_no_show_all(True)
        self.vscroll.set_no_show_all(True)
        go_into_fullscreen = False
        if opts != []:
            for o, a in opts:
                if (o in ("-f", "--fullscreen")) or (
                    (o in ("-s", "--slideshow")) and self.slideshow_in_fullscreen
                ):
                    go_into_fullscreen = True
        if go_into_fullscreen or self.start_in_fullscreen:
            self.enter_fullscreen(None, None, None)
            self.statusbar.set_no_show_all(True)
            self.statusbar2.set_no_show_all(True)
            self.toolbar.set_no_show_all(True)
            self.thumbscroll.set_no_show_all(True)
        self.window.show_all()
        self.set_leave_fullscreen_visible(False)
        self.layout.set_can_focus(True)
        self.window.set_focus(self.layout)

        self.set_slideshow_sensitivities()

        # If arguments (filenames) were passed, try to open them:
        self.image_list = []
        if args != []:
            for i in range(len(args)):
                args[i] = urllib.request.url2pathname(args[i])
            self.expand_filelist_and_load_image(args)
        else:
            self.set_go_sensitivities(False)
            self.set_image_sensitivities(False)

        if opts != []:
            for o, a in opts:
                if o in ("-s", "--slideshow"):
                    self.toggle_slideshow(None, None, None)

    def do_startup(self):
        Gtk.Application.do_startup(self)

        toolbar_builder = Gtk.Builder.new_from_resource(
            "/io/thomasross/mirage/toolbar.ui"
        )
        self.toolbar = toolbar_builder.get_object("toolbar")
        self.fullscreen_controls = FullscreenControls()
        self.popup_menu = Gtk.Menu.new_from_model(self.get_menu_by_id("popup-menu"))

    def add_remove_action(self, action_name, activate, visible):
        if visible and not self.action_group.lookup_action(action_name):
            action = Gio.SimpleAction.new(action_name, None)
            action.connect("activate", activate, None)
            self.action_group.add_action(action)
        elif not visible:
            self.action_group.remove_action(action_name)

    def get_pointer(self, window):
        display = window.get_display()
        seat = display.get_default_seat()
        device = seat.get_pointer()

        return window.get_device_position(device)[1:]

    def refresh_recent_files_menu(self):
        recent_files_section = self.get_menu_by_id("recent-files-section")

        # Clear the old recent files
        recent_files_section.remove_all()
        for action in self.action_group.list_actions():
            if action.startswith("recent-file-{}"):
                self.action_group.remove_action(action)

        for i, recent_file in enumerate(self.recentfiles):
            filename = os.path.basename(recent_file)
            if not filename:
                continue

            if len(filename) > 27:
                filename = "{}..{}".format(filename[:25], os.path.splitext(filename)[1])

            action_name = "recent-file-{}".format(
                hashlib.sha1(recent_file.encode("utf8")).hexdigest()
            )
            action = Gio.SimpleAction.new(action_name, None)
            action.connect("activate", self.recent_action_click, recent_file)
            self.action_group.add_action(action)
            self.set_accels_for_action("app." + action_name, ["<Alt>{}".format(i + 1)])

            menu_item = Gio.MenuItem.new(filename, "app." + action_name)
            recent_files_section.insert_item(i, menu_item)

    def refresh_custom_actions_menu(self):
        custom_actions_section = self.get_menu_by_id("custom-actions-section")

        # Clear the old actions
        custom_actions_section.remove_all()
        for action in self.action_group.list_actions():
            if action.startswith("custom-"):
                self.action_group.remove_action(action)

        for i, name in enumerate(self.action_names):
            action_name = "custom-{}".format(self.action_hashes[i])

            action = Gio.SimpleAction.new(action_name, None)
            action.connect("activate", self.custom_action_click, self.action_hashes[i])
            self.action_group.add_action(action)
            self.set_accels_for_action("app." + action_name, [self.action_shortcuts[i]])

            menu_item = Gio.MenuItem.new(name, "app." + action_name)
            custom_actions_section.insert_item(i, menu_item)

    def thumbpane_do_update(self, force_upto_imgnum):
        # Load all images up to the bottom of the visible thumbpane rect:
        visible_range = self.thumbpane.get_visible_range()
        if not visible_range:
            return

        start_path, end_path = visible_range
        start_image = max(0, start_path.get_indices()[0] - 1)
        end_image = min(len(self.image_list), end_path.get_indices()[0])

        if force_upto_imgnum > end_image:
            end_image = force_upto_imgnum

        # update images:
        with self.thumbpane_stop_updating_lock:
            for thread_id in self.thumbpane_stop_updating:
                self.thumbpane_stop_updating[thread_id] = True

        thread = threading.Thread(
            target=self.thumbpane_update_pending_images,
            args=(start_image, end_image, force_upto_imgnum),
        )
        thread.setDaemon(True)
        thread.start()

    def thumbpane_update_images(self, clear_first=False, force_upto_imgnum=-1):
        self.stop_now = False
        # When first populating the thumbpane, make sure we go up to at least
        # force_upto_imgnum so that we can show this image selected:
        if clear_first:
            self.thumbpane_clear_list()

        GLib.idle_add(self.thumbpane_do_update, force_upto_imgnum)

    def thumbpane_create_dir(self):
        if not os.path.exists(os.path.expanduser("~/.thumbnails/")):
            os.mkdir(os.path.expanduser("~/.thumbnails/"))
        if not os.path.exists(os.path.expanduser("~/.thumbnails/normal/")):
            os.mkdir(os.path.expanduser("~/.thumbnails/normal/"))

    def thumbpane_update_pending_images(self, start, end, select_image):
        thread_id = threading.get_ident()
        with self.thumbpane_stop_updating_lock:
            self.thumbpane_stop_updating[thread_id] = False

        with self.thumbpane_update_lock:
            self.thumbpane_create_dir()
            # Check to see if any images need their thumbnails generated.
            curr_coord = 0
            imgnum = 0

            for imgnum in range(start, end + 1):
                if (
                    self.thumbpane_stop_updating.get(thread_id)
                    or self.closing_app
                    or self.stop_now
                    or not self.thumbpane_show
                ):
                    break

                if imgnum >= len(self.image_list):
                    break

                self.thumbpane_set_image(self.image_list[imgnum], imgnum)

                if select_image == imgnum and select_image == self.curr_img_in_list:
                    GLib.idle_add(self.thumbpane_select, select_image)

            with self.thumbpane_stop_updating_lock:
                del self.thumbpane_stop_updating[thread_id]

    def thumbpane_clear_list(self):
        self.thumbpane_bottom_coord_loaded = 0
        self.thumbscroll.get_vscrollbar().handler_block(self.thumb_scroll_handler)
        self.thumblist.clear()
        self.thumbscroll.get_vscrollbar().handler_unblock(self.thumb_scroll_handler)
        for image in self.image_list:
            blank_pix = self.get_blank_pix_for_image(image)
            self.thumblist.append([blank_pix])
        self.thumbnail_loaded = [False] * len(self.image_list)

    def thumbpane_set_image(self, image_name, imgnum, force_update=False):
        if self.thumbpane_show:
            if not self.thumbnail_loaded[imgnum] or force_update:
                filename, thumbfile = self.thumbnail_get_name(image_name)
                pix = self.thumbpane_get_pixbuf(thumbfile, filename, force_update)
                if pix:
                    if self.thumbnail_size != 128:
                        # 128 is the size of the saved thumbnail, so convert if different:
                        pix, image_width, image_height = self.get_pixbuf_of_size(
                            pix, self.thumbnail_size, GdkPixbuf.InterpType.TILES
                        )
                    self.thumbnail_loaded[imgnum] = True
                    self.thumbscroll.get_vscrollbar().handler_block(
                        self.thumb_scroll_handler
                    )
                    pix = self.pixbuf_add_border(pix)
                    try:
                        self.thumblist[imgnum] = [pix]
                    except:
                        print(traceback.format_exc(), file=sys.stderr)
                    self.thumbscroll.get_vscrollbar().handler_unblock(
                        self.thumb_scroll_handler
                    )

    def thumbnail_get_name(self, image_name):
        filename = os.path.expanduser("file://" + image_name)
        uriname = os.path.expanduser(
            "file://" + urllib.request.pathname2url(image_name)
        )
        if HAS_HASHLIB:
            m = hashlib.md5()
        else:
            m = md5.new()
        m.update(uriname.encode("utf8"))
        mhex = m.hexdigest()
        mhex_filename = os.path.expanduser("~/.thumbnails/normal/" + mhex + ".png")
        return filename, mhex_filename

    def thumbpane_get_pixbuf(self, thumb_url, image_url, force_generation):
        # Returns a valid pixbuf or None if a pixbuf cannot be generated. Tries to re-use
        # a thumbnail from ~/.thumbails/normal/, otherwise generates one with the
        # XDG filename: md5(file:///full/path/to/image).png
        imgfile = image_url
        if imgfile[:7] == "file://":
            imgfile = imgfile[7:]
        try:
            if os.path.exists(thumb_url) and not force_generation:
                pix = GdkPixbuf.Pixbuf.new_from_file(thumb_url)
                pix_mtime = pix.get_option("tEXt::Thumb::MTime")
                if pix_mtime:
                    st = os.stat(imgfile)
                    file_mtime = str(st[stat.ST_MTIME])
                    # If the mtimes match, we're good. if not, regenerate the thumbnail..
                    if pix_mtime == file_mtime:
                        return pix
            # Create the 128x128 thumbnail:
            uri = "file://" + urllib.request.pathname2url(imgfile)
            pix = GdkPixbuf.Pixbuf.new_from_file(imgfile)
            pix, image_width, image_height = self.get_pixbuf_of_size(
                pix, 128, GdkPixbuf.InterpType.TILES
            )
            st = os.stat(imgfile)
            file_mtime = str(st[stat.ST_MTIME])
            # Save image to .thumbnails:
            pix.savev(
                thumb_url,
                "png",
                ["tEXt::Thumb::URI", "tEXt::Thumb::MTime", "tEXt::Software"],
                [uri, file_mtime, "Mirage" + __version__],
            )
            return pix
        except:
            print(traceback.format_exc(), file=sys.stderr)
            return None

    def thumbpane_load_image(self, treeview, imgnum):
        if imgnum != self.curr_img_in_list:
            GLib.idle_add(self.goto_image, str(imgnum), None)

    def thumbpane_selection_changed(self, treeview):
        cancel = self.autosave_image()
        if cancel:
            # Revert selection...
            GLib.idle_add(self.thumbpane_select, self.curr_img_in_list)
            return True
        try:
            model, paths = self.thumbpane.get_selection().get_selected_rows()
            imgnum = paths[0][0]
            if not self.thumbnail_loaded[imgnum]:
                self.thumbpane_set_image(self.image_list[imgnum], imgnum)
            GLib.idle_add(self.thumbpane_load_image, treeview, imgnum)
        except:
            print(traceback.format_exc(), file=sys.stderr)

    def thumbpane_select(self, imgnum):
        if self.thumbpane_show:
            self.thumbpane.get_selection().handler_block(self.thumb_sel_handler)
            try:
                path = Gtk.TreePath(imgnum)
                self.thumbpane.get_selection().select_path(path)
                self.thumbpane.scroll_to_cell(path)
            except:
                print(traceback.format_exc(), file=sys.stderr)
            self.thumbpane.get_selection().handler_unblock(self.thumb_sel_handler)

    def thumbpane_set_size(self):
        self.thumbcolumn.set_fixed_width(self.thumbpane_get_size())
        self.window_resized(None, self.window.get_allocation(), True)

    def thumbpane_get_size(self):
        return int(self.thumbnail_size * 1.3)

    def thumbpane_scrolled(self, range):
        self.thumbpane_update_images()

    def get_blank_pix_for_image(self, image):
        # Sizes the "blank image" icon for the thumbpane. This will ensure that we don't
        # load a humongous icon for a small pix, for example, and will keep the thumbnails
        # from shifting around when they are actually loaded.
        try:
            info = GdkPixbuf.Pixbuf.get_file_info(image)
            imgwidth = float(info[1])
            imgheight = float(info[2])
            if imgheight > self.thumbnail_size:
                if imgheight > imgwidth:
                    imgheight = self.thumbnail_size
                else:
                    imgheight = (imgheight // imgwidth) * self.thumbnail_size
            imgheight = 2 + int(
                imgheight
            )  # Account for border that will be added to thumbnails..
            imgwidth = self.thumbnail_size
        except:
            print(traceback.format_exc(), file=sys.stderr)
            imgheight = 2 + self.thumbnail_size
            imgwidth = self.thumbnail_size
        blank_pix = GdkPixbuf.Pixbuf.new(
            GdkPixbuf.Colorspace.RGB, True, 8, imgwidth, imgheight
        )
        blank_pix.fill(0x00000000)
        imgwidth2 = int(imgheight * 0.8)
        imgheight2 = int(imgheight * 0.8)
        composite_pix = self.blank_image.scale_simple(
            imgwidth2, imgheight2, GdkPixbuf.InterpType.BILINEAR
        )
        leftcoord = int((imgwidth - imgwidth2) // 2)
        topcoord = int((imgheight - imgheight2) // 2)
        composite_pix.copy_area(
            0, 0, imgwidth2, imgheight2, blank_pix, leftcoord, topcoord
        )
        return blank_pix

    def find_path(self, filename, exit_on_fail=True):
        """ Find a pixmap or icon by looking through standard dirs.
            If the image isn't found exit with error status 1 unless
            exit_on_fail is set to False, then return None """
        if not self.resource_path_list:
            # If executed from mirage in bin this points to the basedir
            basedir_mirage = os.path.split(sys.path[0])[0]
            # If executed from mirage.py module in python lib this points to the basedir
            f0 = os.path.split(__file__)[0].split("/lib")[0]
            self.resource_path_list = list(
                set(
                    filter(
                        os.path.isdir,
                        [
                            os.path.join(basedir_mirage, "share", "mirage"),
                            os.path.join(basedir_mirage, "share", "pixmaps"),
                            os.path.join(sys.prefix, "share", "mirage"),
                            os.path.join(sys.prefix, "share", "pixmaps"),
                            os.path.join(sys.prefix, "local", "share", "mirage"),
                            os.path.join(sys.prefix, "local", "share", "pixmaps"),
                            sys.path[0],  # If it's run non-installed
                            os.path.join(f0, "share", "mirage"),
                            os.path.join(f0, "share", "pixmaps"),
                        ],
                    )
                )
            )
        for path in self.resource_path_list:
            pix = os.path.join(path, filename)
            if os.path.exists(pix):
                return pix
        # If we reached here, we didn't find the pixmap
        if exit_on_fail:
            print(
                _("Couldn't find the image %s. Please check your installation.")
                % filename
            )
            sys.exit(1)
        else:
            return None

    def toolbar_focused(self, widget, direction):
        self.layout.grab_focus()
        return True

    def topwindow_keypress(self, widget, event):
        shortcut = Gtk.accelerator_name(event.keyval, event.get_state())
        if "Escape" in shortcut:
            self.stop_now = True
            self.searching_for_images = False
            while Gtk.events_pending():
                Gtk.main_iteration()
            self.update_title()
            return

    def parse_action_command(self, command, batchmode):
        self.running_custom_actions = True
        self.change_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
        while Gtk.events_pending():
            Gtk.main_iteration()
        self.curr_custom_action = 0
        if batchmode:
            self.num_custom_actions = len(self.image_list)
            for i in range(self.num_custom_actions):
                self.curr_custom_action += 1
                self.update_statusbar()
                while Gtk.events_pending():
                    Gtk.main_iteration()
                imagename = self.image_list[i]
                self.parse_action_command2(command, imagename)
        else:
            self.num_custom_actions = 1
            self.curr_custom_action = 1
            self.update_statusbar()
            while Gtk.events_pending():
                Gtk.main_iteration()
            self.parse_action_command2(command, self.currimg_name)
        gc.collect()
        self.change_cursor(None)
        # Refresh the current image or any preloaded needed if they have changed:
        if not os.path.exists(self.currimg_name):
            self.currimg_pixbuf_original = None
            self.image_load_failed(False)
        else:
            animtest = GdkPixbuf.PixbufAnimation.new_from_file(self.currimg_name)
            if animtest.is_static_image():
                if self.images_are_different(
                    animtest.get_static_image(), self.currimg_pixbuf_original
                ):
                    self.load_new_image2(False, False, True, False)
            else:
                if self.images_are_different(animtest, self.currimg_pixbuf_original):
                    self.load_new_image2(False, False, True, False)
        self.running_custom_actions = False
        self.update_statusbar()
        while Gtk.events_pending():
            Gtk.main_iteration()
        if not os.path.exists(self.preloadimg_prev_name):
            self.preloadimg_prev_in_list = -1
        else:
            animtest = GdkPixbuf.PixbufAnimation.new_from_file(
                self.preloadimg_prev_name
            )
            if animtest.is_static_image():
                if self.images_are_different(
                    animtest.get_static_image(), self.preloadimg_prev_pixbuf_original
                ):
                    self.preloadimg_prev_in_list = -1
                    self.preload_when_idle = GLib.idle_add(
                        self.preload_prev_image, False
                    )
            else:
                if self.images_are_different(
                    animtest, self.preloadimg_prev_pixbuf_original
                ):
                    self.preloadimg_prev_in_list = -1
                    self.preload_when_idle = GLib.idle_add(
                        self.preload_prev_image, False
                    )
        if not os.path.exists(self.preloadimg_next_name):
            self.preloadimg_next_in_list = -1
        else:
            animtest = GdkPixbuf.PixbufAnimation.new_from_file(
                self.preloadimg_next_name
            )
            if animtest.is_static_image():
                if self.images_are_different(
                    animtest.get_static_image(), self.preloadimg_next_pixbuf_original
                ):
                    self.preloadimg_next_in_list = -1
                    self.preload_when_idle = GLib.idle_add(
                        self.preload_next_image, False
                    )
            else:
                if self.images_are_different(
                    animtest, self.preloadimg_next_pixbuf_original
                ):
                    self.preloadimg_next_in_list = -1
                    self.preload_when_idle = GLib.idle_add(
                        self.preload_next_image, False
                    )
        self.stop_now = False
        if batchmode:
            # Update all thumbnails:
            GLib.idle_add(self.thumbpane_update_images, True, self.curr_img_in_list)
        else:
            # Update only the current thumbnail:
            GLib.idle_add(
                self.thumbpane_set_image,
                self.image_list[self.curr_img_in_list],
                self.curr_img_in_list,
                True,
            )

    def images_are_different(self, pixbuf1, pixbuf2):
        if pixbuf1.get_pixels() == pixbuf2.get_pixels():
            return False
        else:
            return True

    def recent_action_click(self, action, parameter, filename):
        self.stop_now = True
        while Gtk.events_pending():
            Gtk.main_iteration()
        cancel = self.autosave_image()
        if cancel:
            return
        if (
            os.path.isfile(filename)
            or os.path.exists(filename)
            or filename.startswith("http://")
            or filename.startswith("ftp://")
        ):
            self.expand_filelist_and_load_image([filename])
        else:
            self.image_list = []
            self.curr_img_in_list = 0
            self.image_list.append(filename)
            self.image_load_failed(False)
            self.recent_file_remove_and_refresh(filename)

    def recent_file_remove_and_refresh(self, rmfile):
        try:
            self.recentfiles.remove(rmfile)
        except ValueError:
            pass

        self.refresh_recent_files_menu()

    def recent_file_add_and_refresh(self, addfile):
        try:
            self.recentfiles.remove(addfile)
        except ValueError:
            pass

        self.recentfiles.insert(0, addfile)
        self.recentfiles = self.recentfiles[:9]
        self.refresh_recent_files_menu()

    def custom_action_click(self, action, parameter, action_hash):
        for i, hash in enumerate(self.action_hashes):
            if hash == action_hash:
                try:
                    self.parse_action_command(
                        self.action_commands[i], self.action_batch[i]
                    )
                except:
                    print(traceback.format_exc(), file=sys.stderr)

                break

    def parse_action_command2(self, cmd, imagename):
        # Executes the given command using ``os.system``, substituting "%"-macros approprately.
        def sh_esc(s):
            import re

            return re.sub(r"[^/._a-zA-Z0-9-]", lambda c: "\\" + c.group(), s)

        cmd = cmd.strip()
        # [NEXT] and [PREV] are only valid alone or at the end of the command
        if cmd == "[NEXT]":
            self.goto_next_image(None, None, None)
            return
        elif cmd == "[PREV]":
            self.goto_prev_image(None, None, None)
            return
        # -1=go to previous, 1=go to next, 0=don't change
        prev_or_next = 0
        if cmd[-6:] == "[NEXT]":
            prev_or_next = 1
            cmd = cmd[:-6]
        elif cmd[-6:] == "[PREV]":
            prev_or_next = -1
            cmd = cmd[:-6]
        if "%F" in cmd:
            cmd = cmd.replace("%F", sh_esc(imagename))
        if "%N" in cmd:
            cmd = cmd.replace(
                "%N", sh_esc(os.path.splitext(os.path.basename(imagename))[0])
            )
        if "%P" in cmd:
            cmd = cmd.replace("%P", sh_esc(os.path.dirname(imagename) + "/"))
        if "%E" in cmd:
            cmd = cmd.replace(
                "%E", sh_esc(os.path.splitext(os.path.basename(imagename))[1])
            )
        if "%L" in cmd:
            cmd = cmd.replace("%L", " ".join([sh_esc(s) for s in self.image_list]))
        if self.verbose:
            print(_("Action: %s") % cmd)
        shell_rc = os.system(cmd) >> 8
        if self.verbose:
            print(_("Action return code: %s") % shell_rc)
        if shell_rc != 0:
            msg = (
                _(
                    'Unable to launch "%s". Please specify a valid command from Edit > Custom Actions.'
                )
                % cmd
            )
            error_dialog = Gtk.MessageDialog(
                self.window,
                Gtk.DialogFlags.MODAL,
                Gtk.MessageType.WARNING,
                Gtk.ButtonsType.CLOSE,
                msg,
            )
            error_dialog.set_title(_("Invalid Custom Action"))
            error_dialog.run()
            error_dialog.destroy()
        elif prev_or_next == 1:
            self.goto_next_image(None, None, None)
        elif prev_or_next == -1:
            self.goto_prev_image(None, None, None)
        self.running_custom_actions = False

    def set_go_sensitivities(self, enable):
        self.action_group.lookup_action("goto-next-image").set_enabled(enable)
        self.action_group.lookup_action("goto-prev-image").set_enabled(enable)
        self.action_group.lookup_action("goto-random-image").set_enabled(enable)
        self.action_group.lookup_action("goto-first-image").set_enabled(enable)
        self.action_group.lookup_action("goto-last-image").set_enabled(enable)

    def set_image_sensitivities(self, enable):
        self.set_zoom_in_sensitivities(enable)
        self.set_zoom_out_sensitivities(enable)
        self.action_group.lookup_action("save-image").set_enabled(enable)
        self.action_group.lookup_action("save-image-as").set_enabled(enable)
        self.action_group.lookup_action("show-properties").set_enabled(enable)

        self.action_group.lookup_action("crop-image").set_enabled(enable)
        self.action_group.lookup_action("resize-image").set_enabled(enable)
        self.action_group.lookup_action("saturation").set_enabled(enable)
        self.action_group.lookup_action("rename-image").set_enabled(enable)
        self.action_group.lookup_action("delete-image").set_enabled(enable)

        self.action_group.lookup_action("zoom-1-to-1").set_enabled(enable)
        self.action_group.lookup_action("zoom-to-fit-window").set_enabled(enable)

        # Only jpeg, png, and bmp images are currently supported for saving
        if len(self.image_list) > 0:
            self.action_group.lookup_action("save-image").set_enabled(False)
            try:
                filetype = GdkPixbuf.Pixbuf.get_file_info(self.currimg_name)[
                    0
                ].get_name()
                if self.filetype_is_writable(filetype):
                    self.action_group.lookup_action("save-image").set_enabled(enable)
            except:
                print(traceback.format_exc(), file=sys.stderr)

        for action in self.action_group.list_actions():
            if action.startswith("custom-"):
                self.action_group.lookup_action(action).set_enabled(enable)

        if not HAS_IMGFUNCS:
            enable = False

        self.action_group.lookup_action("rotate-left").set_enabled(enable)
        self.action_group.lookup_action("rotate-right").set_enabled(enable)
        self.action_group.lookup_action("flip-image-vert").set_enabled(enable)
        self.action_group.lookup_action("flip-image-horiz").set_enabled(enable)

    def set_zoom_in_sensitivities(self, enable):
        self.action_group.lookup_action("zoom-in").set_enabled(enable)

    def set_zoom_out_sensitivities(self, enable):
        self.action_group.lookup_action("zoom-out").set_enabled(enable)

    def set_next_image_sensitivities(self, enable):
        self.action_group.lookup_action("goto-next-image").set_enabled(enable)

    def set_previous_image_sensitivities(self, enable):
        self.action_group.lookup_action("goto-prev-image").set_enabled(enable)

    def set_first_image_sensitivities(self, enable):
        self.action_group.lookup_action("goto-first-image").set_enabled(enable)

    def set_last_image_sensitivities(self, enable):
        self.action_group.lookup_action("goto-last-image").set_enabled(enable)

    def set_random_image_sensitivities(self, enable):
        self.action_group.lookup_action("goto-random-image").set_enabled(enable)

    def set_start_slideshow_visible(self, visible):
        self.add_remove_action("start-slideshow", self.toggle_slideshow, visible)

    def set_stop_slideshow_visible(self, visible):
        self.add_remove_action("stop-slideshow", self.toggle_slideshow, visible)

    def set_slideshow_sensitivities(self):
        if len(self.image_list) <= 1:
            self.set_start_slideshow_visible(True)
            self.action_group.lookup_action("start-slideshow").set_enabled(False)

            self.set_stop_slideshow_visible(False)
        elif self.slideshow_mode:
            self.set_start_slideshow_visible(False)

            self.set_stop_slideshow_visible(True)
            self.action_group.lookup_action("stop-slideshow").set_enabled(True)
        else:
            self.set_start_slideshow_visible(True)
            self.action_group.lookup_action("start-slideshow").set_enabled(True)

            self.set_stop_slideshow_visible(False)

    def set_zoom_sensitivities(self):
        if not self.currimg_is_animation:
            self.set_zoom_out_sensitivities(True)
            self.set_zoom_in_sensitivities(True)
        else:
            self.set_zoom_out_sensitivities(False)
            self.set_zoom_in_sensitivities(False)

    def print_version(self):
        print(_("Version: Mirage"), __version__)
        print(_("Website: https://gitlab.com/thomasross/mirage"))

    def print_usage(self):
        self.print_version()
        print("")
        print(_("Usage: mirage [OPTION]... FILES|FOLDERS..."))
        print("")
        print(_("Options") + ":")
        print("  -h, --help           " + _("Show this help and exit"))
        print("  -v, --version        " + _("Show version information and exit"))
        print("  -V, --verbose        " + _("Show more detailed information"))
        print("  -R, --recursive      " + _("Recursively include all images found in"))
        print("                       " + _("subdirectories of FOLDERS"))
        print("  -s, --slideshow      " + _("Start in slideshow mode"))
        print("  -f, --fullscreen     " + _("Start in fullscreen mode"))
        print("  -o, --onload 'cmd'   " + _("Execute 'cmd' when an image is loaded"))
        print("                       " + _("uses same syntax as custom actions,\n"))
        print("                       " + _("i.e. mirage -o 'echo file is %F'"))

    def slideshow_delay_changed(self, action):
        self.curr_slideshow_delay = (
            self.fullscreen_controls.slideshow_delay_adjustment.get_value()
        )
        if self.slideshow_mode:
            GLib.source_remove(self.timer_delay)
            if self.curr_slideshow_random:
                self.timer_delay = GLib.timeout_add(
                    int(self.curr_slideshow_delay * 1000),
                    self.goto_random_image,
                    None,
                    None,
                    "ss",
                )
            else:
                self.timer_delay = GLib.timeout_add(
                    (self.curr_slideshow_delay * 1000),
                    self.goto_next_image,
                    None,
                    None,
                    "ss",
                )
        self.window.set_focus(self.layout)

    def toggle_slideshow_shuffle(self, action, value, data):
        action.set_state(value)
        self.curr_slideshow_random = value.get_boolean()

    def motion_cb(self, widget, context, x, y, time):
        Gdk.drag_status(context, Gdk.DragAction.COPY, time)
        return True

    def drop_cb(self, widget, context, x, y, selection, info, time):
        uris = selection.get_uris()
        paths = [urllib.request.url2pathname(x) for x in uris]
        self.expand_filelist_and_load_image(paths)

    def put_error_image_to_window(self):
        self.imageview.set_from_stock(
            Gtk.STOCK_MISSING_IMAGE, Gtk.IconSize.LARGE_TOOLBAR
        )
        self.currimg_width = self.imageview.size_request().width
        self.currimg_height = self.imageview.size_request().height
        self.center_image()
        self.set_go_sensitivities(False)
        self.set_image_sensitivities(False)
        self.update_statusbar()
        self.loaded_img_in_list = -1

    def draw_image(self, widget, cr):
        if self.updating_adjustments:
            return
        self.updating_adjustments = True
        if self.hscroll.get_property("visible"):
            try:
                zoomratio = float(self.currimg_width) / self.previmg_width
                newvalue = abs(
                    self.layout.get_hadjustment().get_value() * zoomratio
                    + ((self.available_image_width()) * (zoomratio - 1)) // 2
                )
                if newvalue >= self.layout.get_hadjustment().get_lower() and newvalue <= (
                    self.layout.get_hadjustment().get_upper()
                    - self.layout.get_hadjustment().get_page_size()
                ):
                    self.layout.get_hadjustment().set_value(newvalue)
            except:
                print(traceback.format_exc(), file=sys.stderr)
        if self.vscroll.get_property("visible"):
            try:
                newvalue = abs(
                    self.layout.get_vadjustment().get_value() * zoomratio
                    + ((self.available_image_height()) * (zoomratio - 1)) // 2
                )
                if newvalue >= self.layout.get_vadjustment().get_lower() and newvalue <= (
                    self.layout.get_vadjustment().get_upper()
                    - self.layout.get_vadjustment().get_page_size()
                ):
                    self.layout.get_vadjustment().set_value(newvalue)
                self.previmg_width = self.currimg_width
            except:
                print(traceback.format_exc(), file=sys.stderr)
        self.updating_adjustments = False

    def window_resized(self, widget, allocation, force_update=False):
        # Update the image size on window resize if the current image was last fit:
        if self.image_loaded:
            if (
                force_update
                or allocation.width != self.prevwinwidth
                or allocation.height != self.prevwinheight
            ):
                if self.last_image_action_was_fit:
                    if self.last_image_action_was_smart_fit:
                        self.zoom_to_fit_or_1_to_1(None, False, False)
                    else:
                        self.zoom_to_fit_window(None, False, False)
                else:
                    self.center_image()
                self.load_new_image_stop_now()
                self.show_scrollbars_if_needed()
                # Also, regenerate preloaded image for new window size:
                self.preload_when_idle = GLib.idle_add(self.preload_next_image, True)
                self.preload_when_idle2 = GLib.idle_add(self.preload_prev_image, True)
        self.prevwinwidth = allocation.width
        self.prevwinheight = allocation.height
        return

    def save_settings(self):
        conf = configparser.ConfigParser(interpolation=None)
        conf.add_section("window")
        conf.set("window", "w", str(self.window.get_allocation().width))
        conf.set("window", "h", str(self.window.get_allocation().height))
        conf.set("window", "toolbar", str(self.toolbar_show))
        conf.set("window", "statusbar", str(self.statusbar_show))
        conf.set("window", "thumbpane", str(self.thumbpane_show))
        conf.add_section("prefs")
        conf.set("prefs", "simple-bgcolor", str(self.simple_bgcolor))
        conf.set("prefs", "bgcolor-red", str(self.bgcolor.red))
        conf.set("prefs", "bgcolor-green", str(self.bgcolor.green))
        conf.set("prefs", "bgcolor-blue", str(self.bgcolor.blue))
        conf.set("prefs", "open_all", str(self.open_all_images))
        conf.set("prefs", "hidden", str(self.open_hidden_files))
        conf.set("prefs", "use_last_dir", str(self.use_last_dir))
        conf.set("prefs", "last_dir", self.last_dir)
        conf.set("prefs", "fixed_dir", self.fixed_dir)
        conf.set("prefs", "open_mode", str(self.open_mode))
        conf.set("prefs", "last_mode", str(self.last_mode))
        conf.set("prefs", "listwrap_mode", str(self.listwrap_mode))
        conf.set("prefs", "slideshow_delay", str(int(self.slideshow_delay)))
        conf.set("prefs", "slideshow_random", str(self.slideshow_random))
        conf.set("prefs", "zoomquality", str(self.zoomvalue))
        conf.set("prefs", "quality_save", str(int(self.quality_save)))
        conf.set("prefs", "disable_screensaver", str(self.disable_screensaver))
        conf.set("prefs", "slideshow_in_fullscreen", str(self.slideshow_in_fullscreen))
        conf.set("prefs", "confirm_delete", str(self.confirm_delete))
        conf.set("prefs", "preloading_images", str(self.preloading_images))
        conf.set("prefs", "savemode", str(self.savemode))
        conf.set("prefs", "start_in_fullscreen", str(self.start_in_fullscreen))
        conf.set("prefs", "thumbsize", str(self.thumbnail_size))
        conf.set("prefs", "screenshot_delay", str(self.screenshot_delay))
        conf.add_section("actions")
        conf.set("actions", "num_actions", str(len(self.action_names)))
        for i in range(len(self.action_names)):
            conf.set("actions", "names[" + str(i) + "]", self.action_names[i])
            conf.set("actions", "commands[" + str(i) + "]", self.action_commands[i])
            conf.set("actions", "shortcuts[" + str(i) + "]", self.action_shortcuts[i])
            conf.set("actions", "batch[" + str(i) + "]", str(self.action_batch[i]))
        conf.add_section("recent")
        conf.set("recent", "num_recent", str(len(self.recentfiles)))
        for i in range(len(self.recentfiles)):
            conf.set("recent", "num[" + str(i) + "]", str(len(self.recentfiles[i])))
            conf.set("recent", "urls[" + str(i) + ",0]", self.recentfiles[i])
        if not os.path.exists(self.config_dir):
            os.makedirs(self.config_dir)
        with open(self.config_dir + "/miragerc", "w") as f:
            conf.write(f)

        # Also, save accel_map:
        Gtk.AccelMap.save(self.config_dir + "/accel_map")

        return

    def delete_event(self, widget, event, data=None):
        cancel = self.autosave_image()
        if cancel:
            return True
        self.stop_now = True
        self.closing_app = True
        self.save_settings()
        sys.exit(0)

    def destroy(self, event, data=None):
        cancel = self.autosave_image()
        if cancel:
            return True
        self.stop_now = True
        self.closing_app = True
        self.save_settings()

    def exit_app(self, action, parameter, data):
        cancel = self.autosave_image()
        if cancel:
            return True
        self.stop_now = True
        self.closing_app = True
        self.save_settings()
        sys.exit(0)

    def put_zoom_image_to_window(self, currimg_preloaded):
        self.window.get_window().freeze_updates()
        if not currimg_preloaded:
            # Always start with the original image to preserve quality!
            # Calculate image size:
            finalimg_width = int(
                self.currimg_pixbuf_original.get_width() * self.currimg_zoomratio
            )
            finalimg_height = int(
                self.currimg_pixbuf_original.get_height() * self.currimg_zoomratio
            )
            if not self.currimg_is_animation:
                # Scale image:
                if not self.currimg_pixbuf_original.get_has_alpha():
                    self.currimg_pixbuf = self.currimg_pixbuf_original.scale_simple(
                        finalimg_width, finalimg_height, self.zoom_quality
                    )
                else:
                    self.currimg_pixbuf = self.currimg_pixbuf_original.composite_color_simple(
                        finalimg_width,
                        finalimg_height,
                        self.zoom_quality,
                        255,
                        8,
                        0x666666,
                        0x999999,
                    )
            else:
                self.currimg_pixbuf = self.currimg_pixbuf_original
            self.currimg_width, self.currimg_height = finalimg_width, finalimg_height
        self.layout.set_size(self.currimg_width, self.currimg_height)
        self.center_image()
        self.show_scrollbars_if_needed()
        if not self.currimg_is_animation:
            self.imageview.set_from_pixbuf(self.currimg_pixbuf)
            self.previmage_is_animation = False
        else:
            self.imageview.set_from_animation(self.currimg_pixbuf)
            self.previmage_is_animation = True
        # Clean up (free memory) because I'm lazy
        gc.collect()
        self.window.get_window().thaw_updates()
        self.loaded_img_in_list = self.curr_img_in_list

    def show_scrollbars_if_needed(self):
        if self.currimg_width > self.available_image_width():
            self.hscroll.show()
        else:
            self.hscroll.hide()
        if self.currimg_height > self.available_image_height():
            self.vscroll.show()
        else:
            self.vscroll.hide()

    def center_image(self):
        x_shift = int((self.available_image_width() - self.currimg_width) // 2)
        if x_shift < 0:
            x_shift = 0
        y_shift = int((self.available_image_height() - self.currimg_height) // 2)
        if y_shift < 0:
            y_shift = 0
        self.layout.move(self.imageview, x_shift, y_shift)

    def available_image_width(self):
        return self.layout.get_allocation().width

    def available_image_height(self):
        return self.layout.get_allocation().height

    def save_image(self, action, parameter, data):
        if self.action_group.lookup_action("save-image").get_enabled():
            self.save_image_now(
                self.currimg_name,
                GdkPixbuf.Pixbuf.get_file_info(self.currimg_name)[0].get_name(),
            )

    def save_image_as(self, action, parameter, data):
        dialog = Gtk.FileChooserDialog(
            title=_("Save As"), action=Gtk.FileChooserAction.SAVE,
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL,
            Gtk.ResponseType.CANCEL,
            Gtk.STOCK_SAVE,
            Gtk.ResponseType.OK,
        )
        dialog.set_default_response(Gtk.ResponseType.OK)
        filename = os.path.basename(self.currimg_name)
        filetype = None
        dialog.set_current_folder(os.path.dirname(self.currimg_name))
        dialog.set_current_name(filename)
        dialog.set_do_overwrite_confirmation(True)
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            prev_name = self.currimg_name
            filename = dialog.get_filename()
            dialog.destroy()
            fileext = os.path.splitext(os.path.basename(filename))[1].lower()
            if len(fileext) > 0:
                fileext = fileext[1:]
            # Override filetype if user typed a filename with a different extension:
            for i in GdkPixbuf.Pixbuf.get_formats():
                if fileext in i.get_extensions():
                    filetype = i.get_name()
            self.save_image_now(filename, filetype)
            self.register_file_with_recent_docs(filename)
        else:
            dialog.destroy()

    def save_image_now(self, dest_name, filetype):
        try:
            self.change_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
            while Gtk.events_pending():
                Gtk.main_iteration()
            if filetype == None:
                filetype = GdkPixbuf.Pixbuf.get_file_info(self.currimg_name)[
                    0
                ].get_name()
            if self.filetype_is_writable(filetype):
                metadata = GExiv2.Metadata.new()
                metadata.open_path(self.currimg_name)
                self.currimg_pixbuf_original.savev(
                    dest_name, filetype, ["quality"], [str(self.quality_save)]
                )
                self.currimg_name = dest_name
                metadata.save_file(self.currimg_name)
                self.image_list[self.curr_img_in_list] = dest_name
                self.update_title()
                self.update_statusbar()
                # Update thumbnail:
                GLib.idle_add(
                    self.thumbpane_set_image, dest_name, self.curr_img_in_list, True
                )
                self.image_modified = False
            else:
                error_dialog = Gtk.MessageDialog(
                    self.window,
                    Gtk.DialogFlags.MODAL,
                    Gtk.MessageType.WARNING,
                    Gtk.ButtonsType.YES_NO,
                    _(
                        "The %s format is not supported for saving. Do you wish to save the file in a different format?"
                    )
                    % filetype,
                )
                error_dialog.set_title(_("Save"))
                response = error_dialog.run()
                if response == Gtk.ResponseType.YES:
                    error_dialog.destroy()
                    while Gtk.events_pending():
                        Gtk.main_iteration()
                    self.save_image_as(None, None, None)
                else:
                    error_dialog.destroy()
        except:
            print(traceback.format_exc(), file=sys.stderr)
            error_dialog = Gtk.MessageDialog(
                self.window,
                Gtk.DialogFlags.MODAL,
                Gtk.MessageType.WARNING,
                Gtk.ButtonsType.CLOSE,
                _("Unable to save %s") % dest_name,
            )
            error_dialog.set_title(_("Save"))
            error_dialog.run()
            error_dialog.destroy()
        self.change_cursor(None)

    def autosave_image(self):
        # Returns True if the user has canceled out of the dialog
        # Never call this function from an idle or timeout loop! That will cause
        # the app to freeze.
        if self.image_modified:
            if self.savemode == 1:
                action = self.action_group.lookup_action("save-image")
                temp = action.get_enabled()
                action.set_enabled(True)
                self.save_image(None, None, None)
                action.set_enabled(temp)
            elif self.savemode == 2:
                dialog = Gtk.MessageDialog(
                    self.window,
                    Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                    Gtk.MessageType.QUESTION,
                    Gtk.ButtonsType.NONE,
                    _("The current image has been modified. Save changes?"),
                )
                dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
                dialog.add_button(Gtk.STOCK_NO, Gtk.ResponseType.NO)
                dialog.add_button(Gtk.STOCK_SAVE, Gtk.ResponseType.YES)
                dialog.set_title(_("Save?"))
                dialog.set_default_response(Gtk.ResponseType.YES)
                response = dialog.run()
                dialog.destroy()
                if response == Gtk.ResponseType.YES:
                    action = self.action_group.lookup_action("save-image")
                    temp = action.get_enabled()
                    action.set_enabled(True)
                    self.save_image(None, None, None)
                    action.set_enabled(temp)
                    self.image_modified = False
                elif response == Gtk.ResponseType.NO:
                    self.image_modified = False
                    # Ensures that we don't use the current pixbuf for any preload pixbufs if we are in
                    # the process of loading the previous or next image in the list:
                    self.currimg_pixbuf = self.currimg_pixbuf_original
                    self.preloadimg_next_in_list = -1
                    self.preloadimg_prev_in_list = -1
                    self.loaded_img_in_list = -1
                else:
                    return True

    def filetype_is_writable(self, filetype):
        # Determine if filetype is a writable format
        filetype_is_writable = True
        for i in GdkPixbuf.Pixbuf.get_formats():
            if filetype in i.get_extensions():
                if i.is_writable():
                    return True
        return False

    def open_file(self, action, parameter, data):
        self.stop_now = True
        while Gtk.events_pending():
            Gtk.main_iteration()
        self.open_file_or_folder(True)

    def open_file_remote(self, action, parameter, data):
        # Prompt user for the url:
        dialog = Gtk.Dialog(_("Open Remote"), self.window, Gtk.DialogFlags.MODAL)
        dialog.add_buttons(
            Gtk.STOCK_CANCEL,
            Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN,
            Gtk.ResponseType.OK,
        )
        location = Gtk.Entry.new()
        location.set_size_request(300, -1)
        location.set_activates_default(True)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox.pack_start(Gtk.Label.new(_("Image Location (URL):")), False, False, 5)
        hbox.pack_start(location, True, True, 5)
        dialog.vbox.pack_start(hbox, True, True, 10)
        dialog.set_default_response(Gtk.ResponseType.OK)
        dialog.vbox.show_all()
        dialog.connect("response", self.open_file_remote_response, location)
        response = dialog.show()

    def open_file_remote_response(self, dialog, response, location):
        if response == Gtk.ResponseType.OK:
            filenames = []
            filenames.append(location.get_text())
            dialog.destroy()
            while Gtk.events_pending():
                Gtk.main_iteration()
            self.expand_filelist_and_load_image(filenames)
        else:
            dialog.destroy()

    def open_folder(self, action, parameter, data):
        self.stop_now = True
        while Gtk.events_pending():
            Gtk.main_iteration()
        self.open_file_or_folder(False)

    def open_file_or_folder(self, isfile):
        self.thumbpane_create_dir()
        cancel = self.autosave_image()
        if cancel:
            return
        # If isfile = True, file; If isfile = False, folder
        dialog = Gtk.FileChooserDialog(
            title=_("Open"), action=Gtk.FileChooserAction.OPEN,
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL,
            Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN,
            Gtk.ResponseType.OK,
        )
        if isfile:
            filter = Gtk.FileFilter.new()
            filter.set_name(_("Images"))
            filter.add_pixbuf_formats()
            dialog.add_filter(filter)
            filter = Gtk.FileFilter.new()
            filter.set_name(_("All files"))
            filter.add_pattern("*")
            dialog.add_filter(filter)
            preview = Gtk.Image.new()
            dialog.set_preview_widget(preview)
            dialog.set_use_preview_label(False)
            dialog.connect("update-preview", self.update_preview, preview)
            recursivebutton = None
        else:
            dialog.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
            recursivebutton = Gtk.CheckButton.new_with_label(
                _("Include images in subdirectories")
            )
            dialog.set_extra_widget(recursivebutton)
        dialog.set_default_response(Gtk.ResponseType.OK)
        dialog.set_select_multiple(True)
        if self.use_last_dir:
            if self.last_dir != None:
                dialog.set_current_folder(self.last_dir)
        else:
            if self.fixed_dir != None:
                dialog.set_current_folder(self.fixed_dir)
        dialog.connect(
            "response", self.open_file_or_folder_response, isfile, recursivebutton
        )
        response = dialog.show()

    def open_file_or_folder_response(self, dialog, response, isfile, recursivebutton):
        if response == Gtk.ResponseType.OK:
            if self.use_last_dir:
                self.last_dir = dialog.get_current_folder()
            if not isfile and recursivebutton.get_property("active"):
                self.recursive = True
            filenames = dialog.get_filenames()
            dialog.destroy()
            while Gtk.events_pending():
                Gtk.main_iteration()
            self.expand_filelist_and_load_image(filenames)
        else:
            dialog.destroy()

    def update_preview(self, file_chooser, preview):
        filename = file_chooser.get_preview_filename()
        if not filename:
            return
        filename, thumbfile = self.thumbnail_get_name(filename)
        pixbuf = self.thumbpane_get_pixbuf(thumbfile, filename, False)
        if pixbuf:
            preview.set_from_pixbuf(pixbuf)
        else:
            pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, 1, 8, 128, 128)
            pixbuf.fill(0x00000000)
            preview.set_from_pixbuf(pixbuf)
        have_preview = True
        file_chooser.set_preview_widget_active(have_preview)
        del pixbuf
        gc.collect()

    def hide_cursor(self):
        if (
            self.fullscreen_mode
            and not self.user_prompt_visible
            and not self.slideshow_controls_visible
        ):
            invisible = Gdk.Cursor.new_for_display(
                self.window.get_window().get_display(), Gdk.CursorType.BLANK_CURSOR
            )
            self.change_cursor(invisible)

        return False

    def set_enter_fullscreen_visible(self, visible):
        self.add_remove_action("enter-fullscreen", self.enter_fullscreen, visible)

    def set_leave_fullscreen_visible(self, visible):
        self.add_remove_action("leave-fullscreen", self.leave_fullscreen, visible)

    def enter_fullscreen(self, action, parameter, data):
        if not self.fullscreen_mode:
            self.fullscreen_mode = True
            self.set_enter_fullscreen_visible(False)
            self.set_leave_fullscreen_visible(True)
            self.statusbar.hide()
            self.statusbar2.hide()
            self.toolbar.hide()
            self.window.set_show_menubar(False)
            self.thumbscroll.hide()
            self.thumbpane.hide()
            self.window.fullscreen()
            self.timer_id = GLib.timeout_add(2000, self.hide_cursor)
            self.set_slideshow_sensitivities()
        else:
            self.leave_fullscreen(action)

    def leave_fullscreen(self, action, parameter, data):
        if self.fullscreen_mode:
            self.slideshow_controls_visible = False
            self.fullscreen_controls.hide()
            self.fullscreen_mode = False
            self.set_enter_fullscreen_visible(True)
            self.set_leave_fullscreen_visible(False)
            if self.toolbar_show:
                self.toolbar.show()
            self.window.set_show_menubar(True)
            if self.statusbar_show:
                self.statusbar.show()
                self.statusbar2.show()
            if self.thumbpane_show:
                self.thumbscroll.show()
                self.thumbpane.show()
                self.thumbpane_update_images(False, self.curr_img_in_list)
            self.window.unfullscreen()
            self.change_cursor(None)
            self.set_slideshow_sensitivities()

    def toggle_status_bar(self, action, value, data):
        action.set_state(value)
        if not value.get_boolean():
            self.statusbar.hide()
            self.statusbar2.hide()
            self.statusbar_show = False
        else:
            self.statusbar.show()
            self.statusbar2.show()
            self.statusbar_show = True
        if self.image_loaded and self.last_image_action_was_fit:
            if self.last_image_action_was_smart_fit:
                self.zoom_to_fit_or_1_to_1(None, False, False)
            else:
                self.zoom_to_fit_window(None, False, False)

    def toggle_thumbpane(self, action, value, data):
        action.set_state(value)
        if not value.get_boolean():
            self.thumbscroll.hide()
            self.thumbpane.hide()
            self.thumbpane_show = False
        else:
            self.thumbscroll.show()
            self.thumbpane.show()
            self.thumbpane_show = True
            self.stop_now = False
            GLib.idle_add(self.thumbpane_update_images, True, self.curr_img_in_list)
        if self.image_loaded and self.last_image_action_was_fit:
            if self.last_image_action_was_smart_fit:
                self.zoom_to_fit_or_1_to_1(None, False, False)
            else:
                self.zoom_to_fit_window(None, False, False)

    def toggle_toolbar(self, action, value, data):
        action.set_state(value)
        if not value.get_boolean():
            self.toolbar.hide()
            self.toolbar_show = False
        else:
            self.toolbar.show()
            self.toolbar_show = True
        if self.image_loaded and self.last_image_action_was_fit:
            if self.last_image_action_was_smart_fit:
                self.zoom_to_fit_or_1_to_1(None, False, False)
            else:
                self.zoom_to_fit_window(None, False, False)

    def update_statusbar(self):
        # Update status bar:
        try:
            st = os.stat(self.currimg_name)
            filesize = st[stat.ST_SIZE] // 1000
            ratio = int(100 * self.currimg_zoomratio)
            status_text = (
                os.path.basename(self.currimg_name)
                + ":  "
                + str(self.currimg_pixbuf_original.get_width())
                + "x"
                + str(self.currimg_pixbuf_original.get_height())
                + "   "
                + str(filesize)
                + "KB   "
                + str(ratio)
                + "%   "
            )
        except:
            print(traceback.format_exc(), file=sys.stderr)
            status_text = _("Cannot load image.")
        self.statusbar.push(self.statusbar.get_context_id(""), status_text)
        status_text = ""
        if self.running_custom_actions:
            status_text = _("Custom actions: %(current)i of  %(total)i") % {
                "current": self.curr_custom_action,
                "total": self.num_custom_actions,
            }
        elif self.searching_for_images:
            status_text = _("Scanning...")
        self.statusbar2.push(self.statusbar2.get_context_id(""), status_text)

    def show_custom_actions(self, action, parameter, data):
        self.actions_dialog = Gtk.Dialog(title=_("Configure Custom Actions"))
        self.actions_dialog.set_transient_for(self.window)
        self.actions_dialog.set_resizable(False)
        table_actions = Gtk.Table.new(13, 2, False)
        table_actions.attach(
            Gtk.Label.new(""),
            1,
            2,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        actionscrollwindow = Gtk.ScrolledWindow.new(None, None)
        self.actionstore = Gtk.ListStore.new([str, str, str])
        self.actionwidget = Gtk.TreeView.new()
        self.actionwidget.set_enable_search(False)
        self.actionwidget.set_rules_hint(True)
        self.actionwidget.connect("row-activated", self.edit_custom_action2)
        actionscrollwindow.add(self.actionwidget)
        actionscrollwindow.set_shadow_type(Gtk.ShadowType.IN)
        actionscrollwindow.set_policy(
            Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC
        )
        actionscrollwindow.set_size_request(500, 200)
        self.actionwidget.set_model(self.actionstore)
        self.cell = Gtk.CellRendererText.new()
        self.cellbool = Gtk.CellRendererPixbuf.new()
        self.tvcolumn0 = Gtk.TreeViewColumn(title=_("Batch"))
        self.tvcolumn1 = Gtk.TreeViewColumn(
            title=_("Action"), cell_renderer=self.cell, markup=0
        )
        self.tvcolumn2 = Gtk.TreeViewColumn(title=_("Shortcut"))
        self.tvcolumn1.set_max_width(
            self.actionwidget.size_request().width
            - self.tvcolumn0.get_width()
            - self.tvcolumn2.get_width()
        )
        self.actionwidget.append_column(self.tvcolumn0)
        self.actionwidget.append_column(self.tvcolumn1)
        self.actionwidget.append_column(self.tvcolumn2)
        self.populate_treeview()
        if len(self.action_names) > 0:
            self.actionwidget.get_selection().select_path(0)
        vbox_actions = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        addbutton = Gtk.Button.new_from_icon_name("list-add", Gtk.IconSize.BUTTON)
        addbutton.connect("clicked", self.add_custom_action, self.actionwidget)
        addbutton.set_tooltip_text(_("Add action"))
        editbutton = Gtk.Button.new_from_icon_name(
            "document-edit-symbolic", Gtk.IconSize.BUTTON
        )
        editbutton.connect("clicked", self.edit_custom_action, self.actionwidget)
        editbutton.set_tooltip_text(_("Edit selected action."))
        removebutton = Gtk.Button.new_from_icon_name("list-remove", Gtk.IconSize.BUTTON)
        removebutton.connect("clicked", self.remove_custom_action)
        removebutton.set_tooltip_text(_("Remove selected action."))
        upbutton = Gtk.Button.new_from_icon_name("go-up", Gtk.IconSize.BUTTON)
        upbutton.connect("clicked", self.custom_action_move_up, self.actionwidget)
        upbutton.set_tooltip_text(_("Move selected action up."))
        downbutton = Gtk.Button.new_from_icon_name("go-down", Gtk.IconSize.BUTTON)
        downbutton.connect("clicked", self.custom_action_move_down, self.actionwidget)
        downbutton.set_tooltip_text(_("Move selected action down."))
        vbox_buttons = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        propertyinfo = Gtk.Label.new("")
        propertyinfo.set_markup(
            "<small>"
            + _("Parameters")
            + ':\n<span font_family="Monospace">%F</span> - '
            + _("File path, name, and extension")
            + '\n<span font_family="Monospace">%P</span> - '
            + _("File path")
            + '\n<span font_family="Monospace">%N</span> - '
            + _("File name without file extension")
            + '\n<span font_family="Monospace">%E</span> - '
            + _('File extension (i.e. ".png")')
            + '\n<span font_family="Monospace">%L</span> - '
            + _("List of files, space-separated")
            + "</small>"
        )
        propertyinfo.set_alignment(0, 0)
        actioninfo = Gtk.Label.new("")
        actioninfo.set_markup(
            "<small>"
            + _("Operations")
            + ':\n<span font_family="Monospace">[NEXT]</span> - '
            + _("Go to next image")
            + '\n<span font_family="Monospace">[PREV]</span> - '
            + _("Go to previous image")
            + "</small>"
        )
        actioninfo.set_alignment(0, 0)
        hbox_info = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_info.pack_start(propertyinfo, False, False, 15)
        hbox_info.pack_start(actioninfo, False, False, 15)
        vbox_buttons.pack_start(addbutton, False, False, 5)
        vbox_buttons.pack_start(editbutton, False, False, 5)
        vbox_buttons.pack_start(removebutton, False, False, 5)
        vbox_buttons.pack_start(upbutton, False, False, 5)
        vbox_buttons.pack_start(downbutton, False, False, 0)
        hbox_top = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_top.pack_start(actionscrollwindow, True, True, 5)
        hbox_top.pack_start(vbox_buttons, False, False, 5)
        vbox_actions.pack_start(hbox_top, True, True, 5)
        vbox_actions.pack_start(hbox_info, False, False, 5)
        hbox_instructions = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        info_image = Gtk.Image.new()
        info_image.set_from_stock(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON)
        hbox_instructions.pack_start(info_image, False, False, 5)
        instructions = Gtk.Label.new(
            _(
                "Here you can define custom actions with shortcuts. Actions use the built-in parameters and operations listed below and can have multiple statements separated by a semicolon. Batch actions apply to all images in the list."
            )
        )
        instructions.set_line_wrap(True)
        instructions.set_alignment(0, 0.5)
        hbox_instructions.pack_start(instructions, False, False, 5)
        table_actions.attach(
            hbox_instructions,
            1,
            3,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            5,
            0,
        )
        table_actions.attach(
            Gtk.Label.new(""),
            1,
            3,
            3,
            4,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table_actions.attach(
            vbox_actions,
            1,
            3,
            4,
            12,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table_actions.attach(
            Gtk.Label.new(""),
            1,
            3,
            12,
            13,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        self.actions_dialog.vbox.pack_start(table_actions, False, False, 0)
        # Show dialog:
        self.actions_dialog.vbox.show_all()
        instructions.set_size_request(self.actions_dialog.size_request().width - 50, -1)
        close_button = self.actions_dialog.add_button(
            Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE
        )
        close_button.grab_focus()
        self.actions_dialog.run()
        self.refresh_custom_actions_menu()
        while Gtk.events_pending():
            Gtk.main_iteration()
        if len(self.image_list) == 0:
            self.set_image_sensitivities(False)
        self.actions_dialog.destroy()

    def hash_for_custom_action(self, name, command, action, batch):
        return hashlib.sha1(
            "{}$${}$${}$${}".format(name, command, action, batch).encode("utf8")
        ).hexdigest()

    def add_custom_action(self, button, treeview):
        self.open_custom_action_dialog(True, "", "", "None", False, treeview)

    def edit_custom_action2(self, treeview, path, view_column):
        self.edit_custom_action(None, treeview)

    def edit_custom_action(self, button, treeview):
        (model, iter) = self.actionwidget.get_selection().get_selected()
        if iter != None:
            (row,) = self.actionstore.get_path(iter)
            self.open_custom_action_dialog(
                False,
                self.action_names[row],
                self.action_commands[row],
                self.action_shortcuts[row],
                self.action_batch[row],
                treeview,
            )

    def open_custom_action_dialog(
        self, add_call, name, command, shortcut, batch, treeview
    ):
        if add_call:
            self.dialog_name = Gtk.Dialog(
                title=_("Add Custom Action"), flags=Gtk.DialogFlags.MODAL,
            )
            self.dialog_name.set_transient_for(self.actions_dialog)
            self.dialog_name.add_buttons(
                Gtk.STOCK_CANCEL,
                Gtk.ResponseType.REJECT,
                Gtk.STOCK_OK,
                Gtk.ResponseType.ACCEPT,
            )
        else:
            self.dialog_name = Gtk.Dialog(
                title=_("Edit Custom Action"), flags=Gtk.DialogFlags.MODAL,
            )
            self.dialog_name.set_transient_for(self.actions_dialog)
            self.dialog_name.add_buttons(
                Gtk.STOCK_CANCEL,
                Gtk.ResponseType.REJECT,
                Gtk.STOCK_OK,
                Gtk.ResponseType.ACCEPT,
            )

        self.dialog_name.set_modal(True)
        table = Gtk.Table.new(2, 4, False)
        action_name_label = Gtk.Label.new(_("Action Name:"))
        action_name_label.set_alignment(0, 0.5)
        action_command_label = Gtk.Label.new(_("Command:"))
        action_command_label.set_alignment(0, 0.5)
        shortcut_label = Gtk.Label.new(_("Shortcut:"))
        shortcut_label.set_alignment(0, 0.5)
        table.attach(
            action_name_label,
            0,
            1,
            0,
            1,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table.attach(
            action_command_label,
            0,
            1,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table.attach(
            shortcut_label,
            0,
            1,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        action_name = Gtk.Entry.new()
        action_name.set_text(name)
        action_command = Gtk.Entry.new()
        action_command.set_text(command)
        table.attach(
            action_name,
            1,
            2,
            0,
            1,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table.attach(
            action_command,
            1,
            2,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        self.shortcut = Gtk.Button.new_with_label(shortcut)
        self.shortcut.connect("clicked", self.shortcut_clicked)
        table.attach(
            self.shortcut,
            1,
            2,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        batchmode = Gtk.CheckButton.new_with_label(
            _("Perform action on all images (Batch)")
        )
        batchmode.set_active(batch)
        table.attach(
            batchmode,
            0,
            2,
            3,
            4,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        self.dialog_name.vbox.pack_start(table, False, False, 5)
        self.dialog_name.vbox.show_all()
        self.dialog_name.connect(
            "response",
            self.dialog_name_response,
            add_call,
            action_name,
            action_command,
            self.shortcut,
            batchmode,
            treeview,
        )
        self.dialog_name.run()

    def dialog_name_response(
        self,
        dialog,
        response,
        add_call,
        action_name,
        action_command,
        shortcut,
        batchmode,
        treeview,
    ):
        if response == Gtk.ResponseType.ACCEPT:
            if not (
                action_command.get_text() == ""
                or action_name.get_text() == ""
                or self.shortcut.get_label() == "None"
            ):
                name = action_name.get_text()
                command = action_command.get_text()
                if (
                    ("[NEXT]" in command.strip()) and command.strip()[-6:] != "[NEXT]"
                ) or (
                    ("[PREV]" in command.strip()) and command.strip()[-6:] != "[PREV]"
                ):
                    error_dialog = Gtk.MessageDialog(
                        self.actions_dialog,
                        Gtk.DialogFlags.MODAL,
                        Gtk.MessageType.WARNING,
                        Gtk.ButtonsType.CLOSE,
                        _(
                            "[PREV] and [NEXT] are only valid alone or at the end of the command"
                        ),
                    )
                    error_dialog.set_title(_("Invalid Custom Action"))
                    error_dialog.run()
                    error_dialog.destroy()
                    return
                shortcut = shortcut.get_label()
                batch = batchmode.get_active()
                dialog.destroy()
                if add_call:
                    self.action_names.append(name)
                    self.action_hashes.append(
                        self.hash_for_custom_action(name, command, shortcut, batch)
                    )
                    self.action_commands.append(command)
                    self.action_shortcuts.append(shortcut)
                    self.action_batch.append(batch)
                else:
                    (model, iter) = self.actionwidget.get_selection().get_selected()
                    (rownum,) = self.actionstore.get_path(iter)
                    self.action_names[rownum] = name
                    self.action_hashes[rownum] = self.hash_for_custom_action(
                        name, command, shortcut, batch
                    )
                    self.action_commands[rownum] = command
                    self.action_shortcuts[rownum] = shortcut
                    self.action_batch[rownum] = batch
                self.populate_treeview()
                if add_call:
                    rownum = len(self.action_names) - 1
                treeview.get_selection().select_path(rownum)
                while Gtk.events_pending():
                    Gtk.main_iteration()
                # Keep item in visible rect:
                visible_rect = treeview.get_visible_rect()
                row_rect = treeview.get_background_area(
                    Gtk.TreePath(rownum), self.tvcolumn1
                )
                if row_rect.y + row_rect.height > visible_rect.height:
                    top_coord = (
                        row_rect.y + row_rect.height - visible_rect.height
                    ) + visible_rect.y
                    treeview.scroll_to_point(-1, top_coord)
                elif row_rect.y < 0:
                    treeview.scroll_to_cell(rownum)
            else:
                error_dialog = Gtk.MessageDialog(
                    self.actions_dialog,
                    Gtk.DialogFlags.MODAL,
                    Gtk.MessageType.WARNING,
                    Gtk.ButtonsType.CLOSE,
                    _("Incomplete custom action specified."),
                )
                error_dialog.set_title(_("Invalid Custom Action"))
                error_dialog.run()
                error_dialog.destroy()
        else:
            dialog.destroy()

    def custom_action_move_down(self, button, treeview):
        iter = None
        selection = treeview.get_selection()
        model, iter = selection.get_selected()
        if iter:
            rownum = int(model.get_string_from_iter(iter))
            if rownum < len(self.action_names) - 1:
                # Move item down:
                temp_name = self.action_names[rownum]
                temp_hash = self.action_hashes[rownum]
                temp_shortcut = self.action_shortcuts[rownum]
                temp_command = self.action_commands[rownum]
                temp_batch = self.action_batch[rownum]
                self.action_names[rownum] = self.action_names[rownum + 1]
                self.action_hashes[rownum] = self.action_hashes[rownum + 1]
                self.action_shortcuts[rownum] = self.action_shortcuts[rownum + 1]
                self.action_commands[rownum] = self.action_commands[rownum + 1]
                self.action_batch[rownum] = self.action_batch[rownum + 1]
                self.action_names[rownum + 1] = temp_name
                self.action_hashes[rownum + 1] = temp_hash
                self.action_shortcuts[rownum + 1] = temp_shortcut
                self.action_commands[rownum + 1] = temp_command
                self.action_batch[rownum + 1] = temp_batch
                # Repopulate treeview and keep item selected:
                self.populate_treeview()
                selection.select_path((rownum + 1,))
                while Gtk.events_pending():
                    Gtk.main_iteration()
                # Keep item in visible rect:
                rownum = rownum + 1
                visible_rect = treeview.get_visible_rect()
                row_rect = treeview.get_background_area(
                    Gtk.TreePath(rownum), self.tvcolumn1
                )
                if row_rect.y + row_rect.height > visible_rect.height:
                    top_coord = (
                        row_rect.y + row_rect.height - visible_rect.height
                    ) + visible_rect.y
                    treeview.scroll_to_point(-1, top_coord)
                elif row_rect.y < 0:
                    treeview.scroll_to_cell(rownum)

    def custom_action_move_up(self, button, treeview):
        iter = None
        selection = treeview.get_selection()
        model, iter = selection.get_selected()
        if iter:
            rownum = int(model.get_string_from_iter(iter))
            if rownum > 0:
                # Move item down:
                temp_name = self.action_names[rownum]
                temp_hash = self.action_hashes[rownum]
                temp_shortcut = self.action_shortcuts[rownum]
                temp_command = self.action_commands[rownum]
                temp_batch = self.action_batch[rownum]
                self.action_names[rownum] = self.action_names[rownum - 1]
                self.action_hashes[rownum] = self.action_hashes[rownum - 1]
                self.action_shortcuts[rownum] = self.action_shortcuts[rownum - 1]
                self.action_commands[rownum] = self.action_commands[rownum - 1]
                self.action_batch[rownum] = self.action_batch[rownum - 1]
                self.action_names[rownum - 1] = temp_name
                self.action_hashes[rownum - 1] = temp_hash
                self.action_shortcuts[rownum - 1] = temp_shortcut
                self.action_commands[rownum - 1] = temp_command
                self.action_batch[rownum - 1] = temp_batch
                # Repopulate treeview and keep item selected:
                self.populate_treeview()
                selection.select_path((rownum - 1,))
                while Gtk.events_pending():
                    Gtk.main_iteration()
                # Keep item in visible rect:
                rownum = rownum - 1
                visible_rect = treeview.get_visible_rect()
                row_rect = treeview.get_background_area(
                    Gtk.TreePath(rownum), self.tvcolumn1
                )
                if row_rect.y + row_rect.height > visible_rect.height:
                    top_coord = (
                        row_rect.y + row_rect.height - visible_rect.height
                    ) + visible_rect.y
                    treeview.scroll_to_point(-1, top_coord)
                elif row_rect.y < 0:
                    treeview.scroll_to_cell(rownum)

    def shortcut_clicked(self, widget):
        self.dialog_shortcut = Gtk.Dialog(
            _("Action Shortcut"), self.dialog_name, Gtk.DialogFlags.MODAL,
        )
        self.dialog_shortcut.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
        self.shortcut_label = Gtk.Label.new(
            _("Press the desired shortcut for the action.")
        )
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox.pack_start(self.shortcut_label, False, False, 15)
        self.dialog_shortcut.vbox.pack_start(hbox, False, False, 5)
        self.dialog_shortcut.vbox.show_all()
        self.dialog_shortcut.connect("key-press-event", self.shortcut_keypress)
        self.dialog_shortcut.run()
        self.dialog_shortcut.destroy()

    def shortcut_keypress(self, widget, event):
        shortcut = Gtk.accelerator_name(event.keyval, event.get_state())
        if "<Mod2>" in shortcut:
            shortcut = shortcut.replace("<Mod2>", "")
        if (
            shortcut[(len(shortcut) - 2) : len(shortcut)] != "_L"
            and shortcut[(len(shortcut) - 2) : len(shortcut)] != "_R"
        ):
            # Validate to make sure the shortcut hasn't already been used:
            for i in range(len(self.keys)):
                if shortcut == self.keys[i][1]:
                    error_dialog = Gtk.MessageDialog(
                        self.dialog_shortcut,
                        Gtk.DialogFlags.MODAL,
                        Gtk.MessageType.WARNING,
                        Gtk.ButtonsType.CLOSE,
                        _("The shortcut '%(shortcut)s' is already used for '%(key)s'.")
                        % {"shortcut": shortcut, "key": self.keys[i][0]},
                    )
                    error_dialog.set_title(_("Invalid Shortcut"))
                    error_dialog.run()
                    error_dialog.destroy()
                    return
            for i in range(len(self.action_shortcuts)):
                if shortcut == self.action_shortcuts[i]:
                    error_dialog = Gtk.MessageDialog(
                        self.dialog_shortcut,
                        Gtk.DialogFlags.MODAL,
                        Gtk.MessageType.WARNING,
                        Gtk.ButtonsType.CLOSE,
                        _("The shortcut '%(shortcut)s' is already used for '%(key)s'.")
                        % {"shortcut": shortcut, "key": self.action_names[i]},
                    )
                    error_dialog.set_title(_("Invalid Shortcut"))
                    error_dialog.run()
                    error_dialog.destroy()
                    return
            self.shortcut.set_label(shortcut)
            widget.destroy()

    def remove_custom_action(self, button):
        (model, iter) = self.actionwidget.get_selection().get_selected()
        if iter != None:
            (row,) = self.actionstore.get_path(iter)
            self.action_names.pop(row)
            self.action_hashes.pop(row)
            self.action_shortcuts.pop(row)
            self.action_commands.pop(row)
            self.action_batch.pop(row)
            self.populate_treeview()
            self.actionwidget.grab_focus()

    def populate_treeview(self):
        self.actionstore.clear()
        for i in range(len(self.action_names)):
            if self.action_batch[i]:
                pb = Gtk.STOCK_APPLY
            else:
                pb = None
            self.actionstore.append(
                [
                    pb,
                    "<big><b>"
                    + self.action_names[i].replace("&", "&amp;")
                    + "</b></big>\n<small>"
                    + self.action_commands[i].replace("&", "&amp;")
                    + "</small>",
                    self.action_shortcuts[i],
                ]
            )
        self.tvcolumn0.clear()
        self.tvcolumn1.clear()
        self.tvcolumn2.clear()
        self.tvcolumn0.pack_start(self.cellbool, True)
        self.tvcolumn1.pack_start(self.cell, True)
        self.tvcolumn2.pack_start(self.cell, True)
        self.tvcolumn0.add_attribute(self.cellbool, "stock-id", 0)
        self.tvcolumn1.set_attributes(self.cell, markup=1)
        self.tvcolumn2.set_attributes(self.cell, text=2)
        self.tvcolumn1.set_expand(True)

    def screenshot(self, action, parameter, data):
        cancel = self.autosave_image()
        if cancel:
            return
        # Dialog:
        dialog = Gtk.Dialog(_("Screenshot"), self.window, Gtk.DialogFlags.MODAL,)
        dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
        snapbutton = dialog.add_button(_("_Snap"), Gtk.ResponseType.ACCEPT)
        snapimage = Gtk.Image.new()
        snapimage.set_from_stock(Gtk.STOCK_OK, Gtk.IconSize.BUTTON)
        snapbutton.set_image(snapimage)
        loc = Gtk.Label.new("")
        loc.set_markup("<b>" + _("Location") + "</b>")
        loc.set_alignment(0, 0)
        area1 = Gtk.RadioButton.new_with_label_from_widget(None, _("Entire screen"))
        area2 = Gtk.RadioButton.new_with_label_from_widget(
            area1, _("Window under pointer")
        )
        if not HAS_XMOUSE:
            area2.set_sensitive(False)
        area1.set_active(True)
        de = Gtk.Label.new("")
        de.set_markup("<b>" + _("Delay") + "</b>")
        de.set_alignment(0, 0)
        delaybox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        adj = Gtk.Adjustment.new(self.screenshot_delay, 0, 30, 1, 10, 0)
        delay = Gtk.SpinButton.new(adj, 0, 0)
        delay.set_numeric(True)
        delay.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
        delay.set_wrap(False)
        delaylabel = Gtk.Label.new(_(" seconds"))
        delaybox.pack_start(delay, False, True, 0)
        delaybox.pack_start(delaylabel, False, True, 0)
        table = Gtk.Table.new(8, 2, False)
        table.attach(
            Gtk.Label.new(""),
            1,
            2,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table.attach(
            loc,
            1,
            2,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table.attach(
            Gtk.Label.new(""),
            1,
            2,
            3,
            4,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table.attach(
            area1,
            1,
            2,
            4,
            5,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table.attach(
            area2,
            1,
            2,
            5,
            6,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table.attach(
            Gtk.Label.new(""),
            1,
            2,
            6,
            7,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table.attach(
            de,
            1,
            2,
            7,
            8,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table.attach(
            Gtk.Label.new(""),
            1,
            2,
            8,
            9,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table.attach(
            delaybox,
            1,
            2,
            9,
            10,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table.attach(
            Gtk.Label.new(""),
            1,
            2,
            10,
            11,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        dialog.vbox.pack_start(table, True, True, 0)
        dialog.set_default_response(Gtk.ResponseType.ACCEPT)
        dialog.vbox.show_all()
        response = dialog.run()
        if response == Gtk.ResponseType.ACCEPT:
            dialog.destroy()
            while Gtk.events_pending():
                Gtk.main_iteration()
            self.screenshot_delay = delay.get_value_as_int()
            GLib.timeout_add(
                int(self.screenshot_delay * 1000),
                self._screenshot_grab,
                area1.get_active(),
            )
        else:
            dialog.destroy()

    def _screenshot_grab(self, entire_screen):
        root_win = Gdk.get_default_root_window()
        if entire_screen:
            x = 0
            y = 0
            width = Gdk.Screen.width()
            height = Gdk.Screen.height()
        else:
            (x, y, width, height) = xmouse.geometry()
        pix = Gdk.pixbuf_get_from_window(root_win, x, y, width, height)
        # Save as /tmp/mirage-<random>/filename.ext
        tmpdir = tempfile.mkdtemp(prefix="mirage-") + "/"
        tmpfile = tmpdir + "screenshot.png"
        pix.savev(tmpfile, "png", [], [])
        # Load file:
        self.image_list = [tmpfile]
        self.curr_img_in_list = 0
        GLib.idle_add(self.load_new_image2, False, False, False, False, True)
        self.update_statusbar()
        self.set_go_navigation_sensitivities(False)
        self.set_slideshow_sensitivities()
        self.thumbpane_update_images(True, self.curr_img_in_list)
        del pix
        self.window.present()

    def show_properties(self, action, parameter, data):
        show_props = Gtk.Dialog(_("Properties"), self.window)
        show_props.set_resizable(False)
        table = Gtk.Table.new(3, 3, False)
        image = Gtk.Image.new()
        animtest = GdkPixbuf.PixbufAnimation.new_from_file(self.currimg_name)
        image_is_anim = False
        if animtest.is_static_image():
            pixbuf, image_width, image_height = self.get_pixbuf_of_size(
                self.currimg_pixbuf_original, 180, self.zoom_quality
            )
        else:
            pixbuf, image_width, image_height = self.get_pixbuf_of_size(
                animtest.get_static_image(), 180, self.zoom_quality
            )
            image_is_anim = True
        image.set_from_pixbuf(self.pixbuf_add_border(pixbuf))
        vbox_left = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        filename = Gtk.Label.new(_("File name:"))
        filename.set_alignment(1, 1)
        filedate = Gtk.Label.new(_("File modified:"))
        filedate.set_alignment(1, 1)
        imagesize = Gtk.Label.new(_("Dimensions:"))
        imagesize.set_alignment(1, 1)
        filesize = Gtk.Label.new(_("File size:"))
        filesize.set_alignment(1, 1)
        filetype = Gtk.Label.new(_("File type:"))
        filetype.set_alignment(1, 1)
        transparency = Gtk.Label.new(_("Transparency:"))
        transparency.set_alignment(1, 1)
        animation = Gtk.Label.new(_("Animation:"))
        animation.set_alignment(1, 1)
        bits = Gtk.Label.new(_("Bits per sample:"))
        bits.set_alignment(1, 1)
        channels = Gtk.Label.new(_("Channels:"))
        channels.set_alignment(1, 1)
        vbox_left.pack_start(filename, False, False, 2)
        vbox_left.pack_start(filedate, False, False, 2)
        vbox_left.pack_start(imagesize, False, False, 2)
        vbox_left.pack_start(filesize, False, False, 2)
        vbox_left.pack_start(filetype, False, False, 2)
        vbox_left.pack_start(transparency, False, False, 2)
        vbox_left.pack_start(animation, False, False, 2)
        vbox_left.pack_start(bits, False, False, 2)
        vbox_left.pack_start(channels, False, False, 2)
        vbox_right = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        filestat = os.stat(self.currimg_name)
        filename2 = Gtk.Label.new(os.path.basename(self.currimg_name))
        filedate2 = Gtk.Label.new(
            time.strftime("%c", time.localtime(filestat[stat.ST_MTIME]))
        )
        imagesize2 = Gtk.Label.new(
            str(self.currimg_pixbuf_original.get_width())
            + "x"
            + str(self.currimg_pixbuf_original.get_height())
        )
        filetype2 = Gtk.Label.new(
            GdkPixbuf.Pixbuf.get_file_info(self.currimg_name)[0].get_mime_types()[0]
        )
        filesize2 = Gtk.Label.new(str(filestat[stat.ST_SIZE] // 1000) + "KB")
        if not image_is_anim and pixbuf.get_has_alpha():
            transparency2 = Gtk.Label.new(_("Yes"))
        else:
            transparency2 = Gtk.Label.new(_("No"))
        if animtest.is_static_image():
            animation2 = Gtk.Label.new(_("No"))
        else:
            animation2 = Gtk.Label.new(_("Yes"))
        bits2 = Gtk.Label.new(str(pixbuf.get_bits_per_sample()))
        channels2 = Gtk.Label.new(str(pixbuf.get_n_channels()))
        filename2.set_alignment(0, 1)
        filedate2.set_alignment(0, 1)
        imagesize2.set_alignment(0, 1)
        filesize2.set_alignment(0, 1)
        filetype2.set_alignment(0, 1)
        transparency2.set_alignment(0, 1)
        animation2.set_alignment(0, 1)
        bits2.set_alignment(0, 1)
        channels2.set_alignment(0, 1)
        vbox_right.pack_start(filename2, False, False, 2)
        vbox_right.pack_start(filedate2, False, False, 2)
        vbox_right.pack_start(imagesize2, False, False, 2)
        vbox_right.pack_start(filesize2, False, False, 2)
        vbox_right.pack_start(filetype2, False, False, 2)
        vbox_right.pack_start(transparency2, False, False, 2)
        vbox_right.pack_start(animation2, False, False, 2)
        vbox_right.pack_start(bits2, False, False, 2)
        vbox_right.pack_start(channels2, False, False, 2)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox.pack_start(vbox_left, False, False, 3)
        hbox.pack_start(vbox_right, False, False, 3)
        table.attach(
            image,
            1,
            2,
            1,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table.attach(
            hbox,
            2,
            3,
            1,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        show_props.vbox.pack_start(table, False, False, 15)
        show_props.vbox.show_all()
        close_button = show_props.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
        close_button.grab_focus()
        show_props.run()
        show_props.destroy()

    def show_prefs(self, action, parameter, data):
        prev_thumbnail_size = self.thumbnail_size
        self.prefs_dialog = Gtk.Dialog(_("Mirage Preferences"), self.window)
        self.prefs_dialog.set_resizable(False)
        # "Interface" prefs:
        table_settings = Gtk.Table.new(14, 3, False)
        bglabel = Gtk.Label.new("")
        bglabel.set_markup("<b>" + _("Interface") + "</b>")
        bglabel.set_alignment(0, 1)
        color_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        colortext = Gtk.Label.new(_("Background color:"))
        self.colorbutton = Gtk.ColorButton.new_with_color(self.bgcolor)
        self.colorbutton.connect("color-set", self.bgcolor_selected)
        self.colorbutton.set_size_request(150, -1)
        self.colorbutton.set_tooltip_text(
            _("Sets the background color for the application.")
        )
        color_hbox.pack_start(colortext, False, False, 0)
        color_hbox.pack_start(self.colorbutton, False, False, 0)
        color_hbox.pack_start(Gtk.Label.new(""), True, True, 0)

        simplecolor_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        simplecolortext = Gtk.Label.new(_("Simple background color:"))
        simplecolorbutton = Gtk.CheckButton.new()
        simplecolorbutton.connect("toggled", self.simple_bgcolor_selected)
        simplecolor_hbox.pack_start(simplecolortext, False, False, 0)
        simplecolor_hbox.pack_start(simplecolorbutton, False, False, 0)
        simplecolor_hbox.pack_start(Gtk.Label.new(""), True, True, 0)
        if self.simple_bgcolor:
            simplecolorbutton.set_active(True)

        fullscreen = Gtk.CheckButton.new_with_label(_("Open Mirage in fullscreen mode"))
        fullscreen.set_active(self.start_in_fullscreen)
        thumbbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        thumblabel = Gtk.Label.new(_("Thumbnail size:"))
        thumbbox.pack_start(thumblabel, False, False, 0)
        thumbsize = Gtk.ComboBoxText.new()
        option = 0
        for size in self.thumbnail_sizes:
            thumbsize.append_text(size + " x " + size)
            if self.thumbnail_size == int(size):
                thumbsize.set_active(option)
            option += 1
        thumbbox.pack_start(thumbsize, False, False, 5)
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_settings.attach(
            bglabel,
            1,
            3,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            3,
            4,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_settings.attach(
            simplecolor_hbox,
            1,
            2,
            4,
            5,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            color_hbox,
            1,
            2,
            5,
            6,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            6,
            7,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_settings.attach(
            thumbbox,
            1,
            3,
            7,
            8,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            8,
            9,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            fullscreen,
            1,
            3,
            9,
            10,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            10,
            11,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            11,
            12,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            12,
            13,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            13,
            14,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_settings.attach(
            Gtk.Label.new(""),
            1,
            3,
            14,
            15,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        # "Behavior" tab:
        table_behavior = Gtk.Table.new(14, 2, False)
        openlabel = Gtk.Label.new("")
        openlabel.set_markup("<b>" + _("Open Behavior") + "</b>")
        openlabel.set_alignment(0, 1)
        hbox_openmode = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_openmode.pack_start(
            Gtk.Label.new(_("Open new image in:")), False, False, 0
        )
        combobox = Gtk.ComboBoxText.new()
        combobox.append_text(_("Smart Mode"))
        combobox.append_text(_("Zoom To Fit Mode"))
        combobox.append_text(_("1:1 Mode"))
        combobox.append_text(_("Last Active Mode"))
        combobox.set_active(self.open_mode)
        hbox_openmode.pack_start(combobox, False, False, 5)
        openallimages = Gtk.CheckButton.new_with_label(
            _("Load all images in current directory")
        )
        openallimages.set_active(self.open_all_images)
        openallimages.set_tooltip_text(
            _(
                "If enabled, opening an image in Mirage will automatically load all images found in that image's directory."
            )
        )
        hiddenimages = Gtk.CheckButton.new_with_label(_("Allow loading hidden files"))
        hiddenimages.set_active(self.open_hidden_files)
        hiddenimages.set_tooltip_text(
            _(
                "If checked, Mirage will open hidden files. Otherwise, hidden files will be ignored."
            )
        )
        openpref1 = Gtk.RadioButton.new_with_label_from_widget(
            None, _("Use last chosen directory")
        )
        openpref1.set_tooltip_text(
            _("The default 'Open' directory will be the last directory used.")
        )
        openpref2 = Gtk.RadioButton.new_with_label_from_widget(
            openpref1, _("Use this fixed directory:")
        )
        openpref2.connect("toggled", self.prefs_use_fixed_dir_clicked)
        openpref2.set_tooltip_text(
            _("The default 'Open' directory will be this specified directory.")
        )
        hbox_defaultdir = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        self.defaultdir = Gtk.Button.new()
        hbox_defaultdir.pack_start(Gtk.Label.new(""), True, True, 0)
        hbox_defaultdir.pack_start(self.defaultdir, False, False, 0)
        hbox_defaultdir.pack_start(Gtk.Label.new(""), True, True, 0)
        if len(self.fixed_dir) > 25:
            self.defaultdir.set_label("..." + self.fixed_dir[-22:])
        else:
            self.defaultdir.set_label(self.fixed_dir)
        self.defaultdir.connect("clicked", self.defaultdir_clicked)
        self.defaultdir.set_size_request(250, -1)
        if self.use_last_dir:
            openpref1.set_active(True)
            self.defaultdir.set_sensitive(False)
        else:
            openpref2.set_active(True)
            self.defaultdir.set_sensitive(True)
        table_behavior.attach(
            Gtk.Label.new(""),
            1,
            2,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_behavior.attach(
            openlabel,
            1,
            2,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table_behavior.attach(
            Gtk.Label.new(""),
            1,
            2,
            3,
            4,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_behavior.attach(
            hbox_openmode,
            1,
            2,
            4,
            5,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_behavior.attach(
            Gtk.Label.new(""),
            1,
            2,
            5,
            6,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_behavior.attach(
            openallimages,
            1,
            2,
            6,
            7,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_behavior.attach(
            hiddenimages,
            1,
            2,
            7,
            8,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_behavior.attach(
            Gtk.Label.new(""),
            1,
            2,
            8,
            9,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_behavior.attach(
            openpref1,
            1,
            2,
            9,
            10,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_behavior.attach(
            openpref2,
            1,
            2,
            10,
            11,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_behavior.attach(
            hbox_defaultdir,
            1,
            2,
            11,
            12,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            45,
            0,
        )
        table_behavior.attach(
            Gtk.Label.new(""),
            1,
            2,
            12,
            13,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            45,
            0,
        )
        # "Navigation" tab:
        table_navigation = Gtk.Table.new(14, 2, False)
        navlabel = Gtk.Label.new("")
        navlabel.set_markup("<b>" + _("Navigation") + "</b>")
        navlabel.set_alignment(0, 1)
        preloadnav = Gtk.CheckButton.new_with_label(
            _("Preload images for faster navigation")
        )
        preloadnav.set_active(self.preloading_images)
        preloadnav.set_tooltip_text(
            _(
                "If enabled, the next and previous images in the list will be preloaded during idle time. Note that the speed increase comes at the expense of memory usage, so it is recommended to disable this option on machines with limited ram."
            )
        )
        hbox_listwrap = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_listwrap.pack_start(
            Gtk.Label.new(_("Wrap around imagelist:")), False, False, 0
        )
        combobox2 = Gtk.ComboBoxText.new()
        combobox2.append_text(_("No"))
        combobox2.append_text(_("Yes"))
        combobox2.append_text(_("Prompt User"))
        combobox2.set_active(self.listwrap_mode)
        hbox_listwrap.pack_start(combobox2, False, False, 5)
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_navigation.attach(
            navlabel,
            1,
            2,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            3,
            4,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_navigation.attach(
            hbox_listwrap,
            1,
            2,
            4,
            5,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            5,
            6,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_navigation.attach(
            preloadnav,
            1,
            2,
            6,
            7,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            7,
            8,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            8,
            9,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            9,
            10,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            10,
            11,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            11,
            12,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            12,
            13,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_navigation.attach(
            Gtk.Label.new(""),
            1,
            2,
            13,
            14,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        # "Slideshow" tab:
        table_slideshow = Gtk.Table.new(14, 2, False)
        slideshowlabel = Gtk.Label.new("")
        slideshowlabel.set_markup("<b>" + _("Slideshow Mode") + "</b>")
        slideshowlabel.set_alignment(0, 1)
        hbox_delay = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_delay.pack_start(
            Gtk.Label.new(_("Delay between images in seconds:")), False, False, 0,
        )
        spin_adj = Gtk.Adjustment.new(self.slideshow_delay, 0, 50000, 1, 10, 0)
        delayspin = Gtk.SpinButton.new(spin_adj, 1.0, 0)
        delayspin.set_numeric(True)
        hbox_delay.pack_start(delayspin, False, False, 5)
        randomize = Gtk.CheckButton.new_with_label(_("Randomize order of images"))
        randomize.set_active(self.slideshow_random)
        randomize.set_tooltip_text(
            _(
                "If enabled, a random image will be chosen during slideshow mode (without loading any image twice)."
            )
        )
        disable_screensaver = Gtk.CheckButton.new_with_label(
            _("Disable screensaver in slideshow mode")
        )
        disable_screensaver.set_active(self.disable_screensaver)
        disable_screensaver.set_tooltip_text(
            _(
                "If enabled, xscreensaver will be temporarily disabled during slideshow mode."
            )
        )
        ss_in_fs = Gtk.CheckButton.new_with_label(_("Always start in fullscreen mode"))
        ss_in_fs.set_tooltip_text(
            _(
                "If enabled, starting a slideshow will put the application in fullscreen mode."
            )
        )
        ss_in_fs.set_active(self.slideshow_in_fullscreen)
        table_slideshow.attach(
            Gtk.Label.new(""),
            1,
            2,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_slideshow.attach(
            slideshowlabel,
            1,
            2,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table_slideshow.attach(
            Gtk.Label.new(""),
            1,
            2,
            3,
            4,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_slideshow.attach(
            hbox_delay,
            1,
            2,
            4,
            5,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_slideshow.attach(
            Gtk.Label.new(""),
            1,
            2,
            5,
            6,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_slideshow.attach(
            disable_screensaver,
            1,
            2,
            6,
            7,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_slideshow.attach(
            ss_in_fs,
            1,
            2,
            7,
            8,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_slideshow.attach(
            randomize,
            1,
            2,
            8,
            9,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_slideshow.attach(
            Gtk.Label.new(""),
            1,
            2,
            9,
            10,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_slideshow.attach(
            Gtk.Label.new(""),
            1,
            2,
            10,
            11,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_slideshow.attach(
            Gtk.Label.new(""),
            1,
            2,
            11,
            12,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_slideshow.attach(
            Gtk.Label.new(""),
            1,
            2,
            12,
            13,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        table_slideshow.attach(
            Gtk.Label.new(""),
            1,
            2,
            13,
            14,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            0,
            0,
        )
        # "Image" tab:
        table_image = Gtk.Table.new(14, 2, False)
        imagelabel = Gtk.Label.new("")
        imagelabel.set_markup("<b>" + _("Image Editing") + "</b>")
        imagelabel.set_alignment(0, 1)
        deletebutton = Gtk.CheckButton.new_with_label(_("Confirm image delete"))
        deletebutton.set_active(self.confirm_delete)

        zoom_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        zoom_hbox.pack_start(Gtk.Label.new(_("Scaling quality:")), False, False, 0)
        zoomcombo = Gtk.ComboBoxText.new()
        zoomcombo.append_text(_("Nearest (Fastest)"))
        zoomcombo.append_text(_("Tiles"))
        zoomcombo.append_text(_("Bilinear"))
        zoomcombo.append_text(_("Hyper (Best)"))
        zoomcombo.set_active(self.zoomvalue)
        zoom_hbox.pack_start(zoomcombo, False, False, 0)
        zoom_hbox.pack_start(Gtk.Label.new(""), True, True, 0)

        hbox_save = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        savelabel = Gtk.Label.new(_("Modified images:"))
        savecombo = Gtk.ComboBoxText.new()
        savecombo.append_text(_("Ignore Changes"))
        savecombo.append_text(_("Auto-Save"))
        savecombo.append_text(_("Prompt For Action"))
        savecombo.set_active(self.savemode)
        hbox_save.pack_start(savelabel, False, False, 0)
        hbox_save.pack_start(savecombo, False, False, 5)

        hbox_quality = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        qualitylabel = Gtk.Label.new(_("Quality to save in:"))
        qspin_adj = Gtk.Adjustment.new(self.quality_save, 0, 100, 1, 100, 0)
        qualityspin = Gtk.SpinButton.new(qspin_adj, 1.0, 0)
        qualityspin.set_numeric(True)
        hbox_quality.pack_start(qualitylabel, False, False, 0)
        hbox_quality.pack_start(qualityspin, False, False, 5)
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            1,
            2,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            imagelabel,
            1,
            3,
            2,
            3,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            15,
            0,
        )
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            3,
            4,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            zoom_hbox,
            1,
            3,
            4,
            5,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            5,
            6,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            hbox_save,
            1,
            3,
            6,
            7,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            7,
            8,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            hbox_quality,
            1,
            3,
            8,
            9,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            9,
            10,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            deletebutton,
            1,
            3,
            10,
            11,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            11,
            12,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            12,
            13,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            13,
            14,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        table_image.attach(
            Gtk.Label.new(""),
            1,
            3,
            14,
            15,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
            30,
            0,
        )
        # Add tabs:
        notebook = Gtk.Notebook.new()
        notebook.append_page(table_behavior, Gtk.Label.new(_("Behavior")))
        notebook.append_page(table_navigation, Gtk.Label.new(_("Navigation")))
        notebook.append_page(table_settings, Gtk.Label.new(_("Interface")))
        notebook.append_page(table_slideshow, Gtk.Label.new(_("Slideshow")))
        notebook.append_page(table_image, Gtk.Label.new(_("Image")))
        notebook.set_current_page(0)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        self.prefs_dialog.vbox.pack_start(hbox, False, False, 7)
        hbox.pack_start(notebook, False, False, 7)
        notebook.connect("switch-page", self.prefs_tab_switched)
        # Show prefs:
        self.prefs_dialog.vbox.show_all()
        self.close_button = self.prefs_dialog.add_button(
            Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE
        )
        self.close_button.grab_focus()
        response = self.prefs_dialog.run()
        if (
            response == Gtk.ResponseType.CLOSE
            or response == Gtk.ResponseType.DELETE_EVENT
        ):
            self.zoomvalue = zoomcombo.get_active()
            if int(round(self.zoomvalue, 0)) == 0:
                self.zoom_quality = GdkPixbuf.InterpType.NEAREST
            elif int(round(self.zoomvalue, 0)) == 1:
                self.zoom_quality = GdkPixbuf.InterpType.TILES
            elif int(round(self.zoomvalue, 0)) == 2:
                self.zoom_quality = GdkPixbuf.InterpType.BILINEAR
            elif int(round(self.zoomvalue, 0)) == 3:
                self.zoom_quality = GdkPixbuf.InterpType.HYPER
            self.open_all_images = openallimages.get_active()
            self.open_hidden_files = hiddenimages.get_active()
            if openpref1.get_active():
                self.use_last_dir = True
            else:
                self.use_last_dir = False
            open_mode_prev = self.open_mode
            self.open_mode = combobox.get_active()
            preloading_images_prev = self.preloading_images
            self.preloading_images = preloadnav.get_active()
            self.listwrap_mode = combobox2.get_active()
            self.slideshow_delay = delayspin.get_value()
            self.fullscreen_controls.set_slideshow_delay(self.slideshow_delay)
            self.curr_slideshow_delay = self.slideshow_delay
            self.slideshow_random = randomize.get_active()
            self.curr_slideshow_random = self.slideshow_random
            self.disable_screensaver = disable_screensaver.get_active()
            self.slideshow_in_fullscreen = ss_in_fs.get_active()
            self.savemode = savecombo.get_active()
            self.start_in_fullscreen = fullscreen.get_active()
            self.confirm_delete = deletebutton.get_active()
            self.quality_save = qualityspin.get_value()
            self.thumbnail_size = int(self.thumbnail_sizes[thumbsize.get_active()])
            if self.thumbnail_size != prev_thumbnail_size:
                GLib.idle_add(self.thumbpane_set_size)
                GLib.idle_add(self.thumbpane_update_images, True, self.curr_img_in_list)
            self.prefs_dialog.destroy()
            self.set_go_navigation_sensitivities(False)
            if (self.preloading_images and not preloading_images_prev) or (
                open_mode_prev != self.open_mode
            ):
                # The user just turned on preloading, so do it:
                self.preloadimg_next_in_list = -1
                self.preloadimg_prev_in_list = -1
                self.preload_when_idle = GLib.idle_add(self.preload_next_image, False)
                self.preload_when_idle2 = GLib.idle_add(self.preload_prev_image, False)
            elif not self.preloading_images:
                self.preloadimg_next_in_list = -1
                self.preloadimg_prev_in_list = -1

    def prefs_use_fixed_dir_clicked(self, button):
        if button.get_active():
            self.defaultdir.set_sensitive(True)
        else:
            self.defaultdir.set_sensitive(False)

    def rename_image(self, action, parameter, data):
        if len(self.image_list) > 0:
            temp_slideshow_mode = self.slideshow_mode
            if self.slideshow_mode:
                self.toggle_slideshow(None, None, None)
            rename_dialog = Gtk.Dialog(
                _("Rename Image"), self.window, Gtk.DialogFlags.MODAL
            )
            self.rename_txt = Gtk.Entry.new()
            filename = os.path.basename(self.currimg_name)
            self.rename_txt.set_text(filename)
            self.rename_txt.set_activates_default(True)
            cancelbutton = rename_dialog.add_button(
                Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
            )
            renamebutton = rename_dialog.add_button(
                _("_Rename"), Gtk.ResponseType.ACCEPT
            )
            renameimage = Gtk.Image.new()
            renameimage.set_from_stock(Gtk.STOCK_OK, Gtk.IconSize.BUTTON)
            renamebutton.set_image(renameimage)
            animtest = GdkPixbuf.PixbufAnimation.new_from_file(self.currimg_name)
            if animtest.is_static_image():
                pixbuf, image_width, image_height = self.get_pixbuf_of_size(
                    self.currimg_pixbuf_original, 60, self.zoom_quality
                )
            else:
                pixbuf, image_width, image_height = self.get_pixbuf_of_size(
                    animtest.get_static_image(), 60, self.zoom_quality
                )
            image = Gtk.Image.new()
            image.set_from_pixbuf(pixbuf)
            instructions = Gtk.Label.new(_("Enter the new name:"))
            instructions.set_alignment(0, 1)
            hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
            hbox.pack_start(image, False, False, 10)
            vbox_stuff = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
            vbox_stuff.pack_start(Gtk.Label.new(""), False, False, 0)
            vbox_stuff.pack_start(instructions, False, False, 0)
            vbox_stuff.pack_start(Gtk.Label.new(""), False, False, 0)
            vbox_stuff.pack_start(self.rename_txt, True, True, 0)
            vbox_stuff.pack_start(Gtk.Label.new(""), False, False, 0)
            hbox.pack_start(vbox_stuff, True, True, 10)
            rename_dialog.vbox.pack_start(hbox, False, False, 0)
            rename_dialog.set_default_response(Gtk.ResponseType.ACCEPT)
            rename_dialog.set_size_request(300, -1)
            rename_dialog.vbox.show_all()
            rename_dialog.connect("show", self.select_rename_text)
            response = rename_dialog.run()
            if response == Gtk.ResponseType.ACCEPT:
                try:
                    new_filename = (
                        os.path.dirname(self.currimg_name)
                        + "/"
                        + self.rename_txt.get_text()
                    )
                    shutil.move(self.currimg_name, new_filename)
                    # Update thumbnail filename:
                    try:
                        shutil.move(
                            self_get_name(self.currimg_name)[1],
                            self.thumbnail_get_name(new_filename)[1],
                        )
                    except:
                        print(traceback.format_exc(), file=sys.stderr)
                    self.recent_file_remove_and_refresh(self.currimg_name)
                    self.currimg_name = new_filename
                    self.register_file_with_recent_docs(self.currimg_name)
                    self.update_title()
                except:
                    print(traceback.format_exc(), file=sys.stderr)
                    error_dialog = Gtk.MessageDialog(
                        self.window,
                        Gtk.DialogFlags.MODAL,
                        Gtk.MessageType.WARNING,
                        Gtk.ButtonsType.OK,
                        _("Unable to rename %s") % self.currimg_name,
                    )
                    error_dialog.set_title(_("Unable to rename"))
                    error_dialog.run()
                    error_dialog.destroy()
            rename_dialog.destroy()
            if temp_slideshow_mode:
                self.toggle_slideshow(None, None, None)

    def select_rename_text(self, widget):
        filename = os.path.basename(self.currimg_name)
        fileext = os.path.splitext(os.path.basename(self.currimg_name))[1]
        self.rename_txt.select_region(0, len(filename) - len(fileext))

    def delete_image(self, action, parameter, data):
        if len(self.image_list) > 0:
            temp_slideshow_mode = self.slideshow_mode
            if self.slideshow_mode:
                self.toggle_slideshow(None, None, None)
            delete_dialog = Gtk.Dialog(
                _("Delete Image"), self.window, Gtk.DialogFlags.MODAL
            )
            if self.confirm_delete:
                permlabel = Gtk.Label.new(
                    _("Are you sure you wish to permanently delete %s?")
                    % os.path.split(self.currimg_name)[1]
                )
                permlabel.set_line_wrap(True)
                permlabel.set_alignment(0, 0.1)
                warningicon = Gtk.Image.new()
                warningicon.set_from_stock(
                    Gtk.STOCK_DIALOG_WARNING, Gtk.IconSize.DIALOG
                )
                hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
                hbox.pack_start(warningicon, False, False, 10)
                hbox.pack_start(permlabel, False, False, 10)
                delete_dialog.vbox.pack_start(Gtk.Label.new(""), False, False, 0)
                delete_dialog.vbox.pack_start(hbox, False, False, 0)
                cancelbutton = delete_dialog.add_button(
                    Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
                )
                deletebutton = delete_dialog.add_button(
                    Gtk.STOCK_DELETE, Gtk.ResponseType.YES
                )
                deletebutton.set_property("has-focus", True)
                delete_dialog.set_default_response(Gtk.ResponseType.YES)
                delete_dialog.vbox.show_all()
                response = delete_dialog.run()
            else:
                response = Gtk.ResponseType.YES
            if response == Gtk.ResponseType.YES:
                try:
                    os.remove(self.currimg_name)
                    self.image_modified = False
                    try:
                        os.remove(self.thumbnail_get_name(self.currimg_name)[1])
                    except:
                        print(traceback.format_exc(), file=sys.stderr)
                    self.recent_file_remove_and_refresh(self.currimg_name)
                    iter = self.thumblist.get_iter((self.curr_img_in_list,))
                    try:
                        self.thumbnail_loaded.pop(self.curr_img_in_list)
                        self.thumbpane_update_images()
                    except:
                        print(traceback.format_exc(), file=sys.stderr)
                    self.thumblist.remove(iter)
                    templist = self.image_list
                    self.image_list = []
                    for item in templist:
                        if item != self.currimg_name:
                            self.image_list.append(item)
                    if len(self.image_list) >= 1:
                        if len(self.image_list) == 1:
                            self.curr_img_in_list = 0
                        elif self.curr_img_in_list == len(self.image_list):
                            self.curr_img_in_list -= 1
                        self.change_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
                        self.preloadimg_prev_in_list = -1
                        self.preloadimg_next_in_list = -1
                        self.load_when_idle = GLib.idle_add(
                            self.load_new_image, False, False, True, True, True, True
                        )
                        self.set_go_navigation_sensitivities(False)
                    else:
                        self.imageview.clear()
                        self.update_title()
                        self.statusbar.push(self.statusbar.get_context_id(""), "")
                        self.image_loaded = False
                        self.set_slideshow_sensitivities()
                        self.set_image_sensitivities(False)
                        self.set_go_navigation_sensitivities(False)
                    # Select new item:
                    self.thumbpane_select(self.curr_img_in_list)
                except:
                    print(traceback.format_exc(), file=sys.stderr)
                    error_dialog = Gtk.MessageDialog(
                        self.window,
                        Gtk.DialogFlags.MODAL,
                        Gtk.MessageType.WARNING,
                        Gtk.ButtonsType.OK,
                        _("Unable to delete %s") % self.currimg_name,
                    )
                    error_dialog.set_title(_("Unable to delete"))
                    error_dialog.run()
                    error_dialog.destroy()
            delete_dialog.destroy()
            if temp_slideshow_mode:
                self.toggle_slideshow(None, None, None)

    def defaultdir_clicked(self, button):
        getdir = Gtk.FileChooserDialog(
            title=_("Choose directory"), action=Gtk.FileChooserAction.OPEN
        )
        getdir.add_buttons(
            Gtk.STOCK_CANCEL,
            Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN,
            Gtk.ResponseType.OK,
        )
        getdir.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
        getdir.set_filename(self.fixed_dir)
        getdir.set_default_response(Gtk.ResponseType.OK)
        response = getdir.run()
        if response == Gtk.ResponseType.OK:
            self.fixed_dir = getdir.get_filenames()[0]
            if len(self.fixed_dir) > 25:
                button.set_label("..." + self.fixed_dir[-22:])
            else:
                button.set_label(self.fixed_dir)
            getdir.destroy()
        else:
            getdir.destroy()

    def prefs_tab_switched(self, notebook, page, page_num):
        do_when_idle = GLib.idle_add(self.grab_close_button)

    def grab_close_button(self):
        self.close_button.grab_focus()

    def bgcolor_selected(self, widget):
        # When the user selects a color, store this color in self.bgcolor (which will
        # later be saved to .miragerc) and set this background color:
        self.bgcolor = widget.get_property("color")
        if not self.simple_bgcolor:
            self.fullscreen_controls.modify_bg(Gtk.StateType.NORMAL, self.bgcolor)

    def simple_bgcolor_selected(self, widget):
        if widget.get_active():
            self.simple_bgcolor = True
        else:
            self.simple_bgcolor = False
            self.bgcolor_selected(self.colorbutton)

    def show_about(self, action, parameter, data):
        # Help > About
        self.about_dialog = Gtk.AboutDialog()
        try:
            self.about_dialog.set_transient_for(self.window)
            self.about_dialog.set_modal(True)
        except:
            print(traceback.format_exc(), file=sys.stderr)
        self.about_dialog.set_name("Mirage")
        self.about_dialog.set_version(__version__)
        self.about_dialog.set_comments(_("A fast GTK+ Image Viewer."))
        self.about_dialog.set_license(__license__)
        self.about_dialog.set_authors(
            [
                "Scott Horowitz <stonecrest@gmail.com>",
                "Fredric Johansson <fredric.miscmail@gmail.com>",
                "Thomas Ross <thomasross@thomasross.io>",
            ]
        )
        self.about_dialog.set_artists(["William Rea <sillywilly@gmail.com>"])
        self.about_dialog.set_translator_credits(
            "\n".join(
                [
                    "cs - Petr Pisar <petr.pisar@atlas.cz>",
                    "de - Bjoern Martensen <bjoern.martensen@gmail.com>",
                    "es - Isidro Arribas <cdhotfire@gmail.com>",
                    "fr - Mike Massonnet <mmassonnet@gmail.com>",
                    "fr - Otyugh <otyugh@riseup.net>",
                    "hu - Sandor Lisovszki <lisovszki@dunakanyar.net>",
                    "nl - Pascal De Vuyst <pascal.devuyst@gmail.com>",
                    "pl - Tomasz Dominikowski <dominikowski@gmail.com>",
                    "pt_BR - Danilo Martins <mawkee@gmail.com>",
                    "ru - mavka <mavka@justos.org>",
                    "it - Daniele Maggio <dado84@freemail.it>",
                    "zh_CN - Jayden Suen <no.sun@163.com>",
                ]
            )
        )
        self.about_dialog.set_website("https://gitlab.com/thomasross/mirage")
        icon_path = self.find_path("mirage.png")
        try:
            icon_pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon_path)
            self.about_dialog.set_logo(icon_pixbuf)
        except:
            print(traceback.format_exc(), file=sys.stderr)
        self.about_dialog.connect("response", self.close_about)
        self.about_dialog.show_all()

    def show_website(self, dialog, blah, link):
        self.browser_load(link)

    def browser_load(self, docslink):
        try:
            pid = subprocess.Popen(["gnome-open", docslink]).pid
        except:
            try:
                pid = subprocess.Popen(["exo-open", docslink]).pid
            except:
                try:
                    pid = subprocess.Popen(["kfmclient", "openURL", docslink]).pid
                except:
                    try:
                        pid = subprocess.Popen(["firefox", docslink]).pid
                    except:
                        try:
                            pid = subprocess.Popen(["mozilla", docslink]).pid
                        except:
                            try:
                                pid = subprocess.Popen(["opera", docslink]).pid
                            except:
                                error_dialog = Gtk.MessageDialog(
                                    self.window,
                                    Gtk.DialogFlags.MODAL,
                                    Gtk.MessageType.WARNING,
                                    Gtk.ButtonsType.CLOSE,
                                    _("Unable to launch a suitable browser."),
                                )
                                error_dialog.run()
                                error_dialog.destroy()

    def close_about(self, event, data=None):
        self.about_dialog.hide()
        return True

    def mousewheel_scrolled(self, widget, event):
        if event.type == Gdk.EventType.SCROLL:
            direction = 0
            if event.direction == Gdk.ScrollDirection.UP:
                direction = 1
            elif event.direction == Gdk.ScrollDirection.DOWN:
                direction = -1
            elif event.direction == Gdk.ScrollDirection.SMOOTH:
                __, x_delta, y_delta = event.get_scroll_deltas()
                if y_delta > 0:
                    direction = -1
                elif y_delta < 0:
                    direction = 1

            # Zooming of the image by Ctrl-mousewheel
            if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
                if direction == 1:
                    self.zoom_in(None, None, None)
                elif direction == -1:
                    self.zoom_out(None, None, None)

                return True
            # Navigation of images with mousewheel:
            else:
                if direction == 1:
                    self.goto_prev_image(None, None, None)
                elif direction == -1:
                    self.goto_next_image(None, None, None)

                return True

    def mouse_moved(self, widget, event):
        # This handles the panning of the image
        if event.is_hint:
            x, y, state = self.get_pointer(event.window)
        else:
            state = event.get_state()
        x, y = event.x_root, event.y_root
        if (state & Gdk.ModifierType.BUTTON2_MASK) or (
            state & Gdk.ModifierType.BUTTON1_MASK
        ):
            # Prevent self.expose_event() from potentially further changing the
            # adjustments upon the adjustment value changes
            self.updating_adjustments = True
            xadjust = self.layout.get_hadjustment()
            newx = xadjust.get_value() + (self.prevmousex - x)
            if (
                newx >= xadjust.get_lower()
                and newx <= xadjust.get_upper() - xadjust.get_page_size()
            ):
                xadjust.set_value(newx)
                self.layout.set_hadjustment(xadjust)
            yadjust = self.layout.get_vadjustment()
            newy = yadjust.get_value() + (self.prevmousey - y)
            if (
                newy >= yadjust.get_lower()
                and newy <= yadjust.get_upper() - yadjust.get_page_size()
            ):
                yadjust.set_value(newy)
                self.layout.set_vadjustment(yadjust)
            self.updating_adjustments = False
        self.prevmousex = x
        self.prevmousey = y
        if self.fullscreen_mode:
            # Show cursor on movement, then hide after 2 seconds of no movement
            self.change_cursor(None)
            if not self.slideshow_controls_visible:
                GLib.source_remove(self.timer_id)
                if not self.closing_app:
                    while Gtk.events_pending():
                        Gtk.main_iteration()
                self.timer_id = GLib.timeout_add(2000, self.hide_cursor)
            if y > 0.9 * self.available_image_height():
                self.slideshow_controls_show()
            else:
                self.slideshow_controls_hide()
        return True

    def button_pressed(self, widget, event):
        if self.image_loaded:
            # Changes the cursor to the 'resize' cursor, like GIMP, on a middle click:
            if (event.button == 2 or event.button == 1) and (
                self.hscroll.get_property("visible") == True
                or self.vscroll.get_property("visible") == True
            ):
                self.change_cursor(Gdk.Cursor.new(Gdk.CursorType.FLEUR))
                self.prevmousex = event.x_root
                self.prevmousey = event.y_root
            # Right-click popup:
            elif self.image_loaded and event.button == 3:
                self.popup_menu.popup_at_pointer(event)

        return True

    def button_released(self, widget, event):
        # Resets the cursor when middle mouse button is released
        if event.button == 2 or event.button == 1:
            self.change_cursor(None)
        return True

    def draw_background(self, widget, cr):
        if self.simple_bgcolor:
            return

        allocation = widget.get_allocation()
        cr.set_source_rgb(*self.bgcolor.to_floats())
        cr.paint()

    def zoom_in(self, action, parameter, data):
        if (
            self.currimg_name != ""
            and self.action_group.lookup_action("zoom-in").get_enabled()
        ):
            self.image_zoomed = True
            self.currimg_zoomratio = self.currimg_zoomratio * 1.25
            self.set_zoom_sensitivities()
            self.last_image_action_was_fit = False
            self.put_zoom_image_to_window(False)
            self.update_statusbar()

    def zoom_out(self, action, parameter, data):
        if (
            self.currimg_name != ""
            and self.action_group.lookup_action("zoom-out").get_enabled()
        ):
            if self.currimg_zoomratio == self.min_zoomratio:
                # No point in proceeding..
                return
            self.image_zoomed = True
            self.currimg_zoomratio = self.currimg_zoomratio * 1 / 1.25
            if self.currimg_zoomratio < self.min_zoomratio:
                self.currimg_zoomratio = self.min_zoomratio
            self.set_zoom_sensitivities()
            self.last_image_action_was_fit = False
            self.put_zoom_image_to_window(False)
            self.update_statusbar()

    def zoom_to_fit_window_action(self, action, parameter, data):
        self.zoom_to_fit_window(action, False, False)

    def zoom_to_fit_window(self, action, is_preloadimg_next, is_preloadimg_prev):
        if is_preloadimg_next:
            if self.preloading_images and self.preloadimg_next_in_list != -1:
                win_width = self.available_image_width()
                win_height = self.available_image_height()
                preimg_width = self.preloadimg_next_pixbuf_original.get_width()
                preimg_height = self.preloadimg_next_pixbuf_original.get_height()
                prewidth_ratio = float(preimg_width) / win_width
                preheight_ratio = float(preimg_height) / win_height
                if prewidth_ratio < preheight_ratio:
                    premax_ratio = preheight_ratio
                else:
                    premax_ratio = prewidth_ratio
                self.preloadimg_next_zoomratio = 1 / float(max_ratio)
        elif is_preloadimg_prev:
            if self.preloading_images and self.preloadimg_prev_in_list != -1:
                win_width = self.available_image_width()
                win_height = self.available_image_height()
                preimg_width = self.preloadimg_prev_pixbuf_original.get_width()
                preimg_height = self.preloadimg_prev_pixbuf_original.get_height()
                prewidth_ratio = float(preimg_width) / win_width
                preheight_ratio = float(preimg_height) / win_height
                if prewidth_ratio < preheight_ratio:
                    premax_ratio = preheight_ratio
                else:
                    premax_ratio = prewidth_ratio
                self.preloadimg_prev_zoomratio = 1 / float(max_ratio)
        else:
            if self.currimg_name != "" and (
                self.slideshow_mode
                or self.action_group.lookup_action("zoom-to-fit-window").get_enabled()
            ):
                self.image_zoomed = True
                self.last_mode = self.open_mode_fit
                self.last_image_action_was_fit = True
                self.last_image_action_was_smart_fit = False
                # Calculate zoomratio needed to fit to window:
                win_width = self.available_image_width()
                win_height = self.available_image_height()
                img_width = self.currimg_pixbuf_original.get_width()
                img_height = self.currimg_pixbuf_original.get_height()
                width_ratio = float(img_width) / win_width
                height_ratio = float(img_height) / win_height
                if width_ratio < height_ratio:
                    max_ratio = height_ratio
                else:
                    max_ratio = width_ratio
                self.currimg_zoomratio = 1 / float(max_ratio)
                self.set_zoom_sensitivities()
                self.put_zoom_image_to_window(False)
                self.update_statusbar()

    def zoom_to_fit_or_1_to_1(self, action, is_preloadimg_next, is_preloadimg_prev):
        if is_preloadimg_next:
            if self.preloading_images and self.preloadimg_next_in_list != -1:
                win_width = self.available_image_width()
                win_height = self.available_image_height()
                preimg_width = self.preloadimg_next_pixbuf_original.get_width()
                preimg_height = self.preloadimg_next_pixbuf_original.get_height()
                prewidth_ratio = float(preimg_width) / win_width
                preheight_ratio = float(preimg_height) / win_height
                if prewidth_ratio < preheight_ratio:
                    premax_ratio = preheight_ratio
                else:
                    premax_ratio = prewidth_ratio
                self.preloadimg_next_zoomratio = 1 / float(premax_ratio)
                if self.preloadimg_next_zoomratio > 1:
                    self.preloadimg_next_zoomratio = 1
        elif is_preloadimg_prev:
            if self.preloading_images and self.preloadimg_prev_in_list != -1:
                win_width = self.available_image_width()
                win_height = self.available_image_height()
                preimg_width = self.preloadimg_prev_pixbuf_original.get_width()
                preimg_height = self.preloadimg_prev_pixbuf_original.get_height()
                prewidth_ratio = float(preimg_width) / win_width
                preheight_ratio = float(preimg_height) / win_height
                if prewidth_ratio < preheight_ratio:
                    premax_ratio = preheight_ratio
                else:
                    premax_ratio = prewidth_ratio
                self.preloadimg_prev_zoomratio = 1 / float(premax_ratio)
                if self.preloadimg_prev_zoomratio > 1:
                    self.preloadimg_prev_zoomratio = 1
        else:
            if self.currimg_name != "":
                self.image_zoomed = True
                # Calculate zoomratio needed to fit to window:
                win_width = self.available_image_width()
                win_height = self.available_image_height()
                img_width = self.currimg_pixbuf_original.get_width()
                img_height = self.currimg_pixbuf_original.get_height()
                width_ratio = float(img_width) / win_width
                height_ratio = float(img_height) / win_height
                if width_ratio < height_ratio:
                    max_ratio = height_ratio
                else:
                    max_ratio = width_ratio
                self.currimg_zoomratio = 1 / float(max_ratio)
                self.set_zoom_sensitivities()
                if self.currimg_zoomratio > 1:
                    # Revert to 1:1 zoom
                    self.zoom_1_to_1(action, False, False)
                else:
                    self.put_zoom_image_to_window(False)
                    self.update_statusbar()
                self.last_image_action_was_fit = True
                self.last_image_action_was_smart_fit = True

    def zoom_1_to_1_action(self, action, parameter, data):
        self.zoom_1_to_1(action, False, False)

    def zoom_1_to_1(self, action, is_preloadimg_next, is_preloadimg_prev):
        if is_preloadimg_next:
            if self.preloading_images:
                self.preloadimg_next_zoomratio = 1
        elif is_preloadimg_prev:
            if self.preloading_images:
                self.preloadimg_prev_zoomratio = 1
        else:
            if self.currimg_name != "" and (
                self.slideshow_mode
                or self.currimg_is_animation
                or (
                    not self.currimg_is_animation
                    and self.action_group.lookup_action("zoom-1-to-1").get_enabled()
                )
            ):
                self.image_zoomed = True
                self.last_mode = self.open_mode_1to1
                self.last_image_action_was_fit = False
                self.currimg_zoomratio = 1
                self.put_zoom_image_to_window(False)
                self.update_statusbar()

    def rotate_left(self, action, parameter, data):
        if self.action_group.lookup_action("rotate-left").get_enabled():
            self.rotate_left_or_right(90)

    def rotate_right(self, action, parameter, data):
        if self.action_group.lookup_action("rotate-right").get_enabled():
            self.rotate_left_or_right(270)

    def rotate_left_or_right(self, angle):
        if self.currimg_name != "":
            self.currimg_pixbuf_original = self.image_rotate(
                self.currimg_pixbuf_original, angle
            )
            if self.last_image_action_was_fit:
                if self.last_image_action_was_smart_fit:
                    self.zoom_to_fit_or_1_to_1(None, False, False)
                else:
                    self.zoom_to_fit_window(None, False, False)
            else:
                self.currimg_width, self.currimg_height = (
                    self.currimg_height,
                    self.currimg_width,
                )
                self.layout.set_size(self.currimg_width, self.currimg_height)
                self.currimg_pixbuf = self.image_rotate(self.currimg_pixbuf, angle)
                self.imageview.set_from_pixbuf(self.currimg_pixbuf)
                self.show_scrollbars_if_needed()
                self.center_image()
                self.update_statusbar()
            self.image_modified = True

    def flip_image_vert(self, action, parameter, data):
        if self.action_group.lookup_action("flip-image-vert").get_enabled():
            self.flip_image_vert_or_horiz(True)

    def flip_image_horiz(self, action, parameter, data):
        if self.action_group.lookup_action("flip-image-horiz").get_enabled():
            self.flip_image_vert_or_horiz(False)

    def flip_image_vert_or_horiz(self, vertical):
        if self.currimg_name != "":
            self.currimg_pixbuf = self.image_flip(self.currimg_pixbuf, vertical)
            self.currimg_pixbuf_original = self.image_flip(
                self.currimg_pixbuf_original, vertical
            )
            self.imageview.set_from_pixbuf(self.currimg_pixbuf)
            self.image_modified = True

    def get_pixbuf_of_size(self, pixbuf, size, zoom_quality):
        # Creates a pixbuf that fits in the specified square of sizexsize
        # while preserving the aspect ratio
        # Returns tuple: (scaled_pixbuf, actual_width, actual_height)
        image_width = pixbuf.get_width()
        image_height = pixbuf.get_height()
        if image_width - size > image_height - size:
            if image_width > size:
                image_height = int(size / float(image_width) * image_height)
                image_width = size
        else:
            if image_height > size:
                image_width = int(size / float(image_height) * image_width)
                image_height = size
        if not pixbuf.get_has_alpha():
            crop_pixbuf = pixbuf.scale_simple(image_width, image_height, zoom_quality)
        else:
            crop_pixbuf = pixbuf.composite_color_simple(
                image_width, image_height, zoom_quality, 255, 8, 0x666666, 0x999999,
            )
        return (crop_pixbuf, image_width, image_height)

    def pixbuf_add_border(self, pix):
        # Add a gray outline to pix. This will increase the pixbuf size by
        # 2 pixels lengthwise and heightwise, 1 on each side. Returns pixbuf.
        try:
            width = pix.get_width()
            height = pix.get_height()
            newpix = GdkPixbuf.Pixbuf.new(
                GdkPixbuf.Colorspace.RGB, True, 8, width + 2, height + 2
            )
            newpix.fill(0x858585FF)
            pix.copy_area(0, 0, width, height, newpix, 1, 1)
            return newpix
        except:
            print(traceback.format_exc(), file=sys.stderr)
            return pix

    def crop_image(self, action, parameter, data):
        dialog = Gtk.Dialog(_("Crop Image"), self.window, Gtk.DialogFlags.MODAL,)
        dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
        cropbutton = dialog.add_button(_("C_rop"), Gtk.ResponseType.ACCEPT)
        cropimage = Gtk.Image.new()
        cropimage.set_from_stock(Gtk.STOCK_OK, Gtk.IconSize.BUTTON)
        cropbutton.set_image(cropimage)
        image = Gtk.DrawingArea.new()
        crop_pixbuf, image_width, image_height = self.get_pixbuf_of_size(
            self.currimg_pixbuf_original, 400, self.zoom_quality
        )
        image.set_size_request(image_width, image_height)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox.pack_start(Gtk.Label.new(""), True, True, 0)
        hbox.pack_start(image, False, True, 0)
        hbox.pack_start(Gtk.Label.new(""), True, True, 0)
        vbox_left = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        x_adj = Gtk.Adjustment.new(
            0, 0, self.currimg_pixbuf_original.get_width(), 1, 10, 0
        )
        x = Gtk.SpinButton.new(x_adj, 0, 0)
        x.set_numeric(True)
        x.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
        x.set_wrap(False)
        x_label = Gtk.Label.new("X:")
        x_label.set_alignment(0, 0.7)
        y_adj = Gtk.Adjustment.new(
            0, 0, self.currimg_pixbuf_original.get_height(), 1, 10, 0
        )
        y = Gtk.SpinButton.new(y_adj, 0, 0)
        y.set_numeric(True)
        y.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
        y.set_wrap(False)
        y_label = Gtk.Label.new("Y:")
        x_label.set_size_request(y_label.size_request().width, -1)
        hbox_x = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_y = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_x.pack_start(x_label, False, False, 10)
        hbox_x.pack_start(x, False, False, 0)
        hbox_x.pack_start(Gtk.Label.new(""), False, False, 3)
        hbox_y.pack_start(y_label, False, False, 10)
        hbox_y.pack_start(y, False, False, 0)
        hbox_y.pack_start(Gtk.Label.new(""), False, False, 3)
        vbox_left.pack_start(hbox_x, False, False, 0)
        vbox_left.pack_start(hbox_y, False, False, 0)
        vbox_right = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        width_adj = Gtk.Adjustment.new(
            self.currimg_pixbuf_original.get_width(),
            1,
            self.currimg_pixbuf_original.get_width(),
            1,
            10,
            0,
        )
        width = Gtk.SpinButton.new(width_adj, 0, 0)
        width.set_numeric(True)
        width.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
        width.set_wrap(False)
        width_label = Gtk.Label.new(_("Width:"))
        width_label.set_alignment(0, 0.7)
        height_adj = Gtk.Adjustment.new(
            self.currimg_pixbuf_original.get_height(),
            1,
            self.currimg_pixbuf_original.get_height(),
            1,
            10,
            0,
        )
        height = Gtk.SpinButton.new(height_adj, 0, 0)
        height.set_numeric(True)
        height.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
        height.set_wrap(False)
        height_label = Gtk.Label.new(_("Height:"))
        width_label.set_size_request(height_label.size_request().width, -1)
        height_label.set_alignment(0, 0.7)
        hbox_width = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_height = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox_width.pack_start(width_label, False, False, 10)
        hbox_width.pack_start(width, False, False, 0)
        hbox_height.pack_start(height_label, False, False, 10)
        hbox_height.pack_start(height, False, False, 0)
        vbox_right.pack_start(hbox_width, False, False, 0)
        vbox_right.pack_start(hbox_height, False, False, 0)
        hbox2 = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox2.pack_start(Gtk.Label.new(""), True, True, 0)
        hbox2.pack_start(vbox_left, False, False, 0)
        hbox2.pack_start(vbox_right, False, False, 0)
        hbox2.pack_start(Gtk.Label.new(""), True, True, 0)
        dialog.vbox.pack_start(hbox, False, False, 0)
        dialog.vbox.pack_start(hbox2, False, False, 15)
        dialog.set_resizable(False)
        dialog.vbox.show_all()
        image.set_events(
            Gdk.EventMask.POINTER_MOTION_MASK
            | Gdk.EventMask.POINTER_MOTION_HINT_MASK
            | Gdk.EventMask.BUTTON_PRESS_MASK
            | Gdk.EventMask.BUTTON_MOTION_MASK
            | Gdk.EventMask.BUTTON_RELEASE_MASK
        )
        image.connect(
            "draw", self.draw_crop_image, crop_pixbuf, image_width, image_height,
        )
        image.connect(
            "motion-notify-event",
            self.crop_image_mouse_moved,
            0,
            0,
            x,
            y,
            width,
            height,
            image_width,
            image_height,
            width_adj,
            height_adj,
        )
        image.connect("button_press_event", self.crop_image_button_press)
        image.connect("button_release_event", self.crop_image_button_release)
        self.x_changed = x.connect(
            "value-changed",
            self.crop_value_changed,
            x,
            y,
            width,
            height,
            width_adj,
            height_adj,
            image_width,
            image_height,
            image,
            0,
        )
        self.y_changed = y.connect(
            "value-changed",
            self.crop_value_changed,
            x,
            y,
            width,
            height,
            width_adj,
            height_adj,
            image_width,
            image_height,
            image,
            1,
        )
        self.width_changed = width.connect(
            "value-changed",
            self.crop_value_changed,
            x,
            y,
            width,
            height,
            width_adj,
            height_adj,
            image_width,
            image_height,
            image,
            2,
        )
        self.height_changed = height.connect(
            "value-changed",
            self.crop_value_changed,
            x,
            y,
            width,
            height,
            width_adj,
            height_adj,
            image_width,
            image_height,
            image,
            3,
        )
        image.realize()
        self.crop_rectangle = [0, 0]
        self.drawing_crop_rectangle = False
        self.update_rectangle = False
        self.rect = None
        response = dialog.run()
        if response == Gtk.ResponseType.ACCEPT:
            dialog.destroy()
            if self.rect != None:
                temp_pixbuf = GdkPixbuf.Pixbuf.new(
                    GdkPixbuf.Colorspace.RGB,
                    self.currimg_pixbuf_original.get_has_alpha(),
                    8,
                    self.coords[2],
                    self.coords[3],
                )
                self.currimg_pixbuf_original.copy_area(
                    self.coords[0],
                    self.coords[1],
                    self.coords[2],
                    self.coords[3],
                    temp_pixbuf,
                    0,
                    0,
                )
                self.currimg_pixbuf_original = temp_pixbuf
                del temp_pixbuf
                gc.collect()
                self.load_new_image2(False, True, False, False)
                self.image_modified = True
        else:
            dialog.destroy()

    def crop_value_changed(
        self,
        currspinbox,
        x,
        y,
        width,
        height,
        width_adj,
        height_adj,
        image_width,
        image_height,
        image,
        type,
    ):
        if type == 0:  # X
            if (
                x.get_value() + width.get_value()
                > self.currimg_pixbuf_original.get_width()
            ):
                width.handler_block(self.width_changed)
                width.set_value(
                    self.currimg_pixbuf_original.get_width() - x.get_value()
                )
                width.handler_unblock(self.width_changed)
        elif type == 1:  # Y
            if (
                y.get_value() + height.get_value()
                > self.currimg_pixbuf_original.get_height()
            ):
                height.handler_block(self.height_changed)
                height.set_value(
                    self.currimg_pixbuf_original.get_height() - y.get_value()
                )
                height.handler_unblock(self.height_changed)
        self.coords = [
            int(x.get_value()),
            int(y.get_value()),
            int(width.get_value()),
            int(height.get_value()),
        ]
        self.crop_rectangle[0] = int(
            round(
                float(self.coords[0])
                / self.currimg_pixbuf_original.get_width()
                * image_width,
                0,
            )
        )
        self.crop_rectangle[1] = int(
            round(
                float(self.coords[1])
                / self.currimg_pixbuf_original.get_height()
                * image_height,
                0,
            )
        )
        x2 = (
            int(
                round(
                    float(self.coords[2])
                    / self.currimg_pixbuf_original.get_width()
                    * image_width,
                    0,
                )
            )
            + self.crop_rectangle[0]
        )
        y2 = (
            int(
                round(
                    float(self.coords[3])
                    / self.currimg_pixbuf_original.get_height()
                    * image_height,
                    0,
                )
            )
            + self.crop_rectangle[1]
        )
        self.drawing_crop_rectangle = True
        self.update_rectangle = True
        self.crop_image_mouse_moved(
            image,
            None,
            x2,
            y2,
            x,
            y,
            width,
            height,
            image_width,
            image_height,
            width_adj,
            height_adj,
        )
        self.update_rectangle = False
        self.drawing_crop_rectangle = False

    def draw_crop_image(self, widget, cr, pixbuf, width, height):
        Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0)
        cr.paint()

        if self.rect is not None:
            cr.set_source_rgb(1, 1, 1)
            cr.set_operator(cairo.Operator.DIFFERENCE)
            cr.rectangle(self.rect[0], self.rect[1], self.rect[2], self.rect[3])
            cr.stroke()

    def crop_image_mouse_moved(
        self,
        widget,
        event,
        x2,
        y2,
        x,
        y,
        width,
        height,
        image_width,
        image_height,
        width_adj,
        height_adj,
    ):
        if event != None:
            x2, y2, state = self.get_pointer(event.window)
        if self.drawing_crop_rectangle:
            if self.crop_rectangle != None or self.update_rectangle:
                self.rect = [0, 0, 0, 0]
                if self.crop_rectangle[0] > x2:
                    self.rect[0] = x2
                    self.rect[2] = self.crop_rectangle[0] - x2
                else:
                    self.rect[0] = self.crop_rectangle[0]
                    self.rect[2] = x2 - self.crop_rectangle[0]
                if self.crop_rectangle[1] > y2:
                    self.rect[1] = y2
                    self.rect[3] = self.crop_rectangle[1] - y2
                else:
                    self.rect[1] = self.crop_rectangle[1]
                    self.rect[3] = y2 - self.crop_rectangle[1]

                region = cairo.Region(
                    cairo.RectangleInt(0, 0, image_width, image_height)
                )
                window = widget.get_window()
                drawing_context = window.begin_draw_frame(region)
                cr = drawing_context.get_cairo_context()
                widget.draw(cr)
                window.end_draw_frame(drawing_context)

                # Convert the rectangle coordinates of the current image
                # to coordinates of pixbuf_original
                if self.rect[0] < 0:
                    self.rect[2] = self.rect[2] + self.rect[0]
                    self.rect[0] = 0
                if self.rect[1] < 0:
                    self.rect[3] = self.rect[3] + self.rect[1]
                    self.rect[1] = 0
                if event != None:
                    self.coords = [0, 0, 0, 0]
                    self.coords[0] = int(
                        round(
                            float(self.rect[0])
                            / image_width
                            * self.currimg_pixbuf_original.get_width(),
                            0,
                        )
                    )
                    self.coords[1] = int(
                        round(
                            float(self.rect[1])
                            / image_height
                            * self.currimg_pixbuf_original.get_height(),
                            0,
                        )
                    )
                    self.coords[2] = int(
                        round(
                            float(self.rect[2])
                            / image_width
                            * self.currimg_pixbuf_original.get_width(),
                            0,
                        )
                    )
                    self.coords[3] = int(
                        round(
                            float(self.rect[3])
                            / image_height
                            * self.currimg_pixbuf_original.get_height(),
                            0,
                        )
                    )
                    if (
                        self.coords[0] + self.coords[2]
                        > self.currimg_pixbuf_original.get_width()
                    ):
                        self.coords[2] = (
                            self.currimg_pixbuf_original.get_width() - self.coords[0]
                        )
                    if (
                        self.coords[1] + self.coords[3]
                        > self.currimg_pixbuf_original.get_height()
                    ):
                        self.coords[3] = (
                            self.currimg_pixbuf_original.get_height() - self.coords[1]
                        )
                x.handler_block(self.x_changed)
                y.handler_block(self.y_changed)
                width.handler_block(self.width_changed)
                height.handler_block(self.height_changed)
                x.set_value(self.coords[0])
                y.set_value(self.coords[1])
                width.set_value(self.coords[2])
                height.set_value(self.coords[3])
                x.handler_unblock(self.x_changed)
                y.handler_unblock(self.y_changed)
                width_adj.set_property(
                    "upper", self.currimg_pixbuf_original.get_width() - self.coords[0]
                )
                height_adj.set_property(
                    "upper", self.currimg_pixbuf_original.get_height() - self.coords[1]
                )
                width.handler_unblock(self.width_changed)
                height.handler_unblock(self.height_changed)

    def crop_image_button_press(self, widget, event):
        x, y, state = self.get_pointer(event.window)
        if state & Gdk.ModifierType.BUTTON1_MASK:
            self.drawing_crop_rectangle = True
            self.crop_rectangle = [x, y]
            if self.rect != None:
                region = cairo.Region(
                    cairo.RectangleInt(
                        self.rect[0], self.rect[1], self.rect[2], self.rect[3]
                    )
                )
                window = widget.get_window()
                drawing_context = window.begin_draw_frame(region)
                cr = drawing_context.get_cairo_context()
                widget.draw(cr)
                window.end_draw_frame(drawing_context)
                self.rect = None

    def crop_image_button_release(self, widget, event):
        x, y, state = self.get_pointer(event.window)
        if not (state & Gdk.ModifierType.BUTTON1_MASK):
            self.drawing_crop_rectangle = False

    def saturation(self, action, parameter, data):
        dialog = Gtk.Dialog(_("Saturation"), self.window, Gtk.DialogFlags.MODAL,)
        dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
        resizebutton = dialog.add_button(_("_Saturate"), Gtk.ResponseType.ACCEPT)
        resizeimage = Gtk.Image.new()
        resizeimage.set_from_stock(Gtk.STOCK_OK, Gtk.IconSize.BUTTON)
        resizebutton.set_image(resizeimage)
        scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 2, 0.1)
        scale.set_draw_value(False)
        scale.set_increments(0.1, 0.5)
        scale.set_value(1)
        scale.connect("value-changed", self.saturation_preview)
        label = Gtk.Label.new(_("Saturation level:"))
        label.set_alignment(0, 0.5)
        hbox1 = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox1.pack_start(label, True, True, 10)
        hbox2 = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox2.pack_start(scale, True, True, 20)
        dialog.vbox.pack_start(Gtk.Label.new(" "), True, True, 0)
        dialog.vbox.pack_start(hbox1, False, True, 0)
        dialog.vbox.pack_start(hbox2, True, True, 10)
        dialog.vbox.pack_start(Gtk.Label.new(" "), True, True, 0)
        dialog.set_default_response(Gtk.ResponseType.ACCEPT)
        dialog.vbox.show_all()
        response = dialog.run()
        if response == Gtk.ResponseType.ACCEPT:
            self.currimg_pixbuf_original.saturate_and_pixelate(
                self.currimg_pixbuf_original, scale.get_value(), False
            )
            self.currimg_pixbuf.saturate_and_pixelate(
                self.currimg_pixbuf, scale.get_value(), False
            )
            self.imageview.set_from_pixbuf(self.currimg_pixbuf)
            self.image_modified = True
            dialog.destroy()
        else:
            self.imageview.set_from_pixbuf(self.currimg_pixbuf)
            dialog.destroy()

    def saturation_preview(self, range):
        while Gtk.events_pending():
            Gtk.main_iteration()
        try:
            bak = self.currimg_pixbuf.copy()
            self.currimg_pixbuf.saturate_and_pixelate(
                self.currimg_pixbuf, range.get_value(), False
            )
            self.imageview.set_from_pixbuf(self.currimg_pixbuf)
            self.currimg_pixbuf = bak.copy()
            del bak
        except:
            print(traceback.format_exc(), file=sys.stderr)
        gc.collect()

    def resize_image(self, action, parameter, data):
        dialog = Gtk.Dialog(_("Resize Image"), self.window, Gtk.DialogFlags.MODAL)
        dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
        resizebutton = dialog.add_button(_("_Resize"), Gtk.ResponseType.ACCEPT)
        resizeimage = Gtk.Image.new()
        resizeimage.set_from_stock(Gtk.STOCK_OK, Gtk.IconSize.BUTTON)
        resizebutton.set_image(resizeimage)
        hbox_width = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        width_adj = Gtk.Adjustment.new(
            self.currimg_pixbuf_original.get_width(), 1, 100000000000, 1, 10, 0
        )
        width = Gtk.SpinButton.new(width_adj, 0, 0)
        width.set_numeric(True)
        width.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
        width.set_wrap(False)
        width_label = Gtk.Label.new(_("Width:"))
        width_label.set_alignment(0, 0.7)
        hbox_width.pack_start(width_label, False, False, 10)
        hbox_width.pack_start(width, False, False, 0)
        hbox_width.pack_start(Gtk.Label.new(_("pixels")), False, False, 10)
        hbox_height = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        height_adj = Gtk.Adjustment.new(
            self.currimg_pixbuf_original.get_height(), 1, 100000000000, 1, 10, 0
        )
        height = Gtk.SpinButton.new(height_adj, 0, 0)
        height.set_numeric(True)
        height.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
        height.set_wrap(False)
        height_label = Gtk.Label.new(_("Height:"))
        width_label.set_size_request(height_label.size_request().width, -1)
        height_label.set_alignment(0, 0.7)
        hbox_height.pack_start(height_label, False, False, 10)
        hbox_height.pack_start(height, False, False, 0)
        hbox_height.pack_start(Gtk.Label.new(_("pixels")), False, False, 10)
        hbox_aspect = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        aspect_checkbox = Gtk.CheckButton.new_with_label(_("Preserve aspect ratio"))
        aspect_checkbox.set_active(self.preserve_aspect)
        hbox_aspect.pack_start(aspect_checkbox, False, False, 10)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(Gtk.Label.new(""), False, False, 0)
        vbox.pack_start(hbox_width, False, False, 0)
        vbox.pack_start(hbox_height, False, False, 0)
        vbox.pack_start(Gtk.Label.new(""), False, False, 0)
        vbox.pack_start(hbox_aspect, False, False, 0)
        vbox.pack_start(Gtk.Label.new(""), False, False, 0)
        hbox_total = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        animtest = GdkPixbuf.PixbufAnimation.new_from_file(self.currimg_name)
        if animtest.is_static_image():
            pixbuf, image_width, image_height = self.get_pixbuf_of_size(
                self.currimg_pixbuf_original, 96, self.zoom_quality
            )
        else:
            pixbuf, image_width, image_height = self.get_pixbuf_of_size(
                animtest.get_static_image(), 96, self.zoom_quality
            )
        image = Gtk.Image.new()
        image.set_from_pixbuf(self.pixbuf_add_border(pixbuf))
        hbox_total.pack_start(image, False, False, 10)
        hbox_total.pack_start(vbox, False, False, 10)
        dialog.vbox.pack_start(hbox_total, False, False, 0)
        width.connect("value-changed", self.preserve_image_aspect, "width", height)
        height.connect("value-changed", self.preserve_image_aspect, "height", width)
        aspect_checkbox.connect("toggled", self.aspect_ratio_toggled, width, height)
        dialog.set_default_response(Gtk.ResponseType.ACCEPT)
        dialog.vbox.show_all()
        response = dialog.run()
        if response == Gtk.ResponseType.ACCEPT:
            pixelheight = height.get_value_as_int()
            pixelwidth = width.get_value_as_int()
            dialog.destroy()
            self.currimg_pixbuf_original = self.currimg_pixbuf_original.scale_simple(
                pixelwidth, pixelheight, self.zoom_quality
            )
            self.load_new_image2(False, True, False, False)
            self.image_modified = True
        else:
            dialog.destroy()

    def aspect_ratio_toggled(self, togglebutton, width, height):
        self.preserve_aspect = togglebutton.get_active()
        if self.preserve_aspect:
            # Set height based on width and aspect ratio
            target_value = (
                float(width.get_value_as_int())
                / self.currimg_pixbuf_original.get_width()
            )
            target_value = int(target_value * self.currimg_pixbuf_original.get_height())
            self.ignore_preserve_aspect_callback = True
            height.set_value(target_value)
            self.ignore_preserve_aspect_callback = False

    def preserve_image_aspect(self, currspinbox, type, otherspinbox):
        if not self.preserve_aspect:
            return
        if self.ignore_preserve_aspect_callback:
            return
        if type == "width":
            target_value = (
                float(currspinbox.get_value_as_int())
                / self.currimg_pixbuf_original.get_width()
            )
            target_value = int(target_value * self.currimg_pixbuf_original.get_height())
        else:
            target_value = (
                float(currspinbox.get_value_as_int())
                / self.currimg_pixbuf_original.get_height()
            )
            target_value = int(target_value * self.currimg_pixbuf_original.get_width())
        self.ignore_preserve_aspect_callback = True
        otherspinbox.set_value(target_value)
        self.ignore_preserve_aspect_callback = False

    def goto_prev_image(self, action, parameter, data):
        self.goto_image("PREV", data)

    def goto_next_image(self, action, parameter, data):
        self.goto_image("NEXT", data)

    def goto_random_image(self, action, parameter, data):
        self.goto_image("RANDOM", data)

    def goto_first_image(self, action, parameter, data):
        self.goto_image("FIRST", data)

    def goto_last_image(self, action, parameter, data):
        self.goto_image("LAST", data)

    def goto_image(self, location, action):
        # location can be "LAST", "FIRST", "NEXT", "PREV", "RANDOM", or a number
        if self.slideshow_mode and action != "ss":
            GLib.source_remove(self.timer_delay)
        if (
            (
                (location == "PREV" or location == "NEXT" or location == "RANDOM")
                and len(self.image_list) > 1
            )
            or (
                location == "FIRST"
                and (len(self.image_list) > 1 and self.curr_img_in_list != 0)
            )
            or (
                location == "LAST"
                and (
                    len(self.image_list) > 1
                    and self.curr_img_in_list != len(self.image_list) - 1
                )
            )
            or valid_int(location)
        ):
            self.load_new_image_stop_now()
            cancel = self.autosave_image()
            if cancel:
                return
            check_wrap = False
            if location != "RANDOM":
                self.randomlist = []
            if location == "FIRST":
                self.curr_img_in_list = 0
            elif location == "RANDOM":
                if self.randomlist == []:
                    self.reinitialize_randomlist()
                else:
                    # check if we have seen every image; if so, reinitialize array and repeat:
                    all_items_are_true = True
                    for item in self.randomlist:
                        if not item:
                            all_items_are_true = False
                    if all_items_are_true:
                        if not self.slideshow_mode or (
                            self.slideshow_mode and self.listwrap_mode == 1
                        ):
                            self.reinitialize_randomlist()
                        else:
                            check_wrap = True
            elif location == "LAST":
                self.curr_img_in_list = len(self.image_list) - 1
            elif location == "PREV":
                if self.curr_img_in_list > 0:
                    self.curr_img_in_list -= 1
                else:
                    check_wrap = True
            elif location == "NEXT":
                if self.curr_img_in_list < len(self.image_list) - 1:
                    self.curr_img_in_list += 1
                else:
                    check_wrap = True
            if check_wrap:
                if self.listwrap_mode == 0:
                    if location == "NEXT":
                        if self.slideshow_mode:
                            self.toggle_slideshow(None, None, None)
                    return
                elif (
                    location == "PREV" or location == "NEXT"
                ) and self.listwrap_mode == 1:
                    if location == "PREV":
                        self.curr_img_in_list = len(self.image_list) - 1
                    elif location == "NEXT":
                        self.curr_img_in_list = 0
                else:
                    if self.curr_img_in_list != self.loaded_img_in_list:
                        # Ensure that the user is looking at the correct "last" image before
                        # they are asked the wrap question:
                        if location == "PREV":
                            self.load_new_image(True, False, True, True, True, True)
                        else:
                            self.load_new_image(False, False, True, True, True, True)
                        self.set_go_navigation_sensitivities(False)
                        self.thumbpane_select(self.curr_img_in_list)
                    if self.fullscreen_mode:
                        self.change_cursor(None)
                    if location == "PREV":
                        dialog = Gtk.MessageDialog(
                            self.window,
                            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                            Gtk.MessageType.QUESTION,
                            Gtk.ButtonsType.YES_NO,
                            _(
                                "You are viewing the first image in the list. Wrap around to the last image?"
                            ),
                        )
                    elif location == "NEXT":
                        dialog = Gtk.MessageDialog(
                            self.window,
                            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                            Gtk.MessageType.QUESTION,
                            Gtk.ButtonsType.YES_NO,
                            _(
                                "You are viewing the last image in the list. Wrap around to the first image?"
                            ),
                        )
                    elif location == "RANDOM":
                        dialog = Gtk.MessageDialog(
                            self.window,
                            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                            Gtk.MessageType.QUESTION,
                            Gtk.ButtonsType.YES_NO,
                            _(
                                "All images have been viewed. Would you like to cycle through the images again?"
                            ),
                        )
                    dialog.set_title(_("Wrap?"))
                    dialog.label.set_property("can-focus", False)
                    dialog.set_default_response(Gtk.ResponseType.YES)
                    self.user_prompt_visible = True
                    response = dialog.run()
                    if response == Gtk.ResponseType.YES:
                        if location == "PREV":
                            self.curr_img_in_list = len(self.image_list) - 1
                        elif location == "NEXT":
                            self.curr_img_in_list = 0
                        elif location == "RANDOM":
                            self.reinitialize_randomlist()
                        dialog.destroy()
                        self.user_prompt_visible = False
                        if self.fullscreen_mode:
                            self.hide_cursor
                    else:
                        dialog.destroy()
                        self.user_prompt_visible = False
                        if self.fullscreen_mode:
                            self.hide_cursor
                        else:
                            self.change_cursor(None)
                        if self.slideshow_mode:
                            self.toggle_slideshow(None, None, None)
                        return
            if location == "RANDOM":
                # Find random image that hasn't already been chosen:
                j = random.randint(0, len(self.image_list) - 1)
                while self.randomlist[j]:
                    j = random.randint(0, len(self.image_list) - 1)
                self.curr_img_in_list = j
                self.randomlist[j] = True
                self.currimg_name = str(self.image_list[self.curr_img_in_list])
            if valid_int(location):
                prev_img = self.curr_img_in_list
                self.curr_img_in_list = int(location)
            if not self.fullscreen_mode and (
                not self.slideshow_mode or (self.slideshow_mode and action != "ss")
            ):
                self.change_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
            if location == "PREV" or (
                valid_int(location) and int(location) == prev_img - 1
            ):
                self.load_when_idle = GLib.idle_add(
                    self.load_new_image, True, False, True, True, True, True
                )
            else:
                self.load_when_idle = GLib.idle_add(
                    self.load_new_image, False, False, True, True, True, True
                )
            self.set_go_navigation_sensitivities(False)
            if self.slideshow_mode:
                if self.curr_slideshow_random:
                    self.timer_delay = GLib.timeout_add(
                        int(self.curr_slideshow_delay * 1000),
                        self.goto_random_image,
                        None,
                        None,
                        "ss",
                    )
                else:
                    self.timer_delay = GLib.timeout_add(
                        int(self.curr_slideshow_delay * 1000),
                        self.goto_next_image,
                        None,
                        None,
                        "ss",
                    )
            GLib.idle_add(self.thumbpane_select, self.curr_img_in_list)

    def set_go_navigation_sensitivities(self, skip_initial_check):
        # setting skip_image_list_check to True is useful when calling from
        # expand_filelist_and_load_image() for example, as self.image_list has not
        # yet fully populated
        if (
            not self.image_loaded or len(self.image_list) == 1
        ) and not skip_initial_check:
            self.set_previous_image_sensitivities(False)
            self.set_first_image_sensitivities(False)
            self.set_next_image_sensitivities(False)
            self.set_last_image_sensitivities(False)
            self.set_random_image_sensitivities(False)
        elif self.curr_img_in_list == 0:
            if self.listwrap_mode == 0:
                self.set_previous_image_sensitivities(False)
            else:
                self.set_previous_image_sensitivities(True)
            self.set_first_image_sensitivities(False)
            self.set_next_image_sensitivities(True)
            self.set_last_image_sensitivities(True)
            self.set_random_image_sensitivities(True)
        elif self.curr_img_in_list == len(self.image_list) - 1:
            self.set_previous_image_sensitivities(True)
            self.set_first_image_sensitivities(True)
            if self.listwrap_mode == 0:
                self.set_next_image_sensitivities(False)
            else:
                self.set_next_image_sensitivities(True)
            self.set_last_image_sensitivities(False)
            self.set_random_image_sensitivities(True)
        else:
            self.set_previous_image_sensitivities(True)
            self.set_first_image_sensitivities(True)
            self.set_next_image_sensitivities(True)
            self.set_last_image_sensitivities(True)
            self.set_random_image_sensitivities(True)

    def reinitialize_randomlist(self):
        self.randomlist = []
        for i in range(len(self.image_list)):
            self.randomlist.append(False)
        self.randomlist[self.curr_img_in_list] = True

    def image_load_failed(self, reset_cursor, filename=""):
        # If a filename is provided, use it for display:
        if len(filename) == 0:
            self.currimg_name = str(self.image_list[self.curr_img_in_list])
        else:
            self.currmg_name = filename
        if self.verbose and self.currimg_name != "":
            print(_("Loading: %s") % self.currimg_name)
        self.update_title()
        self.put_error_image_to_window()
        self.image_loaded = False
        self.currimg_pixbuf_original = None
        if reset_cursor:
            if not self.fullscreen_mode:
                self.change_cursor(None)

    def load_new_image_stop_now(self):
        try:
            GLib.source_remove(self.load_when_idle)
        except:
            pass
        try:
            GLib.source_remove(self.preload_when_idle)
        except:
            pass
        try:
            GLib.source_remove(self.preload_when_idle2)
        except:
            pass

    def load_new_image(
        self,
        check_prev_last,
        use_current_pixbuf_original,
        reset_cursor,
        perform_onload_action,
        preload_next_image_after,
        preload_prev_image_after,
    ):
        try:
            self.load_new_image2(
                check_prev_last,
                use_current_pixbuf_original,
                reset_cursor,
                perform_onload_action,
            )
        except:
            print(traceback.format_exc(), file=sys.stderr)
            self.image_load_failed(True)
        if preload_next_image_after:
            self.preload_when_idle = GLib.idle_add(self.preload_next_image, False)
        if preload_prev_image_after:
            self.preload_when_idle2 = GLib.idle_add(self.preload_prev_image, False)

    def check_preloadimg_prev_for_existing(
        self, prev_index, reset_preloadimg_prev_in_list
    ):
        # Determines if preloadimg_prev needs to be updated; if so,
        # checks if the image is already stored in self.currimg
        # or self.preloadimg_next and can be reused.
        reset_preloadimg_prev_in_list = False
        if prev_index != self.preloadimg_prev_in_list and prev_index != -1:
            # Need to update preloadimg_prev:
            if (
                prev_index == self.loaded_img_in_list
                and not self.image_modified
                and not self.image_zoomed
            ):
                self.preloadimg_prev_in_list = self.loaded_img_in_list
                self.preloadimg_prev_name = self.currimg_name
                self.preloadimg_prev_width = self.currimg_width
                self.preloadimg_prev_height = self.currimg_height
                self.preloadimg_prev_pixbuf = self.currimg_pixbuf
                self.preloadimg_prev_pixbuf_original = self.currimg_pixbuf_original
                self.preloadimg_prev_zoomratio = self.currimg_zoomratio
                self.preloadimg_prev_is_animation = self.currimg_is_animation
            elif prev_index == self.preloadimg_next_in_list:
                self.preloadimg_prev_in_list = self.preloadimg_next_in_list
                self.preloadimg_prev_name = self.preloadimg_next_name
                self.preloadimg_prev_width = self.preloadimg_next_width
                self.preloadimg_prev_height = self.preloadimg_next_height
                self.preloadimg_prev_pixbuf = self.preloadimg_next_pixbuf
                self.preloadimg_prev_pixbuf_original = (
                    self.preloadimg_next_pixbuf_original
                )
                self.preloadimg_prev_zoomratio = self.preloadimg_next_zoomratio
                self.preloadimg_prev_is_animation = self.preloadimg_next_is_animation
            else:
                reset_preloadimg_prev_in_list = True
        elif prev_index == -1:
            reset_preloadimg_prev_in_list = True

    def check_preloadimg_next_for_existing(
        self, next_index, reset_preloadimg_next_in_list
    ):
        # Determines if preloadimg_next needs to be updated; if so,
        # checks if the image is already stored in self.currimg
        # or self.preloadimg_prev and can be reused.
        reset_preloadimg_next_in_list = False
        if next_index != self.preloadimg_next_in_list and next_index != -1:
            # Need to update preloadimg_next:
            if (
                next_index == self.loaded_img_in_list
                and not self.image_modified
                and not self.image_zoomed
            ):
                self.preloadimg_next_in_list = self.loaded_img_in_list
                self.preloadimg_next_name = self.currimg_name
                self.preloadimg_next_width = self.currimg_width
                self.preloadimg_next_height = self.currimg_height
                self.preloadimg_next_pixbuf = self.currimg_pixbuf
                self.preloadimg_next_pixbuf_original = self.currimg_pixbuf_original
                self.preloadimg_next_zoomratio = self.currimg_zoomratio
                self.preloadimg_next_is_animation = self.currimg_is_animation
            elif next_index == self.preloadimg_prev_in_list:
                self.preloadimg_next_in_list = self.preloadimg_prev_in_list
                self.preloadimg_next_name = self.preloadimg_prev_name
                self.preloadimg_next_width = self.preloadimg_prev_width
                self.preloadimg_next_height = self.preloadimg_prev_height
                self.preloadimg_next_pixbuf = self.preloadimg_prev_pixbuf
                self.preloadimg_next_pixbuf_original = (
                    self.preloadimg_prev_pixbuf_original
                )
                self.preloadimg_next_zoomratio = self.preloadimg_prev_zoomratio
                self.preloadimg_next_is_animation = self.preloadimg_prev_is_animation
            else:
                reset_preloadimg_next_in_list = True
        elif next_index == -1:
            reset_preloadimg_next_in_list = True

    def check_currimg_for_existing(self):
        # Determines if currimg needs to be updated; if so,
        # checks if the image is already stored in self.preloadimg_next
        # or self.preloadimg_prev and can be reused (the whole point of
        # preloading!)
        used_prev = False
        used_next = False
        if self.curr_img_in_list != self.loaded_img_in_list:
            # Need to update currimg:
            if self.curr_img_in_list == self.preloadimg_prev_in_list:
                # Set preload_prev_image as current image
                self.currimg_name = self.preloadimg_prev_name
                self.currimg_width = self.preloadimg_prev_width
                self.currimg_height = self.preloadimg_prev_height
                self.currimg_pixbuf = self.preloadimg_prev_pixbuf
                self.currimg_pixbuf_original = self.preloadimg_prev_pixbuf_original
                self.currimg_zoomratio = self.preloadimg_prev_zoomratio
                self.currimg_is_animation = self.preloadimg_prev_is_animation
                used_prev = True
                if self.verbose and self.currimg_name != "":
                    print(_("Loading: %s") % self.currimg_name)
                self.put_zoom_image_to_window(True)
                if not self.currimg_is_animation:
                    self.set_image_sensitivities(True)
                else:
                    self.set_image_sensitivities(False)
            elif self.curr_img_in_list == self.preloadimg_next_in_list:
                # Use preload_next_image as current image
                self.currimg_name = self.preloadimg_next_name
                self.currimg_width = self.preloadimg_next_width
                self.currimg_height = self.preloadimg_next_height
                self.currimg_pixbuf = self.preloadimg_next_pixbuf
                self.currimg_pixbuf_original = self.preloadimg_next_pixbuf_original
                self.currimg_zoomratio = self.preloadimg_next_zoomratio
                self.currimg_is_animation = self.preloadimg_next_is_animation
                used_next = True
                if self.verbose and self.currimg_name != "":
                    print(_("Loading: %s") % self.currimg_name)
                self.put_zoom_image_to_window(True)
                if not self.currimg_is_animation:
                    self.set_image_sensitivities(True)
                else:
                    self.set_image_sensitivities(False)
        return used_prev, used_next

    def load_new_image2(
        self,
        check_prev_last,
        use_current_pixbuf_original,
        reset_cursor,
        perform_onload_action,
        skip_recentfiles=False,
    ):
        # check_prev_last is used to determine if we should check whether
        # preloadimg_prev can be reused last. This should really only
        # be done if the user just clicked the previous image button in
        # order to reduce the number of image loads.
        # If use_current_pixbuf_original == True, do not reload the
        # self.currimg_pixbuf_original from the file; instead, use the existing
        # one. This is only currently useful for resizing images.
        # Determine the indices in the self.image_list array for the
        # previous and next preload images.
        next_index = self.curr_img_in_list + 1
        if next_index > len(self.image_list) - 1:
            if self.listwrap_mode == 0:
                next_index = -1
            else:
                next_index = 0
        prev_index = self.curr_img_in_list - 1
        if prev_index < 0:
            if self.listwrap_mode == 0:
                prev_index = -1
            else:
                prev_index = len(self.image_list) - 1
        if self.preloading_images:
            reset_preloadimg_next_in_list = False
            reset_preloadimg_prev_in_list = False
            if check_prev_last:
                self.check_preloadimg_next_for_existing(
                    next_index, reset_preloadimg_next_in_list
                )
            else:
                self.check_preloadimg_prev_for_existing(
                    prev_index, reset_preloadimg_prev_in_list
                )
        used_prev, used_next = self.check_currimg_for_existing()
        if self.preloading_images:
            if check_prev_last:
                self.check_preloadimg_prev_for_existing(
                    prev_index, reset_preloadimg_prev_in_list
                )
            else:
                self.check_preloadimg_next_for_existing(
                    next_index, reset_preloadimg_next_in_list
                )
            if reset_preloadimg_prev_in_list:
                self.preloadimg_prev_in_list = -1
            if reset_preloadimg_next_in_list:
                self.preloadimg_next_in_list = -1
        if used_prev or used_next:
            # If we used a preload image, set the correct boolean variables
            if self.open_mode == self.open_mode_smart or (
                self.open_mode == self.open_mode_last
                and self.last_mode == self.open_mode_smart
            ):
                self.last_image_action_was_fit = True
                self.last_image_action_was_smart_fit = True
            elif self.open_mode == self.open_mode_fit or (
                self.open_mode == self.open_mode_last
                and self.last_mode == self.open_mode_fit
            ):
                self.last_image_action_was_fit = True
                self.last_image_action_was_smart_fit = False
            elif self.open_mode == self.open_mode_1to1 or (
                self.open_mode == self.open_mode_last
                and self.last_mode == self.open_mode_1to1
            ):
                self.last_image_action_was_fit = False
        else:
            # Need to load the current image
            self.currimg_pixbuf = None
            self.currimg_zoomratio = 1
            self.currimg_name = str(self.image_list[self.curr_img_in_list])
            if self.verbose and self.currimg_name != "":
                print(_("Loading: %s") % self.currimg_name)
            animtest = GdkPixbuf.PixbufAnimation.new_from_file(self.currimg_name)
            if animtest.is_static_image() or (
                use_current_pixbuf_original and not self.currimg_is_animation
            ):
                self.currimg_is_animation = False
                if not use_current_pixbuf_original:
                    self.currimg_pixbuf_original = animtest.get_static_image()
                self.set_image_sensitivities(True)
                if self.open_mode == self.open_mode_smart or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_smart
                ):
                    self.zoom_to_fit_or_1_to_1(None, False, False)
                elif self.open_mode == self.open_mode_fit or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_fit
                ):
                    self.zoom_to_fit_window(None, False, False)
                elif self.open_mode == self.open_mode_1to1 or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_1to1
                ):
                    self.zoom_1_to_1(None, False, False)
            else:
                self.currimg_is_animation = True
                if not use_current_pixbuf_original:
                    self.currimg_pixbuf_original = animtest
                self.zoom_1_to_1(None, False, False)
                self.set_image_sensitivities(False)
        if self.onload_cmd != None and perform_onload_action:
            self.parse_action_command(self.onload_cmd, False)
        self.update_statusbar()
        self.update_title()
        self.image_loaded = True
        self.image_modified = False
        self.image_zoomed = False
        self.set_slideshow_sensitivities()
        if not skip_recentfiles:
            self.register_file_with_recent_docs(self.currimg_name)
        if reset_cursor:
            if not self.fullscreen_mode:
                self.change_cursor(None)

    def preload_next_image(self, use_existing_image):
        try:
            if self.preloading_images and len(self.image_list) > 1:
                if not use_existing_image:
                    next_index = self.curr_img_in_list + 1
                    if next_index > len(self.image_list) - 1:
                        if self.listwrap_mode == 0:
                            self.preloadimg_next_in_list == -1
                            return
                        else:
                            next_index = 0
                    if next_index == self.preloadimg_next_in_list:
                        return
                    self.preloadimg_next_in_list = next_index
                    self.preloadimg_next_name = str(self.image_list[next_index])
                    pre_animtest = GdkPixbuf.PixbufAnimation.new_from_file(
                        self.preloadimg_next_name
                    )
                    if pre_animtest.is_static_image():
                        self.preloadimg_next_is_animation = False
                        self.preloadimg_next_pixbuf_original = (
                            pre_animtest.get_static_image()
                        )
                    else:
                        self.preloadimg_next_is_animation = True
                        self.preloadimg_next_pixbuf_original = pre_animtest
                if self.preloadimg_next_in_list == -1:
                    return
                # Determine self.preloadimg_next_zoomratio
                if self.open_mode == self.open_mode_smart or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_smart
                ):
                    self.zoom_to_fit_or_1_to_1(None, True, False)
                elif self.open_mode == self.open_mode_fit or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_fit
                ):
                    self.zoom_to_fit_window(None, True, False)
                elif self.open_mode == self.open_mode_1to1 or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_1to1
                ):
                    self.zoom_1_to_1(None, True, False)
                # Always start with the original image to preserve quality!
                # Calculate image size:
                self.preloadimg_next_width = int(
                    self.preloadimg_next_pixbuf_original.get_width()
                    * self.preloadimg_next_zoomratio
                )
                self.preloadimg_next_height = int(
                    self.preloadimg_next_pixbuf_original.get_height()
                    * self.preloadimg_next_zoomratio
                )
                if not self.preloadimg_next_is_animation:
                    # Scale image:
                    if not self.preloadimg_next_pixbuf_original.get_has_alpha():
                        self.preloadimg_next_pixbuf = self.preloadimg_next_pixbuf_original.scale_simple(
                            self.preloadimg_next_width,
                            self.preloadimg_next_height,
                            self.zoom_quality,
                        )
                    else:
                        self.preloadimg_next_pixbuf = self.preloadimg_next_pixbuf_original.composite_color_simple(
                            self.preloadimg_next_width,
                            self.preloadimg_next_height,
                            self.zoom_quality,
                            255,
                            8,
                            0x666666,
                            0x999999,
                        )
                else:
                    self.preloadimg_next_pixbuf = self.preloadimg_next_pixbuf_original
                gc.collect()
                if self.verbose:
                    print(_("Preloading: %s") % self.preloadimg_next_name)
        except:
            print(traceback.format_exc(), file=sys.stderr)
            self.preloadimg_next_in_list = -1

    def preload_prev_image(self, use_existing_image):
        try:
            if self.preloading_images and len(self.image_list) > 1:
                if not use_existing_image:
                    prev_index = self.curr_img_in_list - 1
                    if prev_index < 0:
                        if self.listwrap_mode == 0:
                            self.preloadimg_prev_in_list == -1
                            return
                        else:
                            prev_index = len(self.image_list) - 1
                    if prev_index == self.preloadimg_prev_in_list:
                        return
                    self.preloadimg_prev_in_list = prev_index
                    self.preloadimg_prev_name = str(self.image_list[prev_index])
                    pre_animtest = GdkPixbuf.PixbufAnimation.new_from_file(
                        self.preloadimg_prev_name
                    )
                    if pre_animtest.is_static_image():
                        self.preloadimg_prev_is_animation = False
                        self.preloadimg_prev_pixbuf_original = (
                            pre_animtest.get_static_image()
                        )
                    else:
                        self.preloadimg_prev_is_animation = True
                        self.preloadimg_prev_pixbuf_original = pre_animtest
                if self.preloadimg_prev_in_list == -1:
                    return
                # Determine self.preloadimg_prev_zoomratio
                if self.open_mode == self.open_mode_smart or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_smart
                ):
                    self.zoom_to_fit_or_1_to_1(None, False, True)
                elif self.open_mode == self.open_mode_fit or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_fit
                ):
                    self.zoom_to_fit_window(None, False, True)
                elif self.open_mode == self.open_mode_1to1 or (
                    self.open_mode == self.open_mode_last
                    and self.last_mode == self.open_mode_1to1
                ):
                    self.zoom_1_to_1(None, False, True)
                # Always start with the original image to preserve quality!
                # Calculate image size:
                self.preloadimg_prev_width = int(
                    self.preloadimg_prev_pixbuf_original.get_width()
                    * self.preloadimg_prev_zoomratio
                )
                self.preloadimg_prev_height = int(
                    self.preloadimg_prev_pixbuf_original.get_height()
                    * self.preloadimg_prev_zoomratio
                )
                if not self.preloadimg_prev_is_animation:
                    # Scale image:
                    if not self.preloadimg_prev_pixbuf_original.get_has_alpha():
                        self.preloadimg_prev_pixbuf = self.preloadimg_prev_pixbuf_original.scale_simple(
                            self.preloadimg_prev_width,
                            self.preloadimg_prev_height,
                            self.zoom_quality,
                        )
                    else:
                        self.preloadimg_prev_pixbuf = self.preloadimg_prev_pixbuf_original.composite_color_simple(
                            self.preloadimg_prev_width,
                            self.preloadimg_prev_height,
                            self.zoom_quality,
                            255,
                            8,
                            0x666666,
                            0x999999,
                        )
                else:
                    self.preloadimg_prev_pixbuf = self.preloadimg_prev_pixbuf_original
                gc.collect()
                if self.verbose:
                    print(_("Preloading: %s") % self.preloadimg_prev_name)
        except:
            print(traceback.format_exc(), file=sys.stderr)
            self.preloadimg_prev_in_list = -1

    def change_cursor(self, type):
        self.window.get_window().set_cursor(type)

    def expand_filelist_and_load_image(self, inputlist):
        # Takes the current list (i.e. ["pic.jpg", "pic2.gif", "../images"]) and
        # expands it into a list of all pictures found
        self.thumblist.clear()
        first_image_loaded_successfully = False
        self.images_found = 0
        self.stop_now = True  # Make sure that any previous search process is stopped
        self.change_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
        # Reset preload images:
        self.preloadimg_next_in_list = -1
        self.preloadimg_prev_in_list = -1
        # If any directories were passed, display "Searching..." in statusbar:
        self.searching_for_images = False
        for item in inputlist:
            if os.path.isdir(item):
                self.searching_for_images = True
                self.update_statusbar()
        first_image = ""
        first_image_found = False
        first_image_loaded = False
        second_image = ""
        second_image_found = False
        second_image_preloaded = False
        self.randomlist = []
        folderlist = []
        self.image_list = []
        self.curr_img_in_list = 0
        go_buttons_enabled = False
        self.set_go_sensitivities(False)
        # Clean up list (remove preceding "file://" or "file:" and trailing "/")
        for itemnum in range(len(inputlist)):
            # Strip off preceding file..
            if inputlist[itemnum].startswith("file://"):
                inputlist[itemnum] = inputlist[itemnum][7:]
            elif inputlist[itemnum].startswith("file:"):
                inputlist[itemnum] = inputlist[itemnum][5:]
            # Strip off trailing "/" if it exists:
            if inputlist[itemnum][len(inputlist[itemnum]) - 1] == "/":
                inputlist[itemnum] = inputlist[itemnum][: (len(inputlist[itemnum]) - 1)]
            if not (
                inputlist[itemnum].startswith("http://")
                or inputlist[itemnum].startswith("https://")
                or inputlist[itemnum].startswith("ftp://")
            ):
                inputlist[itemnum] = os.path.abspath(inputlist[itemnum])
            else:
                try:
                    # Remote file. Save as /tmp/mirage-<random>/filename.ext
                    tmpdir = tempfile.mkdtemp(prefix="mirage-") + "/"
                    tmpfile = tmpdir + os.path.basename(inputlist[itemnum])
                    socket.setdefaulttimeout(5)
                    urllib.request.urlretrieve(inputlist[itemnum], tmpfile)
                    inputlist[itemnum] = tmpfile
                except:
                    print(traceback.format_exc(), file=sys.stderr)
        # Remove hidden files from list:
        if not self.open_hidden_files:
            tmplist = []
            for item in inputlist:
                if os.path.basename(item)[0] != ".":
                    tmplist.append(item)
                elif self.verbose:
                    print(_("Skipping: %s") % item)
            inputlist = tmplist
            if len(inputlist) == 0:
                # All files/dirs were hidden, exit..
                self.currimg_name = ""
                self.searching_for_images = False
                self.set_go_navigation_sensitivities(False)
                self.set_slideshow_sensitivities()
                if not self.closing_app:
                    self.change_cursor(None)
                self.recursive = False
                self.put_error_image_to_window()
                self.update_title()
                return
        init_image = os.path.abspath(inputlist[0])
        self.stop_now = False
        # If open all images in dir...
        if self.open_all_images:
            temp = inputlist
            inputlist = []
            for item in temp:
                if os.path.isfile(item):
                    itempath = os.path.dirname(os.path.abspath(item))
                    temp = self.recursive
                    self.recursive = False
                    self.stop_now = False
                    self.expand_directory(
                        itempath, False, go_buttons_enabled, False, False
                    )
                    self.recursive = temp
                else:
                    inputlist.append(item)
            for item in self.image_list:
                inputlist.append(item)
                if first_image_found and not second_image_found:
                    second_image_found = True
                    second_image = item
                    second_image_came_from_dir = False
                if item == init_image:
                    first_image_found = True
                    first_image = item
                    first_image_came_from_dir = False
                    self.curr_img_in_list = len(inputlist) - 1
        self.image_list = []
        for item in inputlist:
            if not self.closing_app:
                if os.path.isfile(item):
                    if self.valid_image(item):
                        if not second_image_found and first_image_found:
                            second_image_found = True
                            second_image = item
                            second_image_came_from_dir = False
                        if not first_image_found:
                            first_image_found = True
                            first_image = item
                            first_image_came_from_dir = False
                        self.image_list.append(item)
                        if self.verbose:
                            self.images_found += 1
                            print(
                                _("Found: %(item)s [%(number)i]")
                                % {"item": item, "number": self.images_found}
                            )
                else:
                    # If it's a directory that was explicitly selected or passed to
                    # the program, get all the files in the dir.
                    # Retrieve only images in the top directory specified by the user
                    # unless explicitly told to recurse (via -R or in Settings>Preferences)
                    folderlist.append(item)
                    if not second_image_found:
                        # See if we can find an image in this directory:
                        self.stop_now = False
                        self.expand_directory(
                            item, True, go_buttons_enabled, False, False
                        )
                        itemnum = 0
                        while itemnum < len(self.image_list) and not second_image_found:
                            if os.path.isfile(self.image_list[itemnum]):
                                if not second_image_found and first_image_found:
                                    second_image_found = True
                                    second_image_came_from_dir = True
                                    second_image = self.image_list[itemnum]
                                    self.set_go_navigation_sensitivities(True)
                                    go_buttons_enabled = True
                                if not first_image_found:
                                    first_image_found = True
                                    first_image = self.image_list[itemnum]
                                    first_image_came_from_dir = True
                            itemnum += 1
                # Load first image and display:
                if (
                    first_image_found
                    and not first_image_loaded
                    and self.curr_img_in_list <= len(self.image_list) - 1
                ):
                    first_image_loaded = True
                    if self.slideshow_mode:
                        self.toggle_slideshow(None, None, None)
                    if self.verbose and self.currimg_name != "":
                        print(_("Loading: %s") % self.currimg_name)
                    try:
                        self.load_new_image2(False, False, True, True)
                        # Calling load_new_image2 will reset the following two vars
                        # to 0, so ensure they are -1 again (no images preloaded)
                        self.preloadimg_prev_in_list = -1
                        self.preloadimg_next_in_list = -1
                        if not self.currimg_is_animation:
                            self.previmg_width = self.currimg_pixbuf.get_width()
                        else:
                            self.previmg_width = (
                                self.currimg_pixbuf.get_static_image().get_width()
                            )
                        self.image_loaded = True
                        first_image_loaded_successfully = True
                    except:
                        print(traceback.format_exc(), file=sys.stderr)
                    if first_image_came_from_dir:
                        self.image_list = []
                # Pre-load second image:
                if (
                    second_image_found
                    and not second_image_preloaded
                    and (
                        (
                            not second_image_came_from_dir
                            and self.curr_img_in_list + 1 <= len(self.image_list) - 1
                        )
                        or second_image_came_from_dir
                    )
                ):
                    second_image_preloaded = True
                    temp = self.image_list
                    self.image_list = []
                    while len(self.image_list) < self.curr_img_in_list + 1:
                        self.image_list.append(first_image)
                    self.image_list.append(second_image)
                    self.preload_next_image(False)
                    self.image_list = temp
        if first_image_found:
            # Sort the filelist and folderlist alphabetically, and recurse into folderlist:
            if first_image_came_from_dir:
                self.add_folderlist_images(folderlist, go_buttons_enabled)
                self.do_image_list_stuff(first_image, second_image)
            else:
                self.do_image_list_stuff(first_image, second_image)
                self.add_folderlist_images(folderlist, go_buttons_enabled)
            self.update_title()
        if not first_image_loaded_successfully:
            self.image_load_failed(False, init_image)
        self.searching_for_images = False
        self.update_statusbar()
        self.set_go_navigation_sensitivities(False)
        self.set_slideshow_sensitivities()
        self.thumbpane_update_images(True, self.curr_img_in_list)
        if not self.closing_app:
            self.change_cursor(None)
        self.recursive = False

    def add_folderlist_images(self, folderlist, go_buttons_enabled):
        if len(folderlist) > 0:
            folderlist.sort(key=locale.strxfrm)
            folderlist = list(set(folderlist))
            for item in folderlist:
                if not self.closing_app:
                    if (
                        not self.open_hidden_files and os.path.basename(item)[0] != "."
                    ) or self.open_hidden_files:
                        self.stop_now = False
                        self.expand_directory(
                            item, False, go_buttons_enabled, True, True
                        )

    def do_image_list_stuff(self, first_image, second_image):
        if len(self.image_list) > 0:
            self.set_go_navigation_sensitivities(True)
            self.image_list = list(set(self.image_list))
            self.image_list.sort(key=locale.strxfrm)

    def expand_directory(
        self,
        item,
        stop_when_second_image_found,
        go_buttons_enabled,
        update_window_title,
        print_found_msg,
    ):
        if not self.stop_now and not self.closing_app:
            folderlist = []
            filelist = []
            if not os.access(item, os.R_OK):
                return False
            for item2 in os.listdir(item):
                if not self.closing_app and not self.stop_now:
                    item2 = item + os.sep + item2
                    item_fullpath2 = os.path.abspath(item2)
                    if (
                        not self.open_hidden_files
                        and os.path.basename(item_fullpath2)[0] != "."
                    ) or self.open_hidden_files:
                        if os.path.isfile(item_fullpath2) and self.valid_image(
                            item_fullpath2
                        ):
                            filelist.append(item2)
                            if self.verbose and print_found_msg:
                                self.images_found += 1
                                print(
                                    _("Found: %(fullpath)s [%(number)i]")
                                    % {
                                        "fullpath": item_fullpath2,
                                        "number": self.images_found,
                                    }
                                )
                        elif os.path.isdir(item_fullpath2) and self.recursive:
                            folderlist.append(item_fullpath2)
                    elif self.verbose:
                        print(_("Skipping: %s") % item_fullpath2)
            if len(self.image_list) > 0 and update_window_title:
                self.update_title()
            # Sort the filelist and folderlist alphabetically:
            if len(filelist) > 0:
                filelist.sort(key=locale.strxfrm)
                for item2 in filelist:
                    if not item2 in self.image_list:
                        self.image_list.append(item2)
                        if stop_when_second_image_found and len(self.image_list) == 2:
                            return
                        if not go_buttons_enabled and len(self.image_list) > 1:
                            self.set_go_navigation_sensitivities(True)
                            go_buttons_enabled = True
            # Recurse into the folderlist:
            if len(folderlist) > 0:
                folderlist.sort(key=locale.strxfrm)
                for item2 in folderlist:
                    if not self.stop_now:
                        self.expand_directory(
                            item2,
                            stop_when_second_image_found,
                            go_buttons_enabled,
                            update_window_title,
                            print_found_msg,
                        )

    def register_file_with_recent_docs(self, imgfile):
        self.recent_file_add_and_refresh(imgfile)
        if os.path.isfile(imgfile) and Gtk.check_version(2, 10, 0) == None:
            try:
                gtk_recent_manager = Gtk.RecentManager.get_default()
                uri = ""
                if imgfile[:7] != "file://":
                    uri = "file://"
                uri = uri + urllib.request.pathname2url(os.path.abspath(imgfile))
                gtk_recent_manager.add_item(uri)
            except:
                print(traceback.format_exc(), file=sys.stderr)
                # Isnt currently functional on win32
                if sys.platform == "win32":
                    pass
                else:
                    raise

    def valid_image(self, file):
        format, width, height = GdkPixbuf.Pixbuf.get_file_info(file)
        if format is None:
            return False

        if format.get_name() == "wbmp":
            # some regular files are thought to be wbmp for whatever reason,
            # so let's check further.. :(
            try:
                test2 = GdkPixbuf.Pixbuf.new_from_file(file)
            except:
                return False

        return True

    def image_flip(self, old_pix, vertical):
        width = old_pix.get_width()
        height = old_pix.get_height()
        d = None
        if vertical:
            d, w, h, rws = imgfuncs.vert(
                old_pix.get_pixels(),
                width,
                height,
                old_pix.get_rowstride(),
                old_pix.get_n_channels(),
            )
        else:
            d, w, h, rws = imgfuncs.horiz(
                old_pix.get_pixels(),
                width,
                height,
                old_pix.get_rowstride(),
                old_pix.get_n_channels(),
            )
        if d:
            new_pix = GdkPixbuf.Pixbuf.new_from_data(
                d,
                old_pix.get_colorspace(),
                old_pix.get_has_alpha(),
                old_pix.get_bits_per_sample(),
                w,
                h,
                rws,
            )
            return new_pix
        return old_pix

    def image_rotate(self, old_pix, full_angle):
        width = old_pix.get_width()
        height = old_pix.get_height()
        angle = full_angle - (int(full_angle) // 360) * 360
        if angle:
            d = None
            if angle % 270 == 0:
                d, w, h, rws = imgfuncs.right(
                    old_pix.get_pixels(),
                    width,
                    height,
                    old_pix.get_rowstride(),
                    old_pix.get_n_channels(),
                )
            elif angle % 180 == 0:
                d, w, h, rws = imgfuncs.mirror(
                    old_pix.get_pixels(),
                    width,
                    height,
                    old_pix.get_rowstride(),
                    old_pix.get_n_channels(),
                )
            elif angle % 90 == 0:
                d, w, h, rws = imgfuncs.left(
                    old_pix.get_pixels(),
                    width,
                    height,
                    old_pix.get_rowstride(),
                    old_pix.get_n_channels(),
                )
            if d:
                new_pix = GdkPixbuf.Pixbuf.new_from_data(
                    d,
                    old_pix.get_colorspace(),
                    old_pix.get_has_alpha(),
                    old_pix.get_bits_per_sample(),
                    w,
                    h,
                    rws,
                )
                return new_pix
        return old_pix

    def toggle_slideshow(self, action, parameter, data):
        if len(self.image_list) > 1:
            if not self.slideshow_mode:
                if self.slideshow_in_fullscreen and not self.fullscreen_mode:
                    self.enter_fullscreen(None, None, None)
                self.slideshow_mode = True
                self.update_title()
                self.set_slideshow_sensitivities()
                if not self.curr_slideshow_random:
                    self.timer_delay = GLib.timeout_add(
                        int(self.curr_slideshow_delay * 1000),
                        self.goto_next_image,
                        None,
                        None,
                        "ss",
                    )
                else:
                    self.reinitialize_randomlist()
                    self.timer_delay = GLib.timeout_add(
                        int(self.curr_slideshow_delay * 1000),
                        self.goto_random_image,
                        None,
                        None,
                        "ss",
                    )
                self.fullscreen_controls.set_slideshow_playing(True)
                timer_screensaver = GLib.timeout_add(
                    1000, self.disable_screensaver_in_slideshow_mode
                )
            else:
                self.slideshow_mode = False
                GLib.source_remove(self.timer_delay)
                self.update_title()
                self.set_slideshow_sensitivities()
                self.set_zoom_sensitivities()
                self.fullscreen_controls.set_slideshow_playing(False)

    def update_title(self):
        if len(self.image_list) == 0:
            title = "Mirage"
        else:
            title = (
                "Mirage - "
                + _("[%(current)i of %(total)i]")
                % {"current": self.curr_img_in_list + 1, "total": len(self.image_list)}
                + " "
                + os.path.basename(self.currimg_name)
            )
            if self.slideshow_mode:
                title = title + " - " + _("Slideshow Mode")
        self.window.set_title(title)

    def slideshow_controls_show(self):
        if self.slideshow_controls_visible:
            return

        self.slideshow_controls_visible = True

        self.fullscreen_controls.set_slideshow_delay(self.curr_slideshow_delay)

        if self.slideshow_mode:
            self.fullscreen_controls.set_slideshow_playing(True)
        else:
            self.fullscreen_controls.set_slideshow_playing(False)

        screen = self.window.get_screen()
        self.fullscreen_controls.set_screen(screen)
        self.fullscreen_controls.show_all()

        if not self.closing_app:
            while Gtk.events_pending():
                Gtk.main_iteration()

        width = self.window.get_allocation().width
        height = self.window.get_allocation().height
        (x, y) = self.window.get_position()
        self.fullscreen_controls.position(width, height, x, y)

    def slideshow_controls_hide(self):
        if not self.slideshow_controls_visible:
            return

        self.slideshow_controls_visible = False
        self.fullscreen_controls.hide()

    def disable_screensaver_in_slideshow_mode(self):
        if self.slideshow_mode and self.disable_screensaver:
            test = os.spawnlp(
                os.P_WAIT,
                "/usr/bin/xscreensaver-command",
                "xscreensaver-command",
                "-deactivate",
            )
            if test != 127:
                timer_screensaver = GLib.timeout_add(
                    1000, self.disable_screensaver_in_slideshow_mode
                )


def run(argv):
    Gdk.threads_enter()
    app = Base()
    Gdk.threads_init()
    app.run(argv)
    Gdk.threads_leave()
