Fix drag-and-drop and file manager integration
This commit is contained in:
@@ -30,8 +30,10 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
||||
let subfolder_choice: Rc<RefCell<Option<bool>>> = Rc::new(RefCell::new(None));
|
||||
|
||||
// Set up drag-and-drop on the entire page
|
||||
let drop_target = gtk::DropTarget::new(gtk::gio::File::static_type(), gtk::gdk::DragAction::COPY);
|
||||
drop_target.set_types(&[gtk::gio::File::static_type()]);
|
||||
// Accept both FileList (from file managers) and single File
|
||||
let drop_target = gtk::DropTarget::new(gtk::gdk::FileList::static_type(), gtk::gdk::DragAction::COPY | gtk::gdk::DragAction::MOVE);
|
||||
drop_target.set_types(&[gtk::gdk::FileList::static_type(), gtk::gio::File::static_type(), glib::GString::static_type()]);
|
||||
drop_target.set_preload(true);
|
||||
|
||||
{
|
||||
let loaded_files = state.loaded_files.clone();
|
||||
@@ -40,40 +42,97 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
||||
let stack_ref = stack.clone();
|
||||
let subfolder_choice = subfolder_choice.clone();
|
||||
drop_target.connect_drop(move |target, value, _x, _y| {
|
||||
if let Ok(file) = value.get::<gtk::gio::File>()
|
||||
&& let Some(path) = file.path()
|
||||
{
|
||||
// Collect paths from FileList, single File, or URI text
|
||||
let mut paths: Vec<PathBuf> = Vec::new();
|
||||
if let Ok(file_list) = value.get::<gtk::gdk::FileList>() {
|
||||
for file in file_list.files() {
|
||||
if let Some(path) = file.path() {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
} else if let Ok(file) = value.get::<gtk::gio::File>() {
|
||||
if let Some(path) = file.path() {
|
||||
paths.push(path);
|
||||
}
|
||||
} else if let Ok(text) = value.get::<glib::GString>() {
|
||||
// Handle URI text drops (from web browsers)
|
||||
let text = text.trim().to_string();
|
||||
let lower = text.to_lowercase();
|
||||
let is_image_url = (lower.starts_with("http://") || lower.starts_with("https://"))
|
||||
&& (lower.ends_with(".jpg")
|
||||
|| lower.ends_with(".jpeg")
|
||||
|| lower.ends_with(".png")
|
||||
|| lower.ends_with(".webp")
|
||||
|| lower.ends_with(".gif")
|
||||
|| lower.ends_with(".avif")
|
||||
|| lower.ends_with(".tiff")
|
||||
|| lower.ends_with(".bmp")
|
||||
|| lower.contains(".jpg?")
|
||||
|| lower.contains(".jpeg?")
|
||||
|| lower.contains(".png?")
|
||||
|| lower.contains(".webp?"));
|
||||
|
||||
if is_image_url {
|
||||
let loaded = loaded_files.clone();
|
||||
let excl = excluded.clone();
|
||||
let sz = sizes.clone();
|
||||
let sr = stack_ref.clone();
|
||||
let (tx, rx) = std::sync::mpsc::channel::<Option<std::path::PathBuf>>();
|
||||
let url = text.clone();
|
||||
std::thread::spawn(move || {
|
||||
let result = download_image_url(&url);
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
|
||||
match rx.try_recv() {
|
||||
Ok(Some(path)) => {
|
||||
let mut files = loaded.borrow_mut();
|
||||
if !files.contains(&path) {
|
||||
files.push(path);
|
||||
}
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
refresh_grid(&sr, &loaded, &excl, &sz, count);
|
||||
glib::ControlFlow::Break
|
||||
}
|
||||
Ok(None) => glib::ControlFlow::Break,
|
||||
Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
|
||||
Err(_) => glib::ControlFlow::Break,
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if paths.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for path in paths {
|
||||
if path.is_dir() {
|
||||
let has_subdirs = has_subfolders(&path);
|
||||
if !has_subdirs {
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_flat(&path, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
||||
} else {
|
||||
let choice = *subfolder_choice.borrow();
|
||||
match choice {
|
||||
Some(true) => {
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_from_dir(&path, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
||||
}
|
||||
Some(false) => {
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_flat(&path, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
||||
}
|
||||
None => {
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_flat(&path, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
||||
|
||||
let loaded_files = loaded_files.clone();
|
||||
let excluded = excluded.clone();
|
||||
@@ -98,88 +157,23 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if is_image_file(&path) {
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
if !files.contains(&path) {
|
||||
files.push(path);
|
||||
}
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
|
||||
let count = loaded_files.borrow().len();
|
||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
stack.add_controller(drop_target);
|
||||
|
||||
// Also accept URI text drops (from web browsers)
|
||||
let uri_drop = gtk::DropTarget::new(glib::GString::static_type(), gtk::gdk::DragAction::COPY);
|
||||
{
|
||||
let loaded_files = state.loaded_files.clone();
|
||||
let excluded = state.excluded_files.clone();
|
||||
let sizes = state.file_sizes.clone();
|
||||
let stack_ref = stack.clone();
|
||||
uri_drop.connect_drop(move |_target, value, _x, _y| {
|
||||
if let Ok(text) = value.get::<glib::GString>() {
|
||||
let text = text.trim().to_string();
|
||||
// Check if it looks like an image URL
|
||||
let lower = text.to_lowercase();
|
||||
let is_image_url = (lower.starts_with("http://") || lower.starts_with("https://"))
|
||||
&& (lower.ends_with(".jpg")
|
||||
|| lower.ends_with(".jpeg")
|
||||
|| lower.ends_with(".png")
|
||||
|| lower.ends_with(".webp")
|
||||
|| lower.ends_with(".gif")
|
||||
|| lower.ends_with(".avif")
|
||||
|| lower.ends_with(".tiff")
|
||||
|| lower.ends_with(".bmp")
|
||||
|| lower.contains(".jpg?")
|
||||
|| lower.contains(".jpeg?")
|
||||
|| lower.contains(".png?")
|
||||
|| lower.contains(".webp?"));
|
||||
|
||||
if is_image_url {
|
||||
let loaded = loaded_files.clone();
|
||||
let excl = excluded.clone();
|
||||
let sz = sizes.clone();
|
||||
let sr = stack_ref.clone();
|
||||
// Download in background thread
|
||||
let (tx, rx) = std::sync::mpsc::channel::<Option<std::path::PathBuf>>();
|
||||
let url = text.clone();
|
||||
std::thread::spawn(move || {
|
||||
let result = download_image_url(&url);
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
|
||||
glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
|
||||
match rx.try_recv() {
|
||||
Ok(Some(path)) => {
|
||||
let mut files = loaded.borrow_mut();
|
||||
if !files.contains(&path) {
|
||||
files.push(path);
|
||||
}
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
refresh_grid(&sr, &loaded, &excl, &sz, count);
|
||||
glib::ControlFlow::Break
|
||||
}
|
||||
Ok(None) => glib::ControlFlow::Break,
|
||||
Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
|
||||
Err(_) => glib::ControlFlow::Break,
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
}
|
||||
stack.add_controller(uri_drop);
|
||||
|
||||
adw::NavigationPage::builder()
|
||||
.title("Add Images")
|
||||
.tag("step-images")
|
||||
|
||||
Reference in New Issue
Block a user