editing: Add error handling for saving files
This commit is contained in:
parent
3f82a5f435
commit
5b51237203
8 changed files with 167 additions and 29 deletions
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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(¤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
|
||||
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>)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: >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 <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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue