editing: Add error handling for saving files

This commit is contained in:
Sophie Herold 2024-12-18 01:29:14 +01:00
parent 3f82a5f435
commit 5b51237203
8 changed files with 167 additions and 29 deletions

View file

@ -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")]

View file

@ -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,34 +100,57 @@ 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(&current_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
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,
)
}
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(
result.unwrap().get().unwrap(),
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,
);
}
}
@ -132,6 +159,11 @@ mod imp {
}
}
}
}
}
}
}
}
glib::wrapper! {
pub struct LpEditWindow(ObjectSubclass<imp::LpEditWindow>)

View file

@ -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<gtk::Button>,
#[template_child]
pub(super) report: TemplateChild<gtk::Button>,
#[template_child]
pub(super) preference_group: TemplateChild<adw::PreferencesGroup>,
}
#[glib::object_subclass]
@ -65,11 +68,17 @@ glib::wrapper! {
}
impl LpErrorDetails {
pub fn new(root: &impl IsA<gtk::Widget>, text: &str) -> Self {
pub fn new(root: &impl IsA<gtk::Widget>, text: &str, type_: ErrorType) -> Self {
let obj = glib::Object::new::<Self>();
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::<gtk::Window>(),
gio::Cancellable::NONE,
|_| {},
@ -98,3 +112,8 @@ impl LpErrorDetails {
obj
}
}
pub enum ErrorType {
Loader,
General,
}

View file

@ -15,8 +15,8 @@
<property name="content">
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<property name="description" translatable="true">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.</property>
<object class="AdwPreferencesGroup" id="preference_group">
<property name="description" translatable="true">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.</property>
</object>
</child>
<child>

View file

@ -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, &gtk::Snapshot::new());
} else {

View file

@ -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,
);
}
));

View file

@ -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,
);
}

View file

@ -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<str>, 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: &gtk::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 <imp::LpWindow as ObjectSubclass>::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);
}
},
);
}
}
}
}
}