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:
parent
e710419a22
commit
aca49427a1
4 changed files with 53 additions and 53 deletions
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue