- Redesign tutorial tour from modal dialogs to popovers pointing at actual UI elements - Add beginner-friendly improvements: help buttons, tooltips, welcome wizard enhancements - Add AppStream metainfo with screenshots, branding, categories, keywords, provides - Update desktop file with GTK category and SingleMainWindow - Add hicolor icon theme with all sizes (16-512px) - Fix debounce SourceId panic in rename step - Various step UI improvements and bug fixes
297 lines
10 KiB
Rust
297 lines
10 KiB
Rust
use adw::prelude::*;
|
|
use crate::app::{AppState, MetadataMode};
|
|
|
|
pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
|
|
let scrolled = gtk::ScrolledWindow::builder()
|
|
.hscrollbar_policy(gtk::PolicyType::Never)
|
|
.vexpand(true)
|
|
.build();
|
|
|
|
let content = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Vertical)
|
|
.spacing(12)
|
|
.margin_top(12)
|
|
.margin_bottom(12)
|
|
.margin_start(24)
|
|
.margin_end(24)
|
|
.build();
|
|
|
|
let cfg = state.job_config.borrow();
|
|
|
|
// Enable toggle
|
|
let enable_row = adw::SwitchRow::builder()
|
|
.title("Enable Metadata Handling")
|
|
.subtitle("Control what image metadata to keep or remove")
|
|
.active(cfg.metadata_enabled)
|
|
.tooltip_text("Toggle metadata handling on or off")
|
|
.build();
|
|
|
|
let enable_group = adw::PreferencesGroup::new();
|
|
enable_group.add(&enable_row);
|
|
content.append(&enable_group);
|
|
|
|
// Quick presets
|
|
let presets_group = adw::PreferencesGroup::builder()
|
|
.title("Metadata Mode")
|
|
.build();
|
|
|
|
let strip_all_row = adw::ActionRow::builder()
|
|
.title("Strip All")
|
|
.subtitle("Remove all metadata - smallest files, maximum privacy")
|
|
.activatable(true)
|
|
.build();
|
|
let strip_all_icon = gtk::Image::from_icon_name("user-trash-symbolic");
|
|
strip_all_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
|
|
strip_all_row.add_prefix(&strip_all_icon);
|
|
let strip_all_check = gtk::CheckButton::new();
|
|
strip_all_check.set_active(cfg.metadata_mode == MetadataMode::StripAll);
|
|
strip_all_row.add_suffix(&strip_all_check);
|
|
strip_all_row.set_activatable_widget(Some(&strip_all_check));
|
|
|
|
let privacy_row = adw::ActionRow::builder()
|
|
.title("Privacy Mode")
|
|
.subtitle("Strip GPS and camera serial, keep copyright")
|
|
.activatable(true)
|
|
.build();
|
|
let privacy_icon = gtk::Image::from_icon_name("security-medium-symbolic");
|
|
privacy_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
|
|
privacy_row.add_prefix(&privacy_icon);
|
|
let privacy_check = gtk::CheckButton::new();
|
|
privacy_check.set_group(Some(&strip_all_check));
|
|
privacy_check.set_active(cfg.metadata_mode == MetadataMode::Privacy);
|
|
privacy_row.add_suffix(&privacy_check);
|
|
privacy_row.set_activatable_widget(Some(&privacy_check));
|
|
|
|
let keep_all_row = adw::ActionRow::builder()
|
|
.title("Keep All")
|
|
.subtitle("Preserve all original metadata")
|
|
.activatable(true)
|
|
.build();
|
|
let keep_all_icon = gtk::Image::from_icon_name("emblem-ok-symbolic");
|
|
keep_all_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
|
|
keep_all_row.add_prefix(&keep_all_icon);
|
|
let keep_all_check = gtk::CheckButton::new();
|
|
keep_all_check.set_group(Some(&strip_all_check));
|
|
keep_all_check.set_active(cfg.metadata_mode == MetadataMode::KeepAll);
|
|
keep_all_row.add_suffix(&keep_all_check);
|
|
keep_all_row.set_activatable_widget(Some(&keep_all_check));
|
|
|
|
let photographer_row = adw::ActionRow::builder()
|
|
.title("Photographer")
|
|
.subtitle("Keep copyright and camera model, strip GPS and software")
|
|
.activatable(true)
|
|
.build();
|
|
let photographer_icon = gtk::Image::from_icon_name("camera-photo-symbolic");
|
|
photographer_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
|
|
photographer_row.add_prefix(&photographer_icon);
|
|
let photographer_check = gtk::CheckButton::new();
|
|
photographer_check.set_group(Some(&strip_all_check));
|
|
photographer_row.add_suffix(&photographer_check);
|
|
photographer_row.set_activatable_widget(Some(&photographer_check));
|
|
|
|
let custom_row = adw::ActionRow::builder()
|
|
.title("Custom")
|
|
.subtitle("Choose exactly which metadata categories to strip")
|
|
.activatable(true)
|
|
.build();
|
|
let custom_icon = gtk::Image::from_icon_name("emblem-system-symbolic");
|
|
custom_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
|
|
custom_row.add_prefix(&custom_icon);
|
|
let custom_check = gtk::CheckButton::new();
|
|
custom_check.set_group(Some(&strip_all_check));
|
|
custom_check.set_active(cfg.metadata_mode == MetadataMode::Custom);
|
|
custom_row.add_suffix(&custom_check);
|
|
custom_row.set_activatable_widget(Some(&custom_check));
|
|
|
|
presets_group.add(&strip_all_row);
|
|
presets_group.add(&privacy_row);
|
|
presets_group.add(&photographer_row);
|
|
presets_group.add(&keep_all_row);
|
|
presets_group.add(&custom_row);
|
|
content.append(&presets_group);
|
|
|
|
// Custom category checkboxes
|
|
let custom_group = adw::PreferencesGroup::builder()
|
|
.title("Custom Categories")
|
|
.description("Select which metadata categories to strip")
|
|
.build();
|
|
|
|
let gps_row = adw::SwitchRow::builder()
|
|
.title("GPS / Location")
|
|
.subtitle("GPS coordinates, location name, altitude")
|
|
.active(cfg.strip_gps)
|
|
.tooltip_text("Strip GPS coordinates, location name, and altitude")
|
|
.build();
|
|
|
|
let camera_row = adw::SwitchRow::builder()
|
|
.title("Camera Info")
|
|
.subtitle("Camera model, serial number, lens data")
|
|
.active(cfg.strip_camera)
|
|
.tooltip_text("Strip camera model, serial number, and lens data")
|
|
.build();
|
|
|
|
let software_row = adw::SwitchRow::builder()
|
|
.title("Software")
|
|
.subtitle("Editing software, processing history")
|
|
.active(cfg.strip_software)
|
|
.tooltip_text("Strip editing software and processing history")
|
|
.build();
|
|
|
|
let timestamps_row = adw::SwitchRow::builder()
|
|
.title("Timestamps")
|
|
.subtitle("Date taken, date modified, date digitized")
|
|
.active(cfg.strip_timestamps)
|
|
.tooltip_text("Strip date taken, date modified, date digitized")
|
|
.build();
|
|
|
|
let copyright_row = adw::SwitchRow::builder()
|
|
.title("Copyright / Author")
|
|
.subtitle("Copyright notice, artist name, credits")
|
|
.active(cfg.strip_copyright)
|
|
.tooltip_text("Strip copyright notice, artist name, and credits")
|
|
.build();
|
|
|
|
custom_group.add(&gps_row);
|
|
custom_group.add(&camera_row);
|
|
custom_group.add(&software_row);
|
|
custom_group.add(×tamps_row);
|
|
custom_group.add(©right_row);
|
|
|
|
// Only show custom group when Custom mode is selected
|
|
custom_group.set_visible(cfg.metadata_mode == MetadataMode::Custom);
|
|
|
|
content.append(&custom_group);
|
|
|
|
drop(cfg);
|
|
|
|
// Wire signals
|
|
{
|
|
let jc = state.job_config.clone();
|
|
enable_row.connect_active_notify(move |row| {
|
|
jc.borrow_mut().metadata_enabled = row.is_active();
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
let cg = custom_group.clone();
|
|
strip_all_check.connect_toggled(move |check| {
|
|
if check.is_active() {
|
|
jc.borrow_mut().metadata_mode = MetadataMode::StripAll;
|
|
cg.set_visible(false);
|
|
}
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
let cg = custom_group.clone();
|
|
privacy_check.connect_toggled(move |check| {
|
|
if check.is_active() {
|
|
jc.borrow_mut().metadata_mode = MetadataMode::Privacy;
|
|
cg.set_visible(false);
|
|
}
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
let cg = custom_group.clone();
|
|
let gps_c = gps_row.clone();
|
|
let camera_c = camera_row.clone();
|
|
let software_c = software_row.clone();
|
|
let timestamps_c = timestamps_row.clone();
|
|
let copyright_c = copyright_row.clone();
|
|
photographer_check.connect_toggled(move |check| {
|
|
if check.is_active() {
|
|
{
|
|
let mut cfg = jc.borrow_mut();
|
|
cfg.metadata_mode = MetadataMode::Custom;
|
|
// Photographer: keep copyright + camera model, strip GPS + software
|
|
cfg.strip_gps = true;
|
|
cfg.strip_camera = false;
|
|
cfg.strip_software = true;
|
|
cfg.strip_timestamps = false;
|
|
cfg.strip_copyright = false;
|
|
}
|
|
// Update UI to match (after dropping borrow to avoid re-entrancy)
|
|
gps_c.set_active(true);
|
|
camera_c.set_active(false);
|
|
software_c.set_active(true);
|
|
timestamps_c.set_active(false);
|
|
copyright_c.set_active(false);
|
|
cg.set_visible(false);
|
|
}
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
let cg = custom_group.clone();
|
|
keep_all_check.connect_toggled(move |check| {
|
|
if check.is_active() {
|
|
jc.borrow_mut().metadata_mode = MetadataMode::KeepAll;
|
|
cg.set_visible(false);
|
|
}
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
let cg = custom_group;
|
|
custom_check.connect_toggled(move |check| {
|
|
if check.is_active() {
|
|
jc.borrow_mut().metadata_mode = MetadataMode::Custom;
|
|
cg.set_visible(true);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Wire custom category toggles
|
|
{
|
|
let jc = state.job_config.clone();
|
|
gps_row.connect_active_notify(move |row| {
|
|
jc.borrow_mut().strip_gps = row.is_active();
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
camera_row.connect_active_notify(move |row| {
|
|
jc.borrow_mut().strip_camera = row.is_active();
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
software_row.connect_active_notify(move |row| {
|
|
jc.borrow_mut().strip_software = row.is_active();
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
timestamps_row.connect_active_notify(move |row| {
|
|
jc.borrow_mut().strip_timestamps = row.is_active();
|
|
});
|
|
}
|
|
{
|
|
let jc = state.job_config.clone();
|
|
copyright_row.connect_active_notify(move |row| {
|
|
jc.borrow_mut().strip_copyright = row.is_active();
|
|
});
|
|
}
|
|
|
|
scrolled.set_child(Some(&content));
|
|
|
|
let page = adw::NavigationPage::builder()
|
|
.title("Metadata")
|
|
.tag("step-metadata")
|
|
.child(&scrolled)
|
|
.build();
|
|
|
|
// Sync enable toggle when navigating to this page
|
|
{
|
|
let jc = state.job_config.clone();
|
|
let er = enable_row.clone();
|
|
page.connect_map(move |_| {
|
|
let enabled = jc.borrow().metadata_enabled;
|
|
er.set_active(enabled);
|
|
});
|
|
}
|
|
|
|
page
|
|
}
|