diff --git a/apps/filmcraft/src/main.rs b/apps/filmcraft/src/main.rs index 3bb009ea..f850b163 100644 --- a/apps/filmcraft/src/main.rs +++ b/apps/filmcraft/src/main.rs @@ -447,11 +447,8 @@ fn run_dialog_off_thread(dialog: filmcraft_ui_egui::native_dialogs::FileDialog) let t = |s: &str| filmcraft_ui_egui::i18n::t(s).to_string(); let media = t("Media"); let project = t("FilmCraft Project"); - let dialog = match dialog { - FileDialog::OpenFile { filter, exts } => FileDialog::OpenFile { filter: t(&filter), exts }, - FileDialog::SaveAs { filter, exts, name } => FileDialog::SaveAs { filter: t(&filter), exts, name }, - other => other, - }; + let project_exts = project_patterns(); + let dialog = prepare_dialog(dialog); let (tx, rx) = std::sync::mpsc::channel(); let spawned = std::thread::Builder::new().name("file-dialog".into()).spawn(move || { let shown = std::panic::catch_unwind(move || { @@ -462,9 +459,9 @@ fn run_dialog_off_thread(dialog: filmcraft_ui_egui::native_dialogs::FileDialog) } FileDialog::OpenFile { filter, exts } => rfd::FileDialog::new().add_filter(&filter, &exts).pick_file().map(path).into_iter().collect(), FileDialog::Relink { exts, .. } => rfd::FileDialog::new().add_filter(&media, &exts).pick_file().map(path).into_iter().collect(), - FileDialog::OpenProject => rfd::FileDialog::new().add_filter(&project, &["fcproj"]).pick_file().map(path).into_iter().collect(), + FileDialog::OpenProject => rfd::FileDialog::new().add_filter(&project, &project_exts).pick_file().map(path).into_iter().collect(), FileDialog::SaveProject { name } => { - rfd::FileDialog::new().add_filter(&project, &["fcproj"]).set_file_name(name).save_file().map(path).into_iter().collect() + rfd::FileDialog::new().add_filter(&project, &project_exts).set_file_name(name).save_file().map(path).into_iter().collect() } FileDialog::SaveAs { filter, exts, name } => { rfd::FileDialog::new().add_filter(&filter, &exts).set_file_name(name).save_file().map(path).into_iter().collect() @@ -492,6 +489,29 @@ fn run_dialog_off_thread(dialog: filmcraft_ui_egui::native_dialogs::FileDialog) rx } +/// A dialog as the Linux dialog thread shows it: filter names translated (here, on the UI thread: +/// the interface language is per thread) and every extension list turned into the +/// case-insensitive patterns the synchronous pickers use, so `CLIP.MP4` stays visible (#262). +#[cfg(target_os = "linux")] +fn prepare_dialog(dialog: filmcraft_ui_egui::native_dialogs::FileDialog) -> filmcraft_ui_egui::native_dialogs::FileDialog { + use filmcraft_ui_egui::native_dialogs::FileDialog; + let t = |s: &str| filmcraft_ui_egui::i18n::t(s).to_string(); + let patterns = |exts: Vec| file_filters::extensions(&exts.iter().map(String::as_str).collect::>()); + match dialog { + FileDialog::OpenFiles { exts } => FileDialog::OpenFiles { exts: patterns(exts) }, + FileDialog::OpenFile { filter, exts } => FileDialog::OpenFile { filter: t(&filter), exts: patterns(exts) }, + FileDialog::SaveAs { filter, exts, name } => FileDialog::SaveAs { filter: t(&filter), exts: patterns(exts), name }, + FileDialog::Relink { exts, hint } => FileDialog::Relink { exts: patterns(exts), hint }, + other => other, + } +} + +/// The project dialogs' filter patterns (`EDIT.FCPROJ` included). +#[cfg(target_os = "linux")] +fn project_patterns() -> Vec { + file_filters::extensions(&["fcproj"]) +} + #[cfg(test)] mod tests { /// Start-up registers the hardware decoders whether or not anything is logged (no logger is @@ -505,4 +525,31 @@ mod tests { || (cfg!(target_os = "linux") && matches!(hardware, filmcraft_platform::Availability::Available(_))); assert_eq!(filmcraft_platform::registered(), expected, "{hardware:?}"); } + + /// #262 on Linux: the dialogs shown off the UI thread (#260) filter with the same + /// case-insensitive patterns as the synchronous pickers, so upper- and mixed-case files show. + #[cfg(target_os = "linux")] + #[test] + fn off_thread_dialogs_keep_case_insensitive_filters() { + use filmcraft_ui_egui::RelinkHint; + use filmcraft_ui_egui::native_dialogs::FileDialog; + let mp4 = || vec!["mp4".to_string()]; + let hint = RelinkHint { command: "media.relink".into(), params: serde_json::json!({}) }; + for dialog in [ + FileDialog::OpenFiles { exts: mp4() }, + FileDialog::OpenFile { filter: "Media".into(), exts: mp4() }, + FileDialog::SaveAs { filter: "Media".into(), exts: mp4(), name: "clip.mp4".into() }, + FileDialog::Relink { exts: mp4(), hint }, + ] { + let exts = match super::prepare_dialog(dialog) { + FileDialog::OpenFiles { exts } | FileDialog::OpenFile { exts, .. } | FileDialog::SaveAs { exts, .. } | FileDialog::Relink { exts, .. } => exts, + other => panic!("the dialog changed kind: {other:?}"), + }; + assert_eq!(exts.first().map(String::as_str), Some("mp4"), "the literal extension stays first (save suffix)"); + assert!(exts.iter().any(|e| e == "[mM][pP]4"), "{exts:?}"); + } + let project = super::project_patterns(); + assert_eq!(project.first().map(String::as_str), Some("fcproj")); + assert!(project.iter().any(|e| e == "[fF][cC][pP][rR][oO][jJ]"), "{project:?}"); + } }