diff --git a/data/resources/icons/scalable/actions/crop-symbolic.svg b/data/resources/icons/scalable/actions/crop-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..422c9001874df70f43b71dd59c088da426f5cd11
--- /dev/null
+++ b/data/resources/icons/scalable/actions/crop-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/data/resources/style.css b/data/resources/style.css
index 0437685463e04ba844eb169c4d3eb3ec3a35807d..d3af62406983fc6859f5d1a2ef5c8c50a94d9db3 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -85,4 +85,9 @@ dialog.error-details textview {
.error-message {
padding: 12px;
font-size: 0.95em;
+}
+
+menubutton.suggested-action button {
+ padding-left: 17px;
+ padding-right: 17px;
}
\ No newline at end of file
diff --git a/src/file_model.rs b/src/file_model.rs
index 09f2580818aff98dfcf20271d9cd1681ccdb0886..e79e9708541455451bdc82c36dbcd54fed3fd18f 100644
--- a/src/file_model.rs
+++ b/src/file_model.rs
@@ -1,4 +1,4 @@
-// Copyright (c) 2022-2024 Sophie Herold
+// Copyright (c) 2022-2025 Sophie Herold
// Copyright (c) 2022 Christopher Davis
//
// This program is free software: you can redistribute it and/or modify
@@ -99,8 +99,11 @@ mod imp {
impl ObjectImpl for LpFileModel {
fn signals() -> &'static [Signal] {
- static SIGNALS: LazyLock> =
- LazyLock::new(|| vec![Signal::builder("changed").build()]);
+ static SIGNALS: LazyLock> = LazyLock::new(|| {
+ vec![Signal::builder("changed")
+ .param_types([glib::Type::VARIANT])
+ .build()]
+ });
SIGNALS.as_ref()
}
}
@@ -243,7 +246,7 @@ impl LpFileModel {
self.imp().files.borrow().len()
}
- pub fn contains(&self, file: &gio::File) -> bool {
+ pub fn contains_file(&self, file: &gio::File) -> bool {
self.imp().files.borrow().contains_key(&file.uri())
}
@@ -336,30 +339,37 @@ impl LpFileModel {
}
/// Signal that notifies about added/removed files
- pub fn connect_changed(&self, f: impl Fn() + 'static) {
- self.connect_local("changed", false, move |_| {
- f();
+ pub fn connect_changed(&self, f: impl Fn(&FileEvent) + 'static) {
+ self.connect_local("changed", false, move |args| {
+ if let Some(file_event) = args
+ .get(1)
+ .and_then(|x| x.get().ok())
+ .and_then(|x| FileEvent::from_variant(&x))
+ {
+ f(&file_event);
+ } else {
+ log::error!("Failed to read arguments from 'changed' signal.");
+ }
+
None
});
}
- /// Insert a file and signal changes
- ///
- /// Setting `changed` to true will always trigger a `notify::changed``
- /// notification. Otherwise, a change signal is only sent if no file with
- /// the same URI existed before.
- pub fn insert(&self, file: gio::File, mut changed: bool) {
+ pub fn emmit_changed(&self, file_event: &FileEvent) {
+ self.emit_by_name::<()>("changed", &[&file_event.to_variant()]);
+ }
+
+ /// Insert a file
+ async fn insert(&self, file: gio::File) -> bool {
let obj = self.clone();
- glib::spawn_future_local(async move {
- let entry = Entry::new(file.clone()).await;
- let mut files = obj.imp().files.borrow_mut();
- changed |= files.insert(file.uri(), entry).is_none();
- if changed {
- Self::sort(&mut files);
- drop(files);
- obj.emit_by_name::<()>("changed", &[]);
- }
- });
+ let entry = Entry::new(file.clone()).await;
+ let mut files = obj.imp().files.borrow_mut();
+ let changed = files.insert(file.uri(), entry).is_none();
+ if changed {
+ Self::sort(&mut files);
+ drop(files);
+ }
+ changed
}
fn file_monitor_cb(
@@ -368,33 +378,83 @@ impl LpFileModel {
file_a: &gio::File,
file_b: Option<&gio::File>,
) {
+ let uri_a = file_a.uri();
match event {
gio::FileMonitorEvent::Created
| gio::FileMonitorEvent::MovedIn
- // Changing file content could theoretically make it an image
- // by adding a magic byte
| gio::FileMonitorEvent::ChangesDoneHint
if Self::is_image_file(file_a) =>
{
- self.insert(file_a.clone(), false);
+ // ^^^^
+ // Changing file content could theoretically make it an image
+ // by adding a magic byte
+
+ log::debug!("File added: {uri_a}");
+
+ glib::spawn_future_local(glib::clone!(
+ #[strong(rename_to=obj)]
+ self,
+ #[strong]
+ file_a,
+ async move {
+ let changed = obj.insert(file_a.clone()).await;
+ if changed {
+ obj.emmit_changed(&FileEvent::New(uri_a.to_string()));
+ }
+ }
+ ));
}
- gio::FileMonitorEvent::Deleted | gio::FileMonitorEvent::MovedOut | gio::FileMonitorEvent::Unmounted => {
- let removed = self.imp().files.borrow_mut().shift_remove(&file_a.uri()).is_some();
+ gio::FileMonitorEvent::Deleted
+ | gio::FileMonitorEvent::MovedOut
+ | gio::FileMonitorEvent::Unmounted => {
+ log::debug!("File removed: {}", file_a.uri());
+ let removed = self
+ .imp()
+ .files
+ .borrow_mut()
+ .shift_remove(&file_a.uri())
+ .is_some();
if removed {
- self.emit_by_name::<()>("changed", &[]);
+ self.emmit_changed(&FileEvent::Removed(file_a.uri().to_string()));
}
}
gio::FileMonitorEvent::Renamed => {
if let Some(file_b) = file_b {
{
- let changed = self.imp().files.borrow_mut().shift_remove(&file_a.uri()).is_some();
+ let uri_b = file_b.uri();
+ log::debug!("File moved from '{uri_a}' to '{uri_b}'");
+ let mut changed =
+ self.imp().files.borrow_mut().shift_remove(&uri_a).is_some();
+
if Self::is_image_file(file_b) {
- self.insert(file_b.clone(), changed);
+ glib::spawn_future_local(glib::clone!(
+ #[strong(rename_to=obj)]
+ self,
+ #[strong]
+ file_b,
+ async move {
+ changed |= obj.insert(file_b.clone()).await;
+
+ if changed {
+ obj.emmit_changed(&FileEvent::Moved(
+ uri_a.to_string(),
+ uri_b.to_string(),
+ ));
+ }
+ }
+ ));
}
}
}
}
- _ => {},
+ _ => {}
}
}
}
+
+#[derive(Debug, glib::Variant)]
+pub enum FileEvent {
+ New(String),
+ Removed(String),
+ Moved(String, String),
+}
diff --git a/src/widgets/edit_window.rs b/src/widgets/edit_window.rs
index 9577a7ff05d5abab6caf9097200da66be95ee166..cd9b01b5b9ced2cd6c1fcf87cca4e403d53f1a27 100644
--- a/src/widgets/edit_window.rs
+++ b/src/widgets/edit_window.rs
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Sophie Herold
+// Copyright (c) 2024-2025 Sophie Herold
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -266,11 +266,6 @@ impl LpEditWindow {
let done_sensitive = operations
.as_ref()
.is_some_and(|x| !x.operations().is_empty());
- if done_sensitive {
- imp.done.add_css_class("suggested-action");
- } else {
- imp.done.remove_css_class("suggested-action");
- }
imp.done.set_sensitive(done_sensitive);
imp.operations.replace(operations);
diff --git a/src/widgets/edit_window.ui b/src/widgets/edit_window.ui
index 6cf5c9b90bd95ad0f72e384a1370a6796b50bc90..3c3422a77ce823aa3af969c84a10c4846952ffac 100644
--- a/src/widgets/edit_window.ui
+++ b/src/widgets/edit_window.ui
@@ -20,7 +20,7 @@
true
false