Add Escape key shortcut and subfolder prompt for folder drops
This commit is contained in:
@@ -119,7 +119,7 @@ pub fn build_app() -> adw::Application {
|
||||
|
||||
fn setup_shortcuts(app: &adw::Application) {
|
||||
app.set_accels_for_action("win.next-step", &["<Alt>Right"]);
|
||||
app.set_accels_for_action("win.prev-step", &["<Alt>Left"]);
|
||||
app.set_accels_for_action("win.prev-step", &["<Alt>Left", "Escape"]);
|
||||
app.set_accels_for_action("win.process", &["<Control>Return"]);
|
||||
for i in 1..=9 {
|
||||
app.set_accels_for_action(
|
||||
|
||||
@@ -16,6 +16,10 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
||||
|
||||
stack.set_visible_child_name("empty");
|
||||
|
||||
// Session-level remembered subfolder choice (None = not yet asked)
|
||||
let subfolder_choice: std::rc::Rc<std::cell::RefCell<Option<bool>>> =
|
||||
std::rc::Rc::new(std::cell::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()]);
|
||||
@@ -24,16 +28,69 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
||||
let loaded_files = state.loaded_files.clone();
|
||||
let excluded = state.excluded_files.clone();
|
||||
let stack_ref = stack.clone();
|
||||
drop_target.connect_drop(move |_target, value, _x, _y| {
|
||||
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()
|
||||
{
|
||||
if path.is_dir() {
|
||||
let has_subdirs = has_subfolders(&path);
|
||||
if !has_subdirs {
|
||||
// No subfolders - just load top-level images
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_flat(&path, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
|
||||
} else {
|
||||
let choice = *subfolder_choice.borrow();
|
||||
match choice {
|
||||
Some(true) => {
|
||||
// Remembered: include subfolders
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_from_dir(&path, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
|
||||
}
|
||||
Some(false) => {
|
||||
// Remembered: top-level only
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_flat(&path, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
|
||||
}
|
||||
None => {
|
||||
// Not yet asked - add top-level now, then prompt
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_flat(&path, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
|
||||
|
||||
// Show dialog asynchronously
|
||||
let loaded_files = loaded_files.clone();
|
||||
let excluded = excluded.clone();
|
||||
let stack_ref = stack_ref.clone();
|
||||
let subfolder_choice = subfolder_choice.clone();
|
||||
let dir_path = path.clone();
|
||||
let window = target.widget()
|
||||
.and_then(|w| w.root())
|
||||
.and_then(|r| r.downcast::<gtk::Window>().ok());
|
||||
gtk::glib::idle_add_local_once(move || {
|
||||
show_subfolder_prompt(
|
||||
window.as_ref(),
|
||||
&dir_path,
|
||||
&loaded_files,
|
||||
&excluded,
|
||||
&stack_ref,
|
||||
&subfolder_choice,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if is_image_file(&path) {
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
@@ -79,6 +136,81 @@ fn add_images_from_dir(dir: &std::path::Path, files: &mut Vec<std::path::PathBuf
|
||||
}
|
||||
}
|
||||
|
||||
/// Add only top-level images from a directory (no recursion into subfolders)
|
||||
fn add_images_flat(dir: &std::path::Path, files: &mut Vec<std::path::PathBuf>) {
|
||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file() && is_image_file(&path) && !files.contains(&path) {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a directory contains any subdirectories
|
||||
fn has_subfolders(dir: &std::path::Path) -> bool {
|
||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||
for entry in entries.flatten() {
|
||||
if entry.path().is_dir() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Add only the images from subfolders (not top-level, since those were already added)
|
||||
fn add_images_from_subdirs(dir: &std::path::Path, files: &mut Vec<std::path::PathBuf>) {
|
||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
add_images_from_dir(&path, files);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_subfolder_prompt(
|
||||
window: Option<>k::Window>,
|
||||
dir: &std::path::Path,
|
||||
loaded_files: &std::rc::Rc<std::cell::RefCell<Vec<std::path::PathBuf>>>,
|
||||
excluded: &std::rc::Rc<std::cell::RefCell<std::collections::HashSet<std::path::PathBuf>>>,
|
||||
stack: >k::Stack,
|
||||
subfolder_choice: &std::rc::Rc<std::cell::RefCell<Option<bool>>>,
|
||||
) {
|
||||
let dialog = adw::AlertDialog::builder()
|
||||
.heading("Include subfolders?")
|
||||
.body("This folder contains subfolders. Would you like to include images from subfolders too?")
|
||||
.build();
|
||||
dialog.add_response("no", "Top-level Only");
|
||||
dialog.add_response("yes", "Include Subfolders");
|
||||
dialog.set_default_response(Some("yes"));
|
||||
dialog.set_response_appearance("yes", adw::ResponseAppearance::Suggested);
|
||||
|
||||
let loaded_files = loaded_files.clone();
|
||||
let excluded = excluded.clone();
|
||||
let stack = stack.clone();
|
||||
let subfolder_choice = subfolder_choice.clone();
|
||||
let dir = dir.to_path_buf();
|
||||
dialog.connect_response(None, move |_dialog, response| {
|
||||
let include_subdirs = response == "yes";
|
||||
*subfolder_choice.borrow_mut() = Some(include_subdirs);
|
||||
if include_subdirs {
|
||||
let mut files = loaded_files.borrow_mut();
|
||||
add_images_from_subdirs(&dir, &mut files);
|
||||
let count = files.len();
|
||||
drop(files);
|
||||
update_loaded_ui(&stack, &loaded_files, &excluded, count);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(win) = window {
|
||||
dialog.present(Some(win));
|
||||
}
|
||||
}
|
||||
|
||||
fn update_loaded_ui(
|
||||
stack: >k::Stack,
|
||||
loaded_files: &std::rc::Rc<std::cell::RefCell<Vec<std::path::PathBuf>>>,
|
||||
|
||||
Reference in New Issue
Block a user