window: Support dropping multiple files

This allows to scroll through all dropped files.
No further images from directories are loaded in
this mode.

Closes #35
This commit is contained in:
Sophie Herold 2023-04-18 01:52:06 +02:00
parent e710419a22
commit aca49427a1
4 changed files with 53 additions and 53 deletions

View file

@ -73,7 +73,7 @@ mod imp {
let application = self.obj(); let application = self.obj();
for file in files { for file in files {
let win = LpWindow::new(&*application); let win = LpWindow::new(&*application);
win.set_image_from_file(file); win.image_view().set_image_from_file(file.clone());
} }
} }
} }

View file

@ -70,12 +70,18 @@ impl Default for LpFileModel {
impl LpFileModel { impl LpFileModel {
/// Create with a single element /// Create with a single element
pub fn from_file(file: &gio::File) -> Self { pub fn from_file(file: gio::File) -> Self {
Self::from_files(vec![file])
}
pub fn from_files(files: Vec<gio::File>) -> Self {
let model = Self::default(); let model = Self::default();
{ {
let mut vec = model.imp().files.borrow_mut(); let mut vec = model.imp().files.borrow_mut();
vec.insert(file.uri(), file.clone()); for file in files {
vec.insert(file.uri(), file);
}
} }
model model

View file

@ -227,15 +227,35 @@ glib::wrapper! {
#[gtk::template_callbacks] #[gtk::template_callbacks]
impl LpImageView { impl LpImageView {
pub fn set_image_from_file(&self, file: &gio::File) { pub fn set_image_from_file(&self, file: gio::File) {
// Add image to recently used file. Does not work in Flatpaks: // Add image to recently used file. Does not work in Flatpaks:
// <https://github.com/flatpak/xdg-desktop-portal/issues/215> // <https://github.com/flatpak/xdg-desktop-portal/issues/215>
gtk::RecentManager::default().add_item(&file.uri()); gtk::RecentManager::default().add_item(&file.uri());
if let Err(err) = self.load_file(file) { self.load_file(file);
log::error!("Failed to load path: {err}"); }
self.activate_action("win.show-toast", Some(&(err.to_string(), 1).to_variant()))
.unwrap(); pub fn set_images_from_files(&self, files: Vec<gio::File>) {
let recent_manager = gtk::RecentManager::default();
for file in files.iter() {
recent_manager.add_item(&file.uri());
}
self.load_files(files);
}
fn load_files(&self, files: Vec<gio::File>) {
let sliding_view = self.sliding_view();
if let Some(first) = files.first().cloned() {
let model = LpFileModel::from_files(files);
self.set_model(model);
let page = LpImagePage::from_file(&first);
sliding_view.clear();
sliding_view.append(&page);
self.update_sliding_view(&first);
} else {
log::error!("File list was empty");
} }
} }
@ -243,8 +263,7 @@ impl LpImageView {
// that holds a `gio::File` for each child within the same directory as the // that holds a `gio::File` for each child within the same directory as the
// file we pass. This model will update with changes to the directory, // file we pass. This model will update with changes to the directory,
// and in turn we'll update our `adw::Carousel`. // and in turn we'll update our `adw::Carousel`.
fn load_file(&self, file: &gio::File) -> anyhow::Result<()> { fn load_file(&self, file: gio::File) {
let sliding_view = self.sliding_view();
let directory = file.parent(); let directory = file.parent();
let is_same_directory = if let (Some(d1), Some(d2)) = (&directory, self.model().directory()) let is_same_directory = if let (Some(d1), Some(d2)) = (&directory, self.model().directory())
@ -256,18 +275,15 @@ impl LpImageView {
if is_same_directory { if is_same_directory {
log::debug!("Re-using old model and navigating to the current file"); log::debug!("Re-using old model and navigating to the current file");
self.navigate_to_file(file); self.navigate_to_file(&file);
return Ok(()); return;
} }
let model = LpFileModel::from_file(file); let model = LpFileModel::from_file(file.clone());
self.set_model(model); self.set_model(model);
log::debug!("New model created"); log::debug!("New model created");
let page = LpImagePage::from_file(file); self.update_sliding_view(&file);
sliding_view.clear();
sliding_view.append(&page);
// List other files in directory // List other files in directory
if let Some(directory) = directory { if let Some(directory) = directory {
@ -282,8 +298,6 @@ impl LpImageView {
obj.update_sliding_view(&file); obj.update_sliding_view(&file);
})); }));
} }
Ok(())
} }
/// Move forward or backwards /// Move forward or backwards

View file

@ -325,38 +325,26 @@ mod imp {
// We've added type annotations here, and written it as `let list: gdk::FileList = ...`, // We've added type annotations here, and written it as `let list: gdk::FileList = ...`,
// but you might also see places where type arguments are used. // but you might also see places where type arguments are used.
// This line could have been written as `let list = value.get::<gdk::FileList>().unwrap()`. // This line could have been written as `let list = value.get::<gdk::FileList>().unwrap()`.
let list: gdk::FileList = match value.get() { let mut files = match value.get::<gdk::FileList>() {
Ok(list) => list, Ok(list) => list.files(),
Err(err) => { Err(err) => {
log::error!("Issue with drop value: {err}"); log::error!("Issue with drop value: {err}");
return false; return false;
} }
}; };
// TODO: Handle this like EOG and make a "directory" out of the given files if files.len() > 1 {
let file = list.files().get(0).unwrap().clone(); obj.image_view().set_images_from_files(files);
let info = util::query_attributes( } else if let Some(file) = files.pop() {
&file, obj.image_view().set_image_from_file(file);
vec![
gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
],
)
.expect("Could not query file info");
if info
.content_type()
.map(|t| t.to_string())
.filter(|t| t.starts_with("image/"))
.is_some() {
obj.set_image_from_file(&file);
} else { } else {
obj.show_toast( log::error!("Dropped FileList was empty");
gettext_f("“{}” is not a valid image.", &[&info.display_name()]), return false;
adw::ToastPriority::High,
);
} }
// Maybe one day this will actually work
obj.present();
true true
}), }),
); );
@ -434,7 +422,7 @@ impl LpWindow {
.build(); .build();
if let Ok(file) = chooser.open_future(Some(self)).await { if let Ok(file) = chooser.open_future(Some(self)).await {
self.set_image_from_file(&file); self.image_view().set_image_from_file(file);
} else { } else {
log::debug!("File dialog canceled or file not readable"); log::debug!("File dialog canceled or file not readable");
} }
@ -506,7 +494,7 @@ impl LpWindow {
spawn(async move { spawn(async move {
let result = crate::util::untrash(&path).await; let result = crate::util::untrash(&path).await;
match result { match result {
Ok(()) => win.image_view().set_image_from_file(&gio::File::for_path(&path)), Ok(()) => win.image_view().set_image_from_file(gio::File::for_path(&path)),
Err(err) => { Err(err) => {
log::error!("Failed to untrash {path:?}: {err}"); log::error!("Failed to untrash {path:?}: {err}");
win.show_toast( win.show_toast(
@ -569,7 +557,7 @@ impl LpWindow {
} }
} }
fn image_view(&self) -> LpImageView { pub fn image_view(&self) -> LpImageView {
self.imp().image_view.clone() self.imp().image_view.clone()
} }
@ -582,14 +570,6 @@ impl LpWindow {
imp.toast_overlay.add_toast(toast); imp.toast_overlay.add_toast(toast);
} }
pub fn set_image_from_file(&self, file: &gio::File) {
let imp = self.imp();
log::debug!("Loading file: {}", file.uri());
imp.image_view.set_image_from_file(file);
self.set_actions_enabled(true);
}
pub fn set_actions_enabled(&self, enabled: bool) { pub fn set_actions_enabled(&self, enabled: bool) {
self.action_set_enabled("win.open-with", enabled); self.action_set_enabled("win.open-with", enabled);
self.action_set_enabled("win.set-background", enabled); self.action_set_enabled("win.set-background", enabled);