diff --git a/src/widgets/edit/crop.rs b/src/widgets/edit/crop.rs index d044f38..798df2f 100644 --- a/src/widgets/edit/crop.rs +++ b/src/widgets/edit/crop.rs @@ -22,7 +22,7 @@ use adw::subclass::prelude::*; use adw::{glib, gtk}; use crate::widgets::edit::LpEditCropSelection; -use crate::widgets::{edit_window, LpEditWindow, LpImage}; +use crate::widgets::LpEditWindow; #[derive(Debug, Clone, Copy, Default, glib::Enum)] #[enum_type(name = "LpAspectRatio")] diff --git a/src/widgets/edit_window.rs b/src/widgets/edit_window.rs index f6a56cd..5a39bf7 100644 --- a/src/widgets/edit_window.rs +++ b/src/widgets/edit_window.rs @@ -25,8 +25,12 @@ use glycin::Operations; use super::edit::LpEditCrop; use super::{LpImage, LpWindow}; use crate::deps::*; +use crate::util::gettext::*; mod imp { + + use crate::widgets::error_details::ErrorType; + use super::*; #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)] @@ -96,35 +100,63 @@ mod imp { impl LpEditWindow { async fn save_image(&self) { - dbg!("save"); let obj = self.obj(); if let Some(current_file) = obj.original_image().file() { let file_dialog = gtk::FileDialog::new(); - file_dialog.set_initial_file(obj.original_image().file().as_ref()); + file_dialog.set_initial_file(Some(¤t_file)); match file_dialog.save_future(Some(&obj.window())).await { Err(err) => { log::error!("{}", err); } Ok(new_file) => { - dbg!(new_file.path()); let editor = glycin::Editor::new(current_file); if let Some(operations) = obj.operations() { + log::debug!("Computing edited image."); let result = editor.apply_complete(&operations).await; - dbg!(&result); - - dbg!( - new_file - .replace_contents_future( - result.unwrap().get().unwrap(), - None, - true, - gio::FileCreateFlags::NONE, + match result { + Err(err) => { + log::warn!("Failed to edit image: {err}"); + obj.window().show_error( + &gettext("Failed to edit image."), + &format!( + "Failed to edit image:\n\n{err}\n\n{operations:#?}" + ), + ErrorType::General, ) - .await - ); + } + Ok(binary_data) => { + log::debug!("Saving edited image to '{}'", new_file.uri()); + match binary_data.get() { + Err(err) => { + obj.window().show_error( + &gettext("Failed to Save Image"), + &format!("Failed to get binary data: {err}"), + ErrorType::General, + ); + } + Ok(data) => { + if let Err(err) = new_file + .replace_contents_future( + data, + None, + true, + gio::FileCreateFlags::NONE, + ) + .await + { + obj.window().show_error( + &gettext("Failed to Save Image"), + &format!("Failed to write file:\n\n{err:?}"), + ErrorType::General, + ); + } + } + } + } + } } } } diff --git a/src/widgets/error_details.rs b/src/widgets/error_details.rs index 687d3cb..44e4d5b 100644 --- a/src/widgets/error_details.rs +++ b/src/widgets/error_details.rs @@ -20,6 +20,7 @@ use adw::subclass::prelude::*; use gtk::CompositeTemplate; use crate::deps::*; +use crate::util::gettext::*; mod imp { use super::*; @@ -33,6 +34,8 @@ mod imp { pub(super) copy: TemplateChild, #[template_child] pub(super) report: TemplateChild, + #[template_child] + pub(super) preference_group: TemplateChild, } #[glib::object_subclass] @@ -65,11 +68,17 @@ glib::wrapper! { } impl LpErrorDetails { - pub fn new(root: &impl IsA, text: &str) -> Self { + pub fn new(root: &impl IsA, text: &str, type_: ErrorType) -> Self { let obj = glib::Object::new::(); let imp = obj.imp(); imp.message.buffer().set_text(text); + if matches!(type_, ErrorType::General) { + imp.preference_group.set_title( + &gettext( "The following error occured. If you think this is an issue with the program, please include this information when you report the error."), + ); + } + imp.copy.connect_clicked(glib::clone!( #[weak] obj, @@ -82,11 +91,16 @@ impl LpErrorDetails { } )); + let issue_uri = match type_ { + ErrorType::Loader => "https://gitlab.gnome.org/GNOME/glycin/-/issues", + ErrorType::General => "https://gitlab.gnome.org/GNOME/loupe/-/issues", + }; + imp.report.connect_clicked(glib::clone!( #[weak] obj, move |_| { - gtk::UriLauncher::new("https://gitlab.gnome.org/GNOME/glycin/-/issues").launch( + gtk::UriLauncher::new(issue_uri).launch( obj.root().and_downcast_ref::(), gio::Cancellable::NONE, |_| {}, @@ -98,3 +112,8 @@ impl LpErrorDetails { obj } } + +pub enum ErrorType { + Loader, + General, +} diff --git a/src/widgets/error_details.ui b/src/widgets/error_details.ui index 2e17dda..380b853 100644 --- a/src/widgets/error_details.ui +++ b/src/widgets/error_details.ui @@ -15,8 +15,8 @@ - - The image loader reported the following when trying to load the image. Please include this information when you report an error. If possible, please also provide the image that causes the issue. + + The image loader reported the following when trying to load or edit the image. Please include this information when you report an error. If possible, please also provide the image that causes the issue. diff --git a/src/widgets/image/editing.rs b/src/widgets/image/editing.rs index b0be3eb..de30a71 100644 --- a/src/widgets/image/editing.rs +++ b/src/widgets/image/editing.rs @@ -15,7 +15,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -use glycin::Operations; use tiling::RenderOptions; use super::*; @@ -63,6 +62,7 @@ impl LpImage { scaling_filter: gsk::ScalingFilter::Nearest, }, ); + if let Some(node) = tmp_snapshot.to_node() { imp.apply_operations(node, >k::Snapshot::new()); } else { diff --git a/src/widgets/image_page.rs b/src/widgets/image_page.rs index 727f747..79bcee3 100644 --- a/src/widgets/image_page.rs +++ b/src/widgets/image_page.rs @@ -255,7 +255,11 @@ impl LpImagePage { #[weak(rename_to = obj)] self, move |_| { - LpErrorDetails::new(&obj.root().unwrap(), &err); + LpErrorDetails::new( + &obj.root().unwrap(), + &err, + super::error_details::ErrorType::Loader, + ); } )); } @@ -267,7 +271,11 @@ impl LpImagePage { #[weak(rename_to = obj)] self, move |_| { - LpErrorDetails::new(&obj.root().unwrap(), &err); + LpErrorDetails::new( + &obj.root().unwrap(), + &err, + super::error_details::ErrorType::Loader, + ); } )); diff --git a/src/widgets/image_window.rs b/src/widgets/image_window.rs index 260d810..13f5cd8 100644 --- a/src/widgets/image_window.rs +++ b/src/widgets/image_window.rs @@ -586,7 +586,7 @@ impl LpImageWindow { log::error!("Failed to copy to clipboard: {}", e); } else { self.window().show_toast( - gettext("Image copied to clipboard"), + &gettext("Image copied to clipboard"), adw::ToastPriority::High, ); } @@ -623,7 +623,7 @@ impl LpImageWindow { if let Err(err) = result { log::error!("Failed to untrash {path:?}: {err}"); obj.window().show_toast( - gettext("Failed to restore image from trash"), + &gettext("Failed to restore image from trash"), adw::ToastPriority::High, ); } @@ -639,7 +639,7 @@ impl LpImageWindow { } else { log::error!("Failed to delete file {path:?}: {err}"); self.window().show_toast( - gettext("Failed to move image to trash"), + &gettext("Failed to move image to trash"), adw::ToastPriority::Normal, ); } @@ -685,7 +685,7 @@ impl LpImageWindow { if let Err(err) = result { log::error!("Failed to delete file {path:?}: {err}"); self.window().show_toast( - gettext("Failed to delete image"), + &gettext("Failed to delete image"), adw::ToastPriority::Normal, ); } diff --git a/src/widgets/window.rs b/src/widgets/window.rs index 39e59cc..399a514 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -25,9 +25,13 @@ use strum::IntoEnumIterator; use crate::config; use crate::deps::*; +use crate::util::gettext::*; use crate::util::Direction; use crate::widgets::{LpEditWindow, LpImageView, LpImageWindow}; +use super::error_details::ErrorType; +use super::LpErrorDetails; + /// Show window after X milliseconds even if image dimensions are not known yet const SHOW_WINDOW_AFTER: u64 = 2000; @@ -95,6 +99,8 @@ mod imp { }, ); + WindowAction::init_actions_and_bindings(klass); + ActionPartGlobal::init_actions_and_bindings(klass); } @@ -190,15 +196,42 @@ impl LpWindow { } } - pub fn show_toast(&self, text: impl AsRef, priority: adw::ToastPriority) { + pub fn show_toast(&self, text: &str, priority: adw::ToastPriority) { let imp = self.imp(); - let toast = adw::Toast::new(text.as_ref()); + let toast = adw::Toast::new(text); toast.set_priority(priority); imp.toast_overlay.add_toast(toast); } + pub fn show_error(&self, stub: &str, details: &str, type_: ErrorType) { + let imp = self.imp(); + + let action = match type_ { + ErrorType::General => WindowAction::ShowError, + ErrorType::Loader => WindowAction::ShowLoaderError, + }; + + let toast = adw::Toast::builder() + .title(stub) + .priority(adw::ToastPriority::High) + .button_label(gettext("Show Details")) + .action_name(action.to_string()) + .action_target(&details.to_variant()) + .build(); + + imp.toast_overlay.add_toast(toast); + } + + pub fn show_error_details(&self, details: &str) { + LpErrorDetails::new(self, details, ErrorType::General); + } + + pub fn show_loader_error_details(&self, details: &str) { + LpErrorDetails::new(self, details, ErrorType::Loader); + } + pub async fn show_about(&self) { let about = crate::about::dialog().await; about.present(Some(self)); @@ -255,6 +288,13 @@ pub enum ActionPartGlobal { PanLeft, } +impl Deref for ActionPartGlobal { + type Target = str; + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + impl ActionPartGlobal { pub fn add_accels(application: >k::Application) { for action in Self::iter() { @@ -334,9 +374,48 @@ impl ActionPartGlobal { } } -impl Deref for ActionPartGlobal { +#[derive(strum::Display, strum::AsRefStr, strum::EnumIter)] +enum WindowAction { + #[strum(to_string = "win.show-error")] + ShowError, + #[strum(to_string = "win.show-loader-error")] + ShowLoaderError, +} + +impl Deref for WindowAction { type Target = str; fn deref(&self) -> &Self::Target { self.as_ref() } } + +impl WindowAction { + pub fn init_actions_and_bindings(klass: &mut ::Class) { + for action in Self::iter() { + match action { + Self::ShowError => { + klass.install_action( + &action, + Some(VariantTy::STRING), + move |win, _, variant| { + if let Some(message) = variant.and_then(String::from_variant) { + win.show_error_details(&message); + } + }, + ); + } + Self::ShowLoaderError => { + klass.install_action( + &action, + Some(VariantTy::STRING), + move |win, _, variant| { + if let Some(message) = variant.and_then(String::from_variant) { + win.show_loader_error_details(&message); + } + }, + ); + } + } + } + } +}