image_view: Don't rely on LpImage signals
This commit is contained in:
parent
0a3a6efa02
commit
8d3c9d70e7
5 changed files with 134 additions and 93 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2022-2024 Sophie Herold
|
// Copyright (c) 2022-2025 Sophie Herold
|
||||||
// Copyright (c) 2022 Christopher Davis
|
// Copyright (c) 2022 Christopher Davis
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -99,8 +99,11 @@ mod imp {
|
||||||
|
|
||||||
impl ObjectImpl for LpFileModel {
|
impl ObjectImpl for LpFileModel {
|
||||||
fn signals() -> &'static [Signal] {
|
fn signals() -> &'static [Signal] {
|
||||||
static SIGNALS: LazyLock<Vec<Signal>> =
|
static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| {
|
||||||
LazyLock::new(|| vec![Signal::builder("changed").build()]);
|
vec![Signal::builder("changed")
|
||||||
|
.param_types([glib::Type::VARIANT])
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
SIGNALS.as_ref()
|
SIGNALS.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +246,7 @@ impl LpFileModel {
|
||||||
self.imp().files.borrow().len()
|
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())
|
self.imp().files.borrow().contains_key(&file.uri())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,30 +339,37 @@ impl LpFileModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signal that notifies about added/removed files
|
/// Signal that notifies about added/removed files
|
||||||
pub fn connect_changed(&self, f: impl Fn() + 'static) {
|
pub fn connect_changed(&self, f: impl Fn(&FileEvent) + 'static) {
|
||||||
self.connect_local("changed", false, move |_| {
|
self.connect_local("changed", false, move |args| {
|
||||||
f();
|
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
|
None
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a file and signal changes
|
pub fn emmit_changed(&self, file_event: &FileEvent) {
|
||||||
///
|
self.emit_by_name::<()>("changed", &[&file_event.to_variant()]);
|
||||||
/// 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.
|
/// Insert a file
|
||||||
pub fn insert(&self, file: gio::File, mut changed: bool) {
|
async fn insert(&self, file: gio::File) -> bool {
|
||||||
let obj = self.clone();
|
let obj = self.clone();
|
||||||
glib::spawn_future_local(async move {
|
|
||||||
let entry = Entry::new(file.clone()).await;
|
let entry = Entry::new(file.clone()).await;
|
||||||
let mut files = obj.imp().files.borrow_mut();
|
let mut files = obj.imp().files.borrow_mut();
|
||||||
changed |= files.insert(file.uri(), entry).is_none();
|
let changed = files.insert(file.uri(), entry).is_none();
|
||||||
if changed {
|
if changed {
|
||||||
Self::sort(&mut files);
|
Self::sort(&mut files);
|
||||||
drop(files);
|
drop(files);
|
||||||
obj.emit_by_name::<()>("changed", &[]);
|
|
||||||
}
|
}
|
||||||
});
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_monitor_cb(
|
fn file_monitor_cb(
|
||||||
|
|
@ -368,33 +378,83 @@ impl LpFileModel {
|
||||||
file_a: &gio::File,
|
file_a: &gio::File,
|
||||||
file_b: Option<&gio::File>,
|
file_b: Option<&gio::File>,
|
||||||
) {
|
) {
|
||||||
|
let uri_a = file_a.uri();
|
||||||
match event {
|
match event {
|
||||||
gio::FileMonitorEvent::Created
|
gio::FileMonitorEvent::Created
|
||||||
| gio::FileMonitorEvent::MovedIn
|
| gio::FileMonitorEvent::MovedIn
|
||||||
// Changing file content could theoretically make it an image
|
|
||||||
// by adding a magic byte
|
|
||||||
| gio::FileMonitorEvent::ChangesDoneHint
|
| gio::FileMonitorEvent::ChangesDoneHint
|
||||||
if Self::is_image_file(file_a) =>
|
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 {
|
if removed {
|
||||||
self.emit_by_name::<()>("changed", &[]);
|
self.emmit_changed(&FileEvent::Removed(file_a.uri().to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gio::FileMonitorEvent::Renamed => {
|
gio::FileMonitorEvent::Renamed => {
|
||||||
if let Some(file_b) = file_b {
|
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) {
|
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),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
||||||
|
|
@ -203,32 +203,48 @@ impl imp::LpImage {
|
||||||
file_a: &gio::File,
|
file_a: &gio::File,
|
||||||
file_b: Option<&gio::File>,
|
file_b: Option<&gio::File>,
|
||||||
) {
|
) {
|
||||||
|
let obj = self.obj().to_owned();
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
gio::FileMonitorEvent::Renamed => {
|
gio::FileMonitorEvent::Renamed => {
|
||||||
if let Some(file) = file_b.cloned() {
|
if let Some(file) = file_b.cloned() {
|
||||||
log::debug!("Moved to {}", file.uri());
|
log::debug!("Moved to {}", file.uri());
|
||||||
let obj = self.obj().to_owned();
|
|
||||||
|
|
||||||
// current file got replaced with a new one
|
// current file got replaced with a new one
|
||||||
let file_replace = obj.file().is_some_and(|x| x.equal(&file));
|
let file_replace = obj.file().is_some_and(|x| x.equal(&file));
|
||||||
if file_replace {
|
if file_replace {
|
||||||
log::debug!("Image got replaced {}", file.uri());
|
log::debug!("Image got replaced {}", file.uri());
|
||||||
// TODO: error handling is missing
|
|
||||||
glib::spawn_future_local(async move {
|
glib::spawn_future_local(async move {
|
||||||
obj.load(&file).await;
|
obj.load(&file).await;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
glib::spawn_future_local(async move {
|
glib::spawn_future_local(async move {
|
||||||
|
if obj.error().is_some() {
|
||||||
|
// Tmp files might be renamed quickly after creation. In this case
|
||||||
|
// the original load fails, because the filename is already invalid.
|
||||||
|
// Therefore reload on name change loading caused an error.
|
||||||
|
obj.load(&file).await;
|
||||||
|
} else {
|
||||||
obj.imp().set_file_changed(&file).await;
|
obj.imp().set_file_changed(&file).await;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
gio::FileMonitorEvent::MovedIn => {
|
||||||
|
log::debug!("Image got replaced by moving into dir {}", file_a.uri());
|
||||||
|
glib::spawn_future_local(glib::clone!(
|
||||||
|
#[strong]
|
||||||
|
file_a,
|
||||||
|
async move {
|
||||||
|
obj.load(&file_a).await;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
gio::FileMonitorEvent::ChangesDoneHint => {
|
gio::FileMonitorEvent::ChangesDoneHint => {
|
||||||
let obj = self.obj().to_owned();
|
let obj = self.obj().to_owned();
|
||||||
let file = file_a.clone();
|
let file = file_a.clone();
|
||||||
log::debug!("Image was edited {}", file.uri());
|
log::debug!("Image was edited {}", file.uri());
|
||||||
// TODO: error handling is missing
|
|
||||||
glib::spawn_future_local(async move {
|
glib::spawn_future_local(async move {
|
||||||
obj.load(&file).await;
|
obj.load(&file).await;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2023-2024 Sophie Herold
|
// Copyright (c) 2023-2025 Sophie Herold
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright (c) 2020-2023 Christopher Davis
|
// Copyright (c) 2020-2023 Christopher Davis
|
||||||
// Copyright (c) 2022-2024 Sophie Herold
|
// Copyright (c) 2022-2025 Sophie Herold
|
||||||
// Copyright (c) 2022 Maximiliano Sandoval R
|
// Copyright (c) 2022 Maximiliano Sandoval R
|
||||||
// Copyright (c) 2023 FineFindus
|
// Copyright (c) 2023 FineFindus
|
||||||
// Copyright (c) 2023 Huan Nguyen
|
// Copyright (c) 2023 Huan Nguyen
|
||||||
|
|
@ -43,7 +43,7 @@ use gtk::CompositeTemplate;
|
||||||
|
|
||||||
use crate::decoder::DecoderError;
|
use crate::decoder::DecoderError;
|
||||||
use crate::deps::*;
|
use crate::deps::*;
|
||||||
use crate::file_model::LpFileModel;
|
use crate::file_model::{FileEvent, LpFileModel};
|
||||||
use crate::util::gettext::*;
|
use crate::util::gettext::*;
|
||||||
use crate::util::{self, Direction, Position};
|
use crate::util::{self, Direction, Position};
|
||||||
use crate::widgets::{LpImage, LpImagePage, LpPrint, LpSlidingView};
|
use crate::widgets::{LpImage, LpImagePage, LpPrint, LpSlidingView};
|
||||||
|
|
@ -174,29 +174,6 @@ mod imp {
|
||||||
let current_image_signal_group = obj.current_image_signals();
|
let current_image_signal_group = obj.current_image_signals();
|
||||||
|
|
||||||
let view = &*obj;
|
let view = &*obj;
|
||||||
current_image_signal_group.connect_closure(
|
|
||||||
"notify::file",
|
|
||||||
true,
|
|
||||||
glib::closure_local!(
|
|
||||||
#[watch]
|
|
||||||
view,
|
|
||||||
move |_: LpImage, _: glib::ParamSpec| {
|
|
||||||
view.current_image_path_changed();
|
|
||||||
}
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
current_image_signal_group.connect_closure(
|
|
||||||
"notify::is-deleted",
|
|
||||||
true,
|
|
||||||
glib::closure_local!(
|
|
||||||
#[watch]
|
|
||||||
view,
|
|
||||||
move |_: LpImage, _: glib::ParamSpec| {
|
|
||||||
view.current_image_path_changed();
|
|
||||||
}
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
current_image_signal_group.connect_closure(
|
current_image_signal_group.connect_closure(
|
||||||
"notify::zoom-target",
|
"notify::zoom-target",
|
||||||
|
|
@ -687,16 +664,16 @@ impl LpImageView {
|
||||||
model.connect_changed(glib::clone!(
|
model.connect_changed(glib::clone!(
|
||||||
#[weak(rename_to = obj)]
|
#[weak(rename_to = obj)]
|
||||||
self,
|
self,
|
||||||
move || obj.model_content_changed_cb()
|
move |change| obj.model_content_changed_cb(change)
|
||||||
));
|
));
|
||||||
self.imp().model.replace(model);
|
self.imp().model.replace(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle files are added or removed from directory
|
/// Handle files are added or removed from directory
|
||||||
fn model_content_changed_cb(&self) {
|
fn model_content_changed_cb(&self, file_event: &FileEvent) {
|
||||||
// Animate to image that was restored from trash
|
// Animate to image that was restored from trash
|
||||||
if let Some(trash_restore) = self.trash_restore() {
|
if let Some(trash_restore) = self.trash_restore() {
|
||||||
if self.model().contains(&trash_restore) {
|
if self.model().contains_file(&trash_restore) {
|
||||||
self.set_trash_restore(gio::File::NONE);
|
self.set_trash_restore(gio::File::NONE);
|
||||||
self.update_sliding_view(&trash_restore);
|
self.update_sliding_view(&trash_restore);
|
||||||
self.scroll_sliding_view(&trash_restore, true);
|
self.scroll_sliding_view(&trash_restore, true);
|
||||||
|
|
@ -708,31 +685,22 @@ impl LpImageView {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// LpImage did not get the update yet
|
match file_event {
|
||||||
// Update will be handled by current_image_path_changed
|
FileEvent::Removed(removed) if removed == ¤t_file.uri() => {
|
||||||
if !self.model().contains(¤t_file) {
|
self.sliding_view().scroll_to_neighbor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
FileEvent::Moved(src, target) => {
|
||||||
|
if let Some(page) = self.sliding_view().get(&gio::File::for_uri(src)) {
|
||||||
|
page.image().init(&gio::File::for_uri(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.imp().preserve_content.get() {
|
||||||
self.update_sliding_view(¤t_file);
|
self.update_sliding_view(¤t_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle current image being moved or deleted
|
|
||||||
///
|
|
||||||
/// This is handled separately since we want to delete animations and
|
|
||||||
/// want to still show the same image if renamed.
|
|
||||||
fn current_image_path_changed(&self) {
|
|
||||||
if let Some(image) = self.current_page().map(|x| x.image()) {
|
|
||||||
if image.is_deleted() {
|
|
||||||
self.sliding_view().scroll_to_neighbor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(current_file) = self.current_file() {
|
|
||||||
if self.model().contains(¤t_file) && !self.imp().preserve_content.get() {
|
|
||||||
self.update_sliding_view(¤t_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_image(&self) -> Option<LpImage> {
|
pub fn current_image(&self) -> Option<LpImage> {
|
||||||
|
|
@ -757,14 +725,11 @@ impl LpImageView {
|
||||||
self.imp()
|
self.imp()
|
||||||
.sliding_view
|
.sliding_view
|
||||||
.current_page()
|
.current_page()
|
||||||
.map(|x| x.file().uri())
|
.map(|x: LpImagePage| x.file().uri())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_file(&self) -> Option<gio::File> {
|
pub fn current_file(&self) -> Option<gio::File> {
|
||||||
self.imp()
|
self.current_uri().map(|x| gio::File::for_uri(&x))
|
||||||
.sliding_view
|
|
||||||
.current_page()
|
|
||||||
.and_then(|x| x.image().file())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drag_source(&self) -> gtk::DragSource {
|
pub fn drag_source(&self) -> gtk::DragSource {
|
||||||
|
|
@ -819,8 +784,8 @@ impl LpImageView {
|
||||||
|
|
||||||
pub async fn set_background(&self) -> anyhow::Result<()> {
|
pub async fn set_background(&self) -> anyhow::Result<()> {
|
||||||
let uri = self
|
let uri = self
|
||||||
.current_file()
|
.current_uri()
|
||||||
.and_then(|f| url::Url::parse(f.uri().as_str()).ok())
|
.and_then(|x| url::Url::parse(x.as_str()).ok())
|
||||||
.context("Invalid URL for background image")?;
|
.context("Invalid URL for background image")?;
|
||||||
let native = self.native().context("View should have a GtkNative")?;
|
let native = self.native().context("View should have a GtkNative")?;
|
||||||
let id = WindowIdentifier::from_native(&native).await;
|
let id = WindowIdentifier::from_native(&native).await;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue