Add per-image checkboxes and wire Select All / Deselect All buttons
- Each image row now has a CheckButton for include/exclude from processing - Select All clears exclusion set, Deselect All adds all files to it - Count label shows "X/Y images selected" when some are excluded - Processing respects excluded files - only processes checked images - Clear All also resets exclusion set - AppState gains excluded_files HashSet for tracking
This commit is contained in:
@@ -81,6 +81,7 @@ pub enum MetadataMode {
|
|||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub wizard: Rc<RefCell<WizardState>>,
|
pub wizard: Rc<RefCell<WizardState>>,
|
||||||
pub loaded_files: Rc<RefCell<Vec<std::path::PathBuf>>>,
|
pub loaded_files: Rc<RefCell<Vec<std::path::PathBuf>>>,
|
||||||
|
pub excluded_files: Rc<RefCell<std::collections::HashSet<std::path::PathBuf>>>,
|
||||||
pub output_dir: Rc<RefCell<Option<std::path::PathBuf>>>,
|
pub output_dir: Rc<RefCell<Option<std::path::PathBuf>>>,
|
||||||
pub job_config: Rc<RefCell<JobConfig>>,
|
pub job_config: Rc<RefCell<JobConfig>>,
|
||||||
}
|
}
|
||||||
@@ -143,6 +144,7 @@ fn build_ui(app: &adw::Application) {
|
|||||||
let app_state = AppState {
|
let app_state = AppState {
|
||||||
wizard: Rc::new(RefCell::new(WizardState::new())),
|
wizard: Rc::new(RefCell::new(WizardState::new())),
|
||||||
loaded_files: Rc::new(RefCell::new(Vec::new())),
|
loaded_files: Rc::new(RefCell::new(Vec::new())),
|
||||||
|
excluded_files: Rc::new(RefCell::new(std::collections::HashSet::new())),
|
||||||
output_dir: Rc::new(RefCell::new(None)),
|
output_dir: Rc::new(RefCell::new(None)),
|
||||||
job_config: Rc::new(RefCell::new(JobConfig {
|
job_config: Rc::new(RefCell::new(JobConfig {
|
||||||
resize_enabled: if remember { sess_state.resize_enabled.unwrap_or(true) } else { true },
|
resize_enabled: if remember { sess_state.resize_enabled.unwrap_or(true) } else { true },
|
||||||
@@ -415,9 +417,11 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
|||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
let action = gtk::gio::SimpleAction::new("process", None);
|
let action = gtk::gio::SimpleAction::new("process", None);
|
||||||
action.connect_activate(move |_, _| {
|
action.connect_activate(move |_, _| {
|
||||||
let files = ui.state.loaded_files.borrow().clone();
|
let excluded = ui.state.excluded_files.borrow();
|
||||||
if files.is_empty() {
|
let has_included = ui.state.loaded_files.borrow().iter().any(|p| !excluded.contains(p));
|
||||||
let toast = adw::Toast::new("No images loaded - go to Step 2 to add images");
|
drop(excluded);
|
||||||
|
if !has_included {
|
||||||
|
let toast = adw::Toast::new("No images selected - go to Step 2 to add images");
|
||||||
ui.toast_overlay.add_toast(toast);
|
ui.toast_overlay.add_toast(toast);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -677,7 +681,10 @@ fn update_output_label(ui: &WizardUi, path: &std::path::Path) {
|
|||||||
|
|
||||||
fn update_images_count_label(ui: &WizardUi, count: usize) {
|
fn update_images_count_label(ui: &WizardUi, count: usize) {
|
||||||
let files = ui.state.loaded_files.borrow();
|
let files = ui.state.loaded_files.borrow();
|
||||||
|
let excluded = ui.state.excluded_files.borrow();
|
||||||
|
let included_count = files.iter().filter(|p| !excluded.contains(*p)).count();
|
||||||
let total_size: u64 = files.iter()
|
let total_size: u64 = files.iter()
|
||||||
|
.filter(|p| !excluded.contains(*p))
|
||||||
.filter_map(|p| std::fs::metadata(p).ok())
|
.filter_map(|p| std::fs::metadata(p).ok())
|
||||||
.map(|m| m.len())
|
.map(|m| m.len())
|
||||||
.sum();
|
.sum();
|
||||||
@@ -692,27 +699,31 @@ fn update_images_count_label(ui: &WizardUi, count: usize) {
|
|||||||
stack.set_visible_child_name("empty");
|
stack.set_visible_child_name("empty");
|
||||||
}
|
}
|
||||||
if let Some(loaded_box) = stack.child_by_name("loaded") {
|
if let Some(loaded_box) = stack.child_by_name("loaded") {
|
||||||
update_count_in_box(&loaded_box, count, total_size);
|
update_count_in_box(&loaded_box, count, included_count, total_size);
|
||||||
update_file_list(&loaded_box, &files);
|
update_file_list(&loaded_box, &files, &excluded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_count_in_box(widget: >k::Widget, count: usize, total_size: u64) {
|
fn update_count_in_box(widget: >k::Widget, count: usize, included_count: usize, total_size: u64) {
|
||||||
if let Some(label) = widget.downcast_ref::<gtk::Label>()
|
if let Some(label) = widget.downcast_ref::<gtk::Label>()
|
||||||
&& label.css_classes().iter().any(|c| c == "heading")
|
&& label.css_classes().iter().any(|c| c == "heading")
|
||||||
{
|
{
|
||||||
label.set_label(&format!("{} images ({})", count, format_bytes(total_size)));
|
if included_count == count {
|
||||||
|
label.set_label(&format!("{} images ({})", count, format_bytes(total_size)));
|
||||||
|
} else {
|
||||||
|
label.set_label(&format!("{}/{} images selected ({})", included_count, count, format_bytes(total_size)));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut child = widget.first_child();
|
let mut child = widget.first_child();
|
||||||
while let Some(c) = child {
|
while let Some(c) = child {
|
||||||
update_count_in_box(&c, count, total_size);
|
update_count_in_box(&c, count, included_count, total_size);
|
||||||
child = c.next_sibling();
|
child = c.next_sibling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_file_list(widget: >k::Widget, files: &[std::path::PathBuf]) {
|
fn update_file_list(widget: >k::Widget, files: &[std::path::PathBuf], excluded: &std::collections::HashSet<std::path::PathBuf>) {
|
||||||
if let Some(list_box) = widget.downcast_ref::<gtk::ListBox>()
|
if let Some(list_box) = widget.downcast_ref::<gtk::ListBox>()
|
||||||
&& list_box.css_classes().iter().any(|c| c == "boxed-list")
|
&& list_box.css_classes().iter().any(|c| c == "boxed-list")
|
||||||
{
|
{
|
||||||
@@ -734,6 +745,12 @@ fn update_file_list(widget: >k::Widget, files: &[std::path::PathBuf]) {
|
|||||||
.title(name)
|
.title(name)
|
||||||
.subtitle(format!("{} - {}", ext, size))
|
.subtitle(format!("{} - {}", ext, size))
|
||||||
.build();
|
.build();
|
||||||
|
let check = gtk::CheckButton::builder()
|
||||||
|
.active(!excluded.contains(path))
|
||||||
|
.tooltip_text("Include in processing")
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
row.add_prefix(&check);
|
||||||
row.add_prefix(>k::Image::from_icon_name("image-x-generic-symbolic"));
|
row.add_prefix(>k::Image::from_icon_name("image-x-generic-symbolic"));
|
||||||
list_box.append(&row);
|
list_box.append(&row);
|
||||||
}
|
}
|
||||||
@@ -741,7 +758,7 @@ fn update_file_list(widget: >k::Widget, files: &[std::path::PathBuf]) {
|
|||||||
}
|
}
|
||||||
let mut child = widget.first_child();
|
let mut child = widget.first_child();
|
||||||
while let Some(c) = child {
|
while let Some(c) = child {
|
||||||
update_file_list(&c, files);
|
update_file_list(&c, files, excluded);
|
||||||
child = c.next_sibling();
|
child = c.next_sibling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -948,7 +965,12 @@ fn show_whats_new_dialog(window: &adw::ApplicationWindow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_processing(_window: &adw::ApplicationWindow, ui: &WizardUi) {
|
fn run_processing(_window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||||
let files = ui.state.loaded_files.borrow().clone();
|
let excluded = ui.state.excluded_files.borrow().clone();
|
||||||
|
let files: Vec<std::path::PathBuf> = ui.state.loaded_files.borrow()
|
||||||
|
.iter()
|
||||||
|
.filter(|p| !excluded.contains(*p))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1393,6 +1415,7 @@ fn reset_wizard(ui: &WizardUi) {
|
|||||||
s.visited[0] = true;
|
s.visited[0] = true;
|
||||||
}
|
}
|
||||||
ui.state.loaded_files.borrow_mut().clear();
|
ui.state.loaded_files.borrow_mut().clear();
|
||||||
|
ui.state.excluded_files.borrow_mut().clear();
|
||||||
|
|
||||||
// Reset nav
|
// Reset nav
|
||||||
ui.nav_view.replace(&ui.pages[..1]);
|
ui.nav_view.replace(&ui.pages[..1]);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let loaded_files = state.loaded_files.clone();
|
let loaded_files = state.loaded_files.clone();
|
||||||
|
let excluded = state.excluded_files.clone();
|
||||||
let stack_ref = stack.clone();
|
let stack_ref = stack.clone();
|
||||||
drop_target.connect_drop(move |_target, value, _x, _y| {
|
drop_target.connect_drop(move |_target, value, _x, _y| {
|
||||||
if let Ok(file) = value.get::<gtk::gio::File>()
|
if let Ok(file) = value.get::<gtk::gio::File>()
|
||||||
@@ -32,7 +33,7 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
add_images_from_dir(&path, &mut files);
|
add_images_from_dir(&path, &mut files);
|
||||||
let count = files.len();
|
let count = files.len();
|
||||||
drop(files);
|
drop(files);
|
||||||
update_loaded_ui(&stack_ref, &loaded_files, count);
|
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
|
||||||
return true;
|
return true;
|
||||||
} else if is_image_file(&path) {
|
} else if is_image_file(&path) {
|
||||||
let mut files = loaded_files.borrow_mut();
|
let mut files = loaded_files.borrow_mut();
|
||||||
@@ -41,7 +42,7 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
}
|
}
|
||||||
let count = files.len();
|
let count = files.len();
|
||||||
drop(files);
|
drop(files);
|
||||||
update_loaded_ui(&stack_ref, &loaded_files, count);
|
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,6 +82,7 @@ fn add_images_from_dir(dir: &std::path::Path, files: &mut Vec<std::path::PathBuf
|
|||||||
fn update_loaded_ui(
|
fn update_loaded_ui(
|
||||||
stack: >k::Stack,
|
stack: >k::Stack,
|
||||||
loaded_files: &std::rc::Rc<std::cell::RefCell<Vec<std::path::PathBuf>>>,
|
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>>>,
|
||||||
count: usize,
|
count: usize,
|
||||||
) {
|
) {
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
@@ -89,36 +91,46 @@ fn update_loaded_ui(
|
|||||||
stack.set_visible_child_name("empty");
|
stack.set_visible_child_name("empty");
|
||||||
}
|
}
|
||||||
if let Some(loaded_widget) = stack.child_by_name("loaded") {
|
if let Some(loaded_widget) = stack.child_by_name("loaded") {
|
||||||
update_count_and_list(&loaded_widget, loaded_files);
|
update_count_and_list(&loaded_widget, loaded_files, excluded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_count_and_list(
|
fn update_count_and_list(
|
||||||
widget: >k::Widget,
|
widget: >k::Widget,
|
||||||
loaded_files: &std::rc::Rc<std::cell::RefCell<Vec<std::path::PathBuf>>>,
|
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>>>,
|
||||||
) {
|
) {
|
||||||
let files = loaded_files.borrow();
|
let files = loaded_files.borrow();
|
||||||
|
let excluded_set = excluded.borrow();
|
||||||
let count = files.len();
|
let count = files.len();
|
||||||
|
let included_count = files.iter().filter(|p| !excluded_set.contains(*p)).count();
|
||||||
let total_size: u64 = files.iter()
|
let total_size: u64 = files.iter()
|
||||||
|
.filter(|p| !excluded_set.contains(*p))
|
||||||
.filter_map(|p| std::fs::metadata(p).ok())
|
.filter_map(|p| std::fs::metadata(p).ok())
|
||||||
.map(|m| m.len())
|
.map(|m| m.len())
|
||||||
.sum();
|
.sum();
|
||||||
let size_str = format_size(total_size);
|
let size_str = format_size(total_size);
|
||||||
|
|
||||||
walk_loaded_widgets(widget, count, &size_str, &files, loaded_files);
|
walk_loaded_widgets(widget, count, included_count, &size_str, &files, loaded_files, excluded);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_loaded_widgets(
|
fn walk_loaded_widgets(
|
||||||
widget: >k::Widget,
|
widget: >k::Widget,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
included_count: usize,
|
||||||
size_str: &str,
|
size_str: &str,
|
||||||
files: &[std::path::PathBuf],
|
files: &[std::path::PathBuf],
|
||||||
loaded_files: &std::rc::Rc<std::cell::RefCell<Vec<std::path::PathBuf>>>,
|
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>>>,
|
||||||
) {
|
) {
|
||||||
if let Some(label) = widget.downcast_ref::<gtk::Label>()
|
if let Some(label) = widget.downcast_ref::<gtk::Label>()
|
||||||
&& label.css_classes().iter().any(|c| c == "heading")
|
&& label.css_classes().iter().any(|c| c == "heading")
|
||||||
{
|
{
|
||||||
label.set_label(&format!("{} images ({})", count, size_str));
|
if included_count == count {
|
||||||
|
label.set_label(&format!("{} images ({})", count, size_str));
|
||||||
|
} else {
|
||||||
|
label.set_label(&format!("{}/{} images selected ({})", included_count, count, size_str));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(list_box) = widget.downcast_ref::<gtk::ListBox>()
|
if let Some(list_box) = widget.downcast_ref::<gtk::ListBox>()
|
||||||
&& list_box.css_classes().iter().any(|c| c == "boxed-list")
|
&& list_box.css_classes().iter().any(|c| c == "boxed-list")
|
||||||
@@ -127,7 +139,8 @@ fn walk_loaded_widgets(
|
|||||||
while let Some(row) = list_box.first_child() {
|
while let Some(row) = list_box.first_child() {
|
||||||
list_box.remove(&row);
|
list_box.remove(&row);
|
||||||
}
|
}
|
||||||
// Add rows for each file with remove button
|
let excluded_set = excluded.borrow();
|
||||||
|
// Add rows for each file with checkbox and remove button
|
||||||
for (idx, path) in files.iter().enumerate() {
|
for (idx, path) in files.iter().enumerate() {
|
||||||
let name = path.file_name()
|
let name = path.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
@@ -143,6 +156,38 @@ fn walk_loaded_widgets(
|
|||||||
.title(name)
|
.title(name)
|
||||||
.subtitle(format!("{} - {}", ext, size))
|
.subtitle(format!("{} - {}", ext, size))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Include/exclude checkbox
|
||||||
|
let check = gtk::CheckButton::builder()
|
||||||
|
.active(!excluded_set.contains(path))
|
||||||
|
.tooltip_text("Include in processing")
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
{
|
||||||
|
let excl = excluded.clone();
|
||||||
|
let file_path = path.clone();
|
||||||
|
let list = list_box.clone();
|
||||||
|
let loaded = loaded_files.clone();
|
||||||
|
let excluded_ref = excluded.clone();
|
||||||
|
check.connect_toggled(move |btn| {
|
||||||
|
{
|
||||||
|
let mut excl = excl.borrow_mut();
|
||||||
|
if btn.is_active() {
|
||||||
|
excl.remove(&file_path);
|
||||||
|
} else {
|
||||||
|
excl.insert(file_path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update the count label
|
||||||
|
if let Some(parent) = list.ancestor(gtk::Stack::static_type())
|
||||||
|
&& let Some(stack) = parent.downcast_ref::<gtk::Stack>()
|
||||||
|
&& let Some(loaded_widget) = stack.child_by_name("loaded")
|
||||||
|
{
|
||||||
|
update_count_label(&loaded_widget, &loaded, &excluded_ref);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
row.add_prefix(&check);
|
||||||
row.add_prefix(>k::Image::from_icon_name("image-x-generic-symbolic"));
|
row.add_prefix(>k::Image::from_icon_name("image-x-generic-symbolic"));
|
||||||
|
|
||||||
// Per-image remove button
|
// Per-image remove button
|
||||||
@@ -154,15 +199,21 @@ fn walk_loaded_widgets(
|
|||||||
remove_btn.add_css_class("flat");
|
remove_btn.add_css_class("flat");
|
||||||
{
|
{
|
||||||
let loaded = loaded_files.clone();
|
let loaded = loaded_files.clone();
|
||||||
|
let excl = excluded.clone();
|
||||||
let list = list_box.clone();
|
let list = list_box.clone();
|
||||||
let file_idx = idx;
|
let file_idx = idx;
|
||||||
remove_btn.connect_clicked(move |_btn| {
|
remove_btn.connect_clicked(move |_btn| {
|
||||||
let mut files = loaded.borrow_mut();
|
let removed_path;
|
||||||
if file_idx < files.len() {
|
{
|
||||||
files.remove(file_idx);
|
let mut files = loaded.borrow_mut();
|
||||||
|
if file_idx < files.len() {
|
||||||
|
removed_path = files.remove(file_idx);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let count = files.len();
|
excl.borrow_mut().remove(&removed_path);
|
||||||
drop(files);
|
let count = loaded.borrow().len();
|
||||||
// Refresh by finding the parent stack
|
// Refresh by finding the parent stack
|
||||||
if let Some(parent) = list.ancestor(gtk::Stack::static_type())
|
if let Some(parent) = list.ancestor(gtk::Stack::static_type())
|
||||||
&& let Some(stack) = parent.downcast_ref::<gtk::Stack>()
|
&& let Some(stack) = parent.downcast_ref::<gtk::Stack>()
|
||||||
@@ -170,7 +221,7 @@ fn walk_loaded_widgets(
|
|||||||
if count == 0 {
|
if count == 0 {
|
||||||
stack.set_visible_child_name("empty");
|
stack.set_visible_child_name("empty");
|
||||||
} else if let Some(loaded_widget) = stack.child_by_name("loaded") {
|
} else if let Some(loaded_widget) = stack.child_by_name("loaded") {
|
||||||
update_count_and_list(&loaded_widget, &loaded);
|
update_count_and_list(&loaded_widget, &loaded, &excl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -182,7 +233,44 @@ fn walk_loaded_widgets(
|
|||||||
// Recurse
|
// Recurse
|
||||||
let mut child = widget.first_child();
|
let mut child = widget.first_child();
|
||||||
while let Some(c) = child {
|
while let Some(c) = child {
|
||||||
walk_loaded_widgets(&c, count, size_str, files, loaded_files);
|
walk_loaded_widgets(&c, count, included_count, size_str, files, loaded_files, excluded);
|
||||||
|
child = c.next_sibling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only the count label without rebuilding the list
|
||||||
|
fn update_count_label(
|
||||||
|
widget: >k::Widget,
|
||||||
|
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>>>,
|
||||||
|
) {
|
||||||
|
let files = loaded_files.borrow();
|
||||||
|
let excluded_set = excluded.borrow();
|
||||||
|
let count = files.len();
|
||||||
|
let included_count = files.iter().filter(|p| !excluded_set.contains(*p)).count();
|
||||||
|
let total_size: u64 = files.iter()
|
||||||
|
.filter(|p| !excluded_set.contains(*p))
|
||||||
|
.filter_map(|p| std::fs::metadata(p).ok())
|
||||||
|
.map(|m| m.len())
|
||||||
|
.sum();
|
||||||
|
let size_str = format_size(total_size);
|
||||||
|
|
||||||
|
update_count_label_walk(widget, count, included_count, &size_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_count_label_walk(widget: >k::Widget, count: usize, included_count: usize, size_str: &str) {
|
||||||
|
if let Some(label) = widget.downcast_ref::<gtk::Label>()
|
||||||
|
&& label.css_classes().iter().any(|c| c == "heading")
|
||||||
|
{
|
||||||
|
if included_count == count {
|
||||||
|
label.set_label(&format!("{} images ({})", count, size_str));
|
||||||
|
} else {
|
||||||
|
label.set_label(&format!("{}/{} images selected ({})", included_count, count, size_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut child = widget.first_child();
|
||||||
|
while let Some(c) = child {
|
||||||
|
update_count_label_walk(&c, count, included_count, size_str);
|
||||||
child = c.next_sibling();
|
child = c.next_sibling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,9 +413,11 @@ fn build_loaded_state(state: &AppState) -> gtk::Box {
|
|||||||
// Wire clear button
|
// Wire clear button
|
||||||
{
|
{
|
||||||
let files = state.loaded_files.clone();
|
let files = state.loaded_files.clone();
|
||||||
|
let excl = state.excluded_files.clone();
|
||||||
let count_label_c = count_label.clone();
|
let count_label_c = count_label.clone();
|
||||||
clear_button.connect_clicked(move |btn| {
|
clear_button.connect_clicked(move |btn| {
|
||||||
files.borrow_mut().clear();
|
files.borrow_mut().clear();
|
||||||
|
excl.borrow_mut().clear();
|
||||||
count_label_c.set_label("0 images");
|
count_label_c.set_label("0 images");
|
||||||
if let Some(parent) = btn.ancestor(gtk::Stack::static_type())
|
if let Some(parent) = btn.ancestor(gtk::Stack::static_type())
|
||||||
&& let Some(stack) = parent.downcast_ref::<gtk::Stack>()
|
&& let Some(stack) = parent.downcast_ref::<gtk::Stack>()
|
||||||
@@ -337,6 +427,44 @@ fn build_loaded_state(state: &AppState) -> gtk::Box {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wire Select All - clears exclusion set, re-checks all checkboxes
|
||||||
|
{
|
||||||
|
let excl = state.excluded_files.clone();
|
||||||
|
let loaded = state.loaded_files.clone();
|
||||||
|
select_all_button.connect_clicked(move |btn| {
|
||||||
|
excl.borrow_mut().clear();
|
||||||
|
if let Some(parent) = btn.ancestor(gtk::Stack::static_type())
|
||||||
|
&& let Some(stack) = parent.downcast_ref::<gtk::Stack>()
|
||||||
|
&& let Some(loaded_widget) = stack.child_by_name("loaded")
|
||||||
|
{
|
||||||
|
set_all_checkboxes(&loaded_widget, true);
|
||||||
|
update_count_label(&loaded_widget, &loaded, &excl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire Deselect All - adds all files to exclusion set, unchecks all
|
||||||
|
{
|
||||||
|
let excl = state.excluded_files.clone();
|
||||||
|
let loaded = state.loaded_files.clone();
|
||||||
|
deselect_all_button.connect_clicked(move |btn| {
|
||||||
|
{
|
||||||
|
let files = loaded.borrow();
|
||||||
|
let mut excl_set = excl.borrow_mut();
|
||||||
|
for f in files.iter() {
|
||||||
|
excl_set.insert(f.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(parent) = btn.ancestor(gtk::Stack::static_type())
|
||||||
|
&& let Some(stack) = parent.downcast_ref::<gtk::Stack>()
|
||||||
|
&& let Some(loaded_widget) = stack.child_by_name("loaded")
|
||||||
|
{
|
||||||
|
set_all_checkboxes(&loaded_widget, false);
|
||||||
|
update_count_label(&loaded_widget, &loaded, &excl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
toolbar.append(&count_label);
|
toolbar.append(&count_label);
|
||||||
toolbar.append(&select_all_button);
|
toolbar.append(&select_all_button);
|
||||||
toolbar.append(&deselect_all_button);
|
toolbar.append(&deselect_all_button);
|
||||||
@@ -368,3 +496,16 @@ fn build_loaded_state(state: &AppState) -> gtk::Box {
|
|||||||
|
|
||||||
container
|
container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set all CheckButton widgets within a container to a given state
|
||||||
|
fn set_all_checkboxes(widget: >k::Widget, active: bool) {
|
||||||
|
if let Some(check) = widget.downcast_ref::<gtk::CheckButton>() {
|
||||||
|
check.set_active(active);
|
||||||
|
return; // Don't recurse into CheckButton children
|
||||||
|
}
|
||||||
|
let mut child = widget.first_child();
|
||||||
|
while let Some(c) = child {
|
||||||
|
set_all_checkboxes(&c, active);
|
||||||
|
child = c.next_sibling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user