diff --git a/Cargo.lock b/Cargo.lock index e47f24a..14da230 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1398,6 +1398,7 @@ dependencies = [ "libgweather", "log", "once_cell", + "strum", "url", ] @@ -1767,6 +1768,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" + [[package]] name = "ryu" version = "1.0.17" @@ -1886,6 +1893,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.50", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 808d619..2020c20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ libgweather = "4.4.0" log = "0.4.17" once_cell = "1.16.0" url = "2.3.1" +strum = { version = "0.26.2", features = ["derive"] } [dependencies.ashpd] version = "0.8.0" @@ -36,7 +37,7 @@ features = ["gtk4_wayland"] [dependencies.gtk] package = "gtk4" version = "0.8.0" -features = ["v4_12", "xml_validation"] +features = ["v4_14", "xml_validation"] [dependencies.gio] package = "gio" diff --git a/data/resources/gtk/help-overlay.ui b/data/resources/gtk/help-overlay.ui index 9d831ad..5f79ad9 100644 --- a/data/resources/gtk/help-overlay.ui +++ b/data/resources/gtk/help-overlay.ui @@ -140,13 +140,13 @@ Rotate Clockwise - win.rotate_cw + win.rotate-cw Rotate Counterclockwise - win.rotate_ccw + win.rotate-ccw @@ -275,4 +275,4 @@ - \ No newline at end of file + diff --git a/po/POTFILES.in b/po/POTFILES.in index fc42903..93c0a5a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -17,6 +17,8 @@ src/metadata/mod.rs src/util/gettext.rs src/util/mod.rs src/widgets/drag_overlay.rs +src/widgets/error_details.rs +src/widgets/error_details.ui src/widgets/image.rs src/widgets/image/background_color.rs src/widgets/image/drag.rs @@ -43,4 +45,5 @@ src/widgets/shy_bin.rs src/widgets/sliding_view.rs src/window.rs src/window.ui +src/window/actions.rs src/window/controls.rs diff --git a/src/application.rs b/src/application.rs index f3b8612..01dbbab 100644 --- a/src/application.rs +++ b/src/application.rs @@ -23,9 +23,9 @@ use adw::prelude::*; use adw::subclass::prelude::*; +use crate::config; use crate::deps::*; use crate::window::LpWindow; -use crate::{about, config}; mod imp { use super::*; @@ -128,12 +128,6 @@ impl LpApplication { // that implements gio::ActionMap. Here we build the application's actions and // add them with add_action_entries(). let actions = [ - gio::ActionEntryBuilder::new("about") - .activate(|app: &Self, _, _| { - let app = app.clone(); - glib::spawn_future_local(async move { app.show_about().await }); - }) - .build(), gio::ActionEntryBuilder::new("help") .activate(|app: &Self, _, _| app.show_help()) .build(), @@ -152,65 +146,8 @@ impl LpApplication { self.set_accels_for_action("app.help", &["F1"]); self.set_accels_for_action("app.quit", &["Q"]); - self.set_accels_for_action("app.new-window", &["N"]); - - self.set_accels_for_action("win.open", &["O"]); - self.set_accels_for_action("win.print", &["P"]); - self.set_accels_for_action("win.copy", &["C"]); - self.set_accels_for_action("win.trash", &["Delete", "KP_Delete"]); - self.set_accels_for_action("win.delete", &["Delete", "KP_Delete"]); - self.set_accels_for_action("win.show-help-overlay", &["question"]); - self.set_accels_for_action("win.leave-fullscreen", &["Escape"]); - self.set_accels_for_action("win.toggle-properties", &["F9", "Return"]); - self.set_accels_for_action("win.toggle-fullscreen", &["F11"]); - self.set_accels_for_action("win.set-background", &["F8"]); - - self.set_accels_for_action("win.image-left-instant", &["Left", "Page_Down"]); - self.set_accels_for_action("win.image-right-instant", &["Right", "Page_Up"]); - self.set_accels_for_action("win.first", &["Home"]); - self.set_accels_for_action("win.last", &["End"]); - - self.set_accels_for_action( - "win.zoom-to-exact(1.0)", - &["1", "KP_1", "1", "KP_1"], - ); - self.set_accels_for_action( - "win.zoom-to-exact(2.0)", - &["2", "KP_2", "2", "KP_2"], - ); - self.set_accels_for_action("win.zoom-best-fit", &["0", "KP_0", "0", "KP_0"]); - self.set_accels_for_action( - "win.zoom-in-cursor", - &[ - "plus", - "plus", - "equal", - "equal", - "KP_Add", - "KP_Add", - ], - ); - self.set_accels_for_action( - "win.zoom-out-cursor", - &["minus", "minus", "KP_Subtract", "KP_Subtract"], - ); - - self.set_accels_for_action("win.pan-left", &["Left"]); - self.set_accels_for_action("win.pan-right", &["Right"]); - self.set_accels_for_action("win.pan-up", &["Up"]); - self.set_accels_for_action("win.pan-down", &["Down"]); - - self.set_accels_for_action("win.rotate_cw", &["R"]); - self.set_accels_for_action("win.rotate_ccw", &["R"]); - self.set_accels_for_action("window.close", &["W"]); - } - - pub async fn show_about(&self) { - let about = about::dialog().await; - let window = self.active_window().unwrap(); - - about.present(&window); + self.set_accels_for_action("app.new-window", &["N"]); } pub fn show_help(&self) { diff --git a/src/widgets/error_details.rs b/src/widgets/error_details.rs index c1bb345..093fa56 100644 --- a/src/widgets/error_details.rs +++ b/src/widgets/error_details.rs @@ -1,12 +1,8 @@ use adw::prelude::*; use adw::subclass::prelude::*; -use glib::{clone, Properties}; -use gtk::prelude::*; use gtk::CompositeTemplate; use crate::deps::*; -use crate::util::gettext::*; -use crate::widgets::LpImage; mod imp { use super::*; diff --git a/src/widgets/error_details.ui b/src/widgets/error_details.ui index 499ea44..77552af 100644 --- a/src/widgets/error_details.ui +++ b/src/widgets/error_details.ui @@ -34,6 +34,7 @@ true + false word-char diff --git a/src/widgets/image_page.rs b/src/widgets/image_page.rs index 98e68ff..0f3db88 100644 --- a/src/widgets/image_page.rs +++ b/src/widgets/image_page.rs @@ -193,11 +193,11 @@ impl LpImagePage { let image = self.image(); if image.is_unsupported() { - let message = if glycin::supported_mime_types().await.len() == 0 { + let message = if glycin::supported_mime_types().await.is_empty() { // Translators: {} is replace with a version number gettext_f( "No image loaders available. Mabye the “glycin-loaders” package with compatibility version “{}+” is not installed.", - [ format!("{}+", glycin::COMPAT_VERSION.to_string())]) + [ format!("{}+", glycin::COMPAT_VERSION)]) } else { let mime_type = image.metadata().mime_type().unwrap_or_default(); let content_type = gio::content_type_from_mime_type(&mime_type).unwrap_or_default(); @@ -221,6 +221,9 @@ impl LpImagePage { }; imp.error_page.set_description(Some(&message)); + imp.error_more_info.set_visible(false); + + imp.stack.set_visible_child(&*imp.error_page); } else if let Some(err) = image.error() { imp.error_more_info .connect_clicked(glib::clone!(@weak self as obj => move |_| { @@ -230,7 +233,9 @@ impl LpImagePage { imp.error_page.set_description(Some(&gettext( "Either the image file is corrupted or it contains unsupported elements.", ))); + + imp.error_more_info.set_visible(true); + imp.stack.set_visible_child(&*imp.error_page); } - imp.stack.set_visible_child(&*imp.error_page); } } diff --git a/src/widgets/image_page.ui b/src/widgets/image_page.ui index 90f0036..ec8abad 100644 --- a/src/widgets/image_page.ui +++ b/src/widgets/image_page.ui @@ -103,7 +103,7 @@ object-rotate-left-symbolic Rotate Left - win.rotate_ccw + win.rotate-ccw @@ -113,7 +113,7 @@ object-rotate-right-symbolic Rotate Right - win.rotate_cw + win.rotate-cw diff --git a/src/window.rs b/src/window.rs index 6b5b27a..2aa2842 100644 --- a/src/window.rs +++ b/src/window.rs @@ -25,11 +25,13 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +mod actions; mod controls; use std::cell::{Cell, OnceCell, RefCell}; use std::path::{Path, PathBuf}; +use actions::*; use adw::prelude::*; use adw::subclass::prelude::*; use glib::clone; @@ -39,7 +41,7 @@ use crate::application::LpApplication; use crate::config; use crate::deps::*; use crate::util::gettext::*; -use crate::util::{Direction, Position}; +use crate::util::Direction; use crate::widgets::{LpDragOverlay, LpImage, LpImageView, LpPropertiesView}; /// Show window after X milliseconds even if image dimensions are not known yet @@ -138,148 +140,16 @@ mod imp { } }); - // Set up actions - klass.install_action("win.toggle-fullscreen", None, move |win, _, _| { - win.toggle_fullscreen(!win.is_fullscreened()); - }); - - klass.install_action("win.next", None, move |win, _, _| { - win.imp().image_view.navigate(Direction::Forward, true); - }); - - klass.install_action("win.previous", None, move |win, _, _| { - win.imp().image_view.navigate(Direction::Back, true); - }); - - klass.install_action("win.image-right", None, move |win, _, _| { - if win.direction() == gtk::TextDirection::Rtl { - win.imp().image_view.navigate(Direction::Back, true); - } else { - win.imp().image_view.navigate(Direction::Forward, true); - } - }); - - klass.install_action("win.image-right-instant", None, move |win, _, _| { - if win.direction() == gtk::TextDirection::Rtl { - win.imp().image_view.navigate(Direction::Back, false); - } else { - win.imp().image_view.navigate(Direction::Forward, false); - } - }); - - klass.install_action("win.image-left", None, move |win, _, _| { - if win.direction() == gtk::TextDirection::Rtl { - win.imp().image_view.navigate(Direction::Forward, true); - } else { - win.imp().image_view.navigate(Direction::Back, true); - } - }); - - klass.install_action("win.image-left-instant", None, move |win, _, _| { - if win.direction() == gtk::TextDirection::Rtl { - win.imp().image_view.navigate(Direction::Forward, false); - } else { - win.imp().image_view.navigate(Direction::Back, false); - } - }); - - klass.install_action("win.first", None, move |win, _, _| { - win.imp().image_view.jump(Position::First); - }); - - klass.install_action("win.last", None, move |win, _, _| { - win.imp().image_view.jump(Position::Last); - }); - - klass.install_action("win.zoom-out-cursor", None, move |win, _, _| { - win.zoom_out_cursor(); - }); - - klass.install_action("win.zoom-out-center", None, move |win, _, _| { - win.zoom_out_center(); - }); - - klass.install_action("win.zoom-in-cursor", None, move |win, _, _| { - win.zoom_in_cursor(); - }); - - klass.install_action("win.zoom-in-center", None, move |win, _, _| { - win.zoom_in_center(); - }); - - klass.install_action( - "win.zoom-to-exact", - Some(VariantTy::DOUBLE), - move |win, _, level| { - win.zoom_to_exact(level.unwrap().get().unwrap()); - }, + klass.add_binding_action( + gdk::Key::question, + gdk::ModifierType::CONTROL_MASK, + "win.show-help-overlay", ); - klass.install_action("win.zoom-best-fit", None, move |win, _, _| { - win.zoom_best_fit(); - }); + // Set up actions - klass.install_action("win.pan-up", None, move |win, _, _| { - win.pan(>k::PanDirection::Up); - }); - - klass.install_action("win.pan-down", None, move |win, _, _| { - win.pan(>k::PanDirection::Down); - }); - - klass.install_action("win.pan-left", None, move |win, _, _| { - win.pan(>k::PanDirection::Left); - }); - - klass.install_action("win.pan-right", None, move |win, _, _| { - win.pan(>k::PanDirection::Right); - }); - - klass.install_action("win.leave-fullscreen", None, move |win, _, _| { - win.toggle_fullscreen(false); - }); - - klass.install_action("win.toggle-properties", None, move |win, _, _| { - win.imp() - .properties_button - .set_active(!win.imp().properties_button.is_active()); - }); - - klass.install_action_async("win.open", None, |win, _, _| async move { - win.pick_file().await; - }); - - klass.install_action_async("win.open-with", None, |win, _, _| async move { - win.open_with().await; - }); - - klass.install_action("win.rotate_cw", None, |win, _, _| { - win.rotate_image(90.0); - }); - - klass.install_action("win.rotate_ccw", None, |win, _, _| { - win.rotate_image(-90.0); - }); - - klass.install_action_async("win.set-background", None, |win, _, _| async move { - win.set_background().await; - }); - - klass.install_action("win.print", None, move |win, _, _| { - win.print(); - }); - - klass.install_action("win.copy-image", None, move |win, _, _| { - win.copy_image(); - }); - - klass.install_action_async("win.trash", None, |win, _, _| async move { - win.trash().await; - }); - - klass.install_action_async("win.delete", None, |win, _, _| async move { - win.delete().await; - }); + ActionPartGlobal::init_actions_and_bindings(klass); + Action::init_actions_and_bindings(klass); klass.install_action( "win.show-toast", @@ -445,6 +315,10 @@ mod imp { obj.schedule_hide_controls(); }); + // Activate global shortcuts only if no dialog is open + obj.connect_visible_dialog_notify(|obj| obj.update_accel_status()); + obj.connect_is_active_notify(|obj| obj.update_accel_status()); + self.status_page .set_icon_name(Some(&format!("{}-symbolic", config::APP_ID))); @@ -791,8 +665,8 @@ impl LpWindow { "win.set-background", "win.toggle-fullscreen", "win.print", - "win.rotate_cw", - "win.rotate_ccw", + "win.rotate-cw", + "win.rotate-ccw", "win.copy-image", "win.zoom-best-fit", "win.zoom-to-exact", @@ -992,4 +866,27 @@ impl LpWindow { }; imp.toolbar_view.set_top_bar_style(toolbar_style); } + + fn update_accel_status(&self) { + let Some(application) = self.application() else { + log::error!("No application for window found"); + return; + }; + + // Only change status if active window + if self.is_active() { + if self.visible_dialog().is_some() { + // If AdwDialog is visible, remove global accels that are for the main window + ActionPartGlobal::remove_accels(&application); + } else { + // Add accels if viewing main window + ActionPartGlobal::add_accels(&application); + } + } + } + + async fn show_about(&self) { + let about = crate::about::dialog().await; + about.present(self); + } } diff --git a/src/window.ui b/src/window.ui index 8ac0798..4231d67 100644 --- a/src/window.ui +++ b/src/window.ui @@ -52,7 +52,7 @@ _About Image Viewer - app.about + win.about @@ -104,7 +104,7 @@ object-rotate-left-symbolic Rotate Left - win.rotate_ccw + win.rotate-ccw @@ -114,7 +114,7 @@ object-rotate-right-symbolic Rotate Right - win.rotate_cw + win.rotate-cw diff --git a/src/window/actions.rs b/src/window/actions.rs new file mode 100644 index 0000000..e1fc0af --- /dev/null +++ b/src/window/actions.rs @@ -0,0 +1,470 @@ +use std::ops::Deref; + +use adw::subclass::prelude::*; +use gtk::prelude::*; +use strum::IntoEnumIterator; + +use crate::deps::*; +use crate::util::{Direction, Position}; + +/// Actions that need some global accels +/// +/// The app level accels are needed for arrow key bindings. Because the arrow +/// keys are usually used for widget navigation, it's not possible to overwrite +/// them on key-binding level. +#[derive(strum::Display, strum::AsRefStr, strum::EnumIter)] +pub enum ActionPartGlobal { + #[strum(to_string = "win.image-left-instant")] + ImageLeftInstant, + #[strum(to_string = "win.image-right-instant")] + ImageRightInstant, + // Pan + #[strum(to_string = "win.pan-up")] + PanUp, + #[strum(to_string = "win.pan-right")] + PanRight, + #[strum(to_string = "win.pan-down")] + PanDown, + #[strum(to_string = "win.pan-left")] + PanLeft, +} + +impl ActionPartGlobal { + pub fn add_accels(application: >k::Application) { + for action in Self::iter() { + match action { + ActionPartGlobal::ImageRightInstant => { + application.set_accels_for_action(&action, &["Right"]) + } + ActionPartGlobal::ImageLeftInstant => { + application.set_accels_for_action(&action, &["Left"]) + } + ActionPartGlobal::PanUp => { + application.set_accels_for_action(&action, &["Up"]) + } + ActionPartGlobal::PanRight => { + application.set_accels_for_action(&action, &["Right"]) + } + ActionPartGlobal::PanDown => { + application.set_accels_for_action(&action, &["Down"]) + } + ActionPartGlobal::PanLeft => { + application.set_accels_for_action(&action, &["Left"]) + } + } + } + } + + pub fn remove_accels(application: >k::Application) { + for action in Self::iter() { + application.set_accels_for_action(&action, &[]); + } + } + + pub fn init_actions_and_bindings(klass: &mut ::Class) { + for action in Self::iter() { + match action { + ActionPartGlobal::ImageLeftInstant => { + klass.install_action(&action, None, move |win, _, _| { + if win.direction() == gtk::TextDirection::Rtl { + win.imp().image_view.navigate(Direction::Forward, false); + } else { + win.imp().image_view.navigate(Direction::Back, false); + } + }); + klass.add_binding_action( + gdk::Key::Page_Down, + gdk::ModifierType::empty(), + &action, + ); + } + + ActionPartGlobal::ImageRightInstant => { + klass.install_action(&action, None, move |win, _, _| { + if win.direction() == gtk::TextDirection::Rtl { + win.imp().image_view.navigate(Direction::Back, false); + } else { + win.imp().image_view.navigate(Direction::Forward, false); + } + }); + klass.add_binding_action( + gdk::Key::Page_Up, + gdk::ModifierType::empty(), + &action, + ); + } + + // Pan + ActionPartGlobal::PanUp => { + klass.install_action(&action, None, move |win, _, _| { + win.pan(>k::PanDirection::Up); + }); + } + ActionPartGlobal::PanRight => { + klass.install_action(&action, None, move |win, _, _| { + win.pan(>k::PanDirection::Right); + }); + } + ActionPartGlobal::PanDown => { + klass.install_action(&action, None, move |win, _, _| { + win.pan(>k::PanDirection::Down); + }); + } + ActionPartGlobal::PanLeft => { + klass.install_action(&action, None, move |win, _, _| { + win.pan(>k::PanDirection::Left); + }); + } + } + } + } +} + +#[derive(strum::Display, strum::AsRefStr, strum::EnumIter)] +pub enum Action { + // Navigation + #[strum(to_string = "win.image-left")] + ImageLeft, + #[strum(to_string = "win.image-right")] + ImageRight, + #[strum(to_string = "win.previous")] + Previous, + #[strum(to_string = "win.next")] + Next, + #[strum(to_string = "win.first")] + First, + #[strum(to_string = "win.last")] + Last, + // Zoom/Rotate + #[strum(to_string = "win.rotate-cw")] + RotateCw, + #[strum(to_string = "win.rotate-ccw")] + RotateCcw, + #[strum(to_string = "win.zoom-out-center")] + ZoomOutCenter, + #[strum(to_string = "win.zoom-in-center")] + ZoomInCenter, + #[strum(to_string = "win.zoom-out-cursor")] + ZoomOutCursor, + #[strum(to_string = "win.zoom-in-cursor")] + ZoomInCursor, + #[strum(to_string = "win.zoom-best-fit")] + ZoomBestFit, + #[strum(to_string = "win.zoom-to-exact-1")] + ZoomToExact1, + #[strum(to_string = "win.zoom-to-exact-2")] + ZoomToExact2, + // Misc + #[strum(to_string = "win.open")] + Open, + #[strum(to_string = "win.open-with")] + OpenWith, + #[strum(to_string = "win.print")] + Print, + #[strum(to_string = "win.copy-image")] + CopyImage, + #[strum(to_string = "win.set-background")] + SetBackground, + #[strum(to_string = "win.toggle-fullscreen")] + ToggleFullscreen, + #[strum(to_string = "win.leave-fullscreen")] + LeaveFullscreen, + #[strum(to_string = "win.trash")] + Trash, + #[strum(to_string = "win.delete")] + Delete, + #[strum(to_string = "win.toggle-properties")] + ToggleProperties, + #[strum(to_string = "win.about")] + About, +} + +impl Action { + pub fn init_actions_and_bindings(klass: &mut ::Class) { + for action in Self::iter() { + match action { + // Navigation + Action::ImageLeft => klass.install_action(&action, None, move |win, _, _| { + if win.direction() == gtk::TextDirection::Rtl { + win.imp().image_view.navigate(Direction::Forward, true); + } else { + win.imp().image_view.navigate(Direction::Back, true); + } + }), + + Action::ImageRight => klass.install_action(&action, None, move |win, _, _| { + if win.direction() == gtk::TextDirection::Rtl { + win.imp().image_view.navigate(Direction::Back, true); + } else { + win.imp().image_view.navigate(Direction::Forward, true); + } + }), + + Action::Previous => klass.install_action(&action, None, move |win, _, _| { + win.imp().image_view.navigate(Direction::Back, true); + }), + + Action::Next => klass.install_action(&action, None, move |win, _, _| { + win.imp().image_view.navigate(Direction::Forward, true); + }), + + Action::First => { + klass.install_action(&action, None, move |win, _, _| { + win.imp().image_view.jump(Position::First); + }); + klass.add_binding_action(gdk::Key::Home, gdk::ModifierType::empty(), &action); + } + + Action::Last => { + klass.install_action(&action, None, move |win, _, _| { + win.imp().image_view.jump(Position::Last); + }); + klass.add_binding_action(gdk::Key::End, gdk::ModifierType::empty(), &action); + } + + // Rotation/Zoom + Action::RotateCw => { + klass.install_action(&action, None, |win, _, _| { + win.rotate_image(90.0); + }); + klass.add_binding_action(gdk::Key::r, gdk::ModifierType::CONTROL_MASK, &action); + } + + Action::RotateCcw => { + klass.install_action(&action, None, |win, _, _| { + win.rotate_image(-90.0); + }); + klass.add_binding_action( + gdk::Key::r, + gdk::ModifierType::CONTROL_MASK.union(gdk::ModifierType::SHIFT_MASK), + &action, + ); + } + + Action::ZoomOutCenter => klass.install_action(&action, None, move |win, _, _| { + win.zoom_out_center(); + }), + + Action::ZoomInCenter => klass.install_action(&action, None, move |win, _, _| { + win.zoom_in_center(); + }), + + Action::ZoomOutCursor => { + klass.install_action(&action, None, move |win, _, _| { + win.zoom_out_cursor(); + }); + klass.add_binding_action(gdk::Key::minus, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::minus, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + klass.add_binding_action( + gdk::Key::KP_Subtract, + gdk::ModifierType::empty(), + &action, + ); + klass.add_binding_action( + gdk::Key::KP_Subtract, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + } + + Action::ZoomInCursor => { + klass.install_action(&action, None, move |win, _, _| { + win.zoom_in_cursor(); + }); + klass.add_binding_action(gdk::Key::equal, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::equal, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + klass.add_binding_action(gdk::Key::plus, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::plus, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + klass.add_binding_action(gdk::Key::KP_Add, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::KP_Add, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + } + + Action::ZoomBestFit => { + klass.install_action(&action, None, move |win, _, _| { + win.zoom_best_fit(); + }); + klass.add_binding_action(gdk::Key::_0, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::_0, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + klass.add_binding_action(gdk::Key::KP_0, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::KP_0, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + } + + Action::ZoomToExact1 => { + klass.install_action(&action, None, move |win, _, _| { + win.zoom_to_exact(1.); + }); + klass.add_binding_action(gdk::Key::_1, gdk::ModifierType::empty(), &action); + klass.add_binding_action(gdk::Key::KP_1, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::_1, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + klass.add_binding_action( + gdk::Key::KP_1, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + } + + Action::ZoomToExact2 => { + klass.install_action(&action, None, move |win, _, _| { + win.zoom_to_exact(2.); + }); + klass.add_binding_action(gdk::Key::_2, gdk::ModifierType::empty(), &action); + klass.add_binding_action(gdk::Key::KP_2, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::_2, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + klass.add_binding_action( + gdk::Key::KP_2, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + } + + // Misc + Action::Open => { + klass.install_action_async(&action, None, |win, _, _| async move { + win.pick_file().await; + }); + klass.add_binding_action(gdk::Key::O, gdk::ModifierType::CONTROL_MASK, &action); + } + + Action::OpenWith => { + klass.install_action_async(&action, None, |win, _, _| async move { + win.open_with().await; + }); + klass.add_binding_action( + gdk::Key::O, + gdk::ModifierType::CONTROL_MASK.union(gdk::ModifierType::SHIFT_MASK), + &action, + ); + } + + Action::Print => { + klass.install_action(&action, None, move |win, _, _| { + win.print(); + }); + klass.add_binding_action(gdk::Key::P, gdk::ModifierType::CONTROL_MASK, &action); + } + + Action::CopyImage => klass.install_action(&action, None, move |win, _, _| { + win.copy_image(); + }), + Action::SetBackground => { + klass.install_action_async(&action, None, |win, _, _| async move { + win.set_background().await; + }); + klass.add_binding_action( + gdk::Key::F8, + gdk::ModifierType::CONTROL_MASK, + &action, + ); + } + + Action::ToggleFullscreen => { + klass.install_action(&action, None, move |win, _, _| { + win.toggle_fullscreen(!win.is_fullscreened()); + }); + klass.add_binding_action(gdk::Key::F11, gdk::ModifierType::empty(), &action); + } + + Action::LeaveFullscreen => { + klass.install_action(&action, None, move |win, _, _| { + win.toggle_fullscreen(false); + }); + klass.add_binding_action(gdk::Key::Escape, gdk::ModifierType::empty(), &action); + } + + Action::Trash => { + klass.install_action_async(&action, None, |win, _, _| async move { + win.trash().await; + }); + klass.add_binding_action(gdk::Key::Delete, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::KP_Delete, + gdk::ModifierType::empty(), + &action, + ); + } + + Action::Delete => { + klass.install_action_async(&action, None, |win, _, _| async move { + win.delete().await; + }); + klass.add_binding_action( + gdk::Key::Delete, + gdk::ModifierType::SHIFT_MASK, + &action, + ); + klass.add_binding_action( + gdk::Key::KP_Delete, + gdk::ModifierType::SHIFT_MASK, + &action, + ); + } + + Action::ToggleProperties => { + klass.install_action(&action, None, move |win, _, _| { + win.imp() + .properties_button + .set_active(!win.imp().properties_button.is_active()); + }); + klass.add_binding_action(gdk::Key::F9, gdk::ModifierType::empty(), &action); + klass.add_binding_action( + gdk::Key::Return, + gdk::ModifierType::ALT_MASK, + &action, + ); + } + + Action::About => { + klass.install_action_async(&action, None, |win, _, _| async move { + win.show_about().await + }); + } + } + } + } +} + +impl Deref for ActionPartGlobal { + type Target = str; + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl Deref for Action { + type Target = str; + fn deref(&self) -> &Self::Target { + self.as_ref() + } +}