From 0678c579c05d99a6783b8fe442e46b2e3197b79e Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:22:44 +0300 Subject: [PATCH] ATTACH: FILEDIA, -ATTACH command line and reference file types Co-Authored-By: Claude Opus 5.5 --- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 7 ++ locales/bg-BG/opencadstudio.ftl | 7 ++ locales/cs-CZ/opencadstudio.ftl | 7 ++ locales/de-DE/opencadstudio.ftl | 7 ++ locales/el-GR/opencadstudio.ftl | 7 ++ locales/en-US/opencadstudio.ftl | 7 ++ locales/es-ES/opencadstudio.ftl | 7 ++ locales/fi-FI/opencadstudio.ftl | 7 ++ locales/fr-FR/opencadstudio.ftl | 7 ++ locales/hi-IN/opencadstudio.ftl | 7 ++ locales/hu-HU/opencadstudio.ftl | 7 ++ locales/it-IT/opencadstudio.ftl | 7 ++ locales/ja-JP/opencadstudio.ftl | 7 ++ locales/ko-KR/opencadstudio.ftl | 7 ++ locales/lo-LA/opencadstudio.ftl | 7 ++ locales/nl-NL/opencadstudio.ftl | 7 ++ locales/pl-PL/opencadstudio.ftl | 7 ++ locales/pt-BR/opencadstudio.ftl | 7 ++ locales/ru-RU/opencadstudio.ftl | 7 ++ locales/tr-TR/opencadstudio.ftl | 7 ++ locales/zh-CN/opencadstudio.ftl | 7 ++ locales/zh-TW/opencadstudio.ftl | 7 ++ src/app/commands/blocks.rs | 2 +- src/app/commands/mod.rs | 2 + src/app/commands/styleprops.rs | 30 +++++++ src/app/commands/xref_attach.rs | 133 +++++++++++++++++++++----------- src/app/settings.rs | 6 ++ src/app/update/file.rs | 4 + src/locale_catalog.rs | 7 ++ src/modules/insert/xattach.rs | 3 + src/modules/insert/xref_cmd.rs | 108 ++++++++++++++++++++++++++ src/sys.rs | 115 +++++++++++++++++++++++++++ 33 files changed, 519 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 638c98922..07efcd499 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ naga = { version = "27", features = ["wgsl-in"] } [target.'cfg(target_os = "windows")'.dependencies] libloading = "0.8" -windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_UI_Input", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_Security", "Win32_System_Com", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Graphics_Printing", "Win32_Graphics_Gdi", "Win32_Storage_Xps", "Win32_Globalization", "Win32_Security_Authentication_Identity"] } +windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_UI_Input", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_Security", "Win32_System_Com", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Graphics_Printing", "Win32_Graphics_Gdi", "Win32_Storage_Xps", "Win32_Globalization", "Win32_Security_Authentication_Identity", "Win32_UI_Controls_Dialogs"] } [target.'cfg(target_os = "linux")'.dependencies] ashpd = { version = "0.13.13", default-features = false, features = ["async-io", "wayland"] } diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 506cc6a3a..117d5458c 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -3737,6 +3737,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 491097bb2..76e574e41 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -3724,6 +3724,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 678e9179d..d45299bdc 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -3722,6 +3722,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index b5d3e3009..e9a9e4f07 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -3725,6 +3725,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 3cd9057c4..2aca93064 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -3725,6 +3725,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 47dd8e2b0..09a6fb165 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -3725,6 +3725,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 5ee417586..3339cd89b 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -3725,6 +3725,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 9af55c14a..940d78f2a 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -3724,6 +3724,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 08b0e100d..75a3d1920 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -3725,6 +3725,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index c520b2135..4c9f792db 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -3713,6 +3713,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 9b40b57ca..418ddbaf2 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -3723,6 +3723,13 @@ insert = .xref-attach-hide-details = Részletek elrejtése .xref-attach-select-file = Hivatkozási fájl kiválasztása .xref-attach-all-files = Minden hivatkozási fájl + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Külső fájlokra – például más rajzokra, raszterképekre és alávetítésekre – mutató hivatkozásokat szúr be. .xref-attach-invalid-input = Érvénytelen bevitel. .xref-insertion-point-x = Beszúrási pont X diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index b494306f2..474d50077 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -3724,6 +3724,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 88381d6b0..0e255550b 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -3715,6 +3715,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 265f722ce..236647f2f 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -3724,6 +3724,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/lo-LA/opencadstudio.ftl b/locales/lo-LA/opencadstudio.ftl index f419faa2a..2d47c0b6a 100644 --- a/locales/lo-LA/opencadstudio.ftl +++ b/locales/lo-LA/opencadstudio.ftl @@ -3727,6 +3727,13 @@ insert = .xref-attach-hide-details = ເຊື່ອງລາຍລະອຽດ .xref-attach-select-file = ເລືອກໄຟລ໌ອ້າງອີງ .xref-attach-all-files = ໄຟລ໌ອ້າງອີງທັງໝົດ + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = ແຊກການອ້າງອີງໃສ່ໄຟລ໌ພາຍນອກ ເຊັ່ນ: ຮູບແຕ້ມອື່ນ, ຮູບພາບແຣັສເຕີ ແລະ ຊັ້ນຮອງພື້ນ. .xref-attach-invalid-input = ຂໍ້ມູນປ້ອນເຂົ້າບໍ່ຖືກຕ້ອງ. .xref-insertion-point-x = ຈຸດແຊກ X diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 64b4faa7c..93b28fb8f 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -3723,6 +3723,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index e0c88c3a4..ffa80e678 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -3724,6 +3724,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 18d9cd8cd..3861c9ef4 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -3725,6 +3725,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index df43b135a..a87abbdbf 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -3726,6 +3726,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 83e54cc6a..5ff5c41dc 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -3698,6 +3698,13 @@ insert = .xref-attach-hide-details = Ayrıntıları Gizle .xref-attach-select-file = Referans Dosyası Seç .xref-attach-all-files = Tüm Referans Dosyaları + .xref-attach-filter-images = Tüm görüntü dosyaları + .xref-attach-filter-dwf = DWF dosyaları (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = Tüm DGN Dosyaları (*.*) + .xref-attach-filter-pdf = PDF dosyaları (*.pdf) + .xref-attach-filter-point-cloud = Nokta Bulutu (*.rcp; *.rcs) + .xref-attach-filter-all = Tüm dosyalar (*.*) .xref-attach-help = Diğer çizimler, raster görüntüler ve altlıklar gibi harici dosyalara referans ekler. .xref-attach-invalid-input = Geçersiz giriş. .xref-insertion-point-x = Ekleme noktası X diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 373580f22..13e7d3ad3 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -3712,6 +3712,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index fe9ae1a53..45f504153 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -3724,6 +3724,13 @@ insert = .xref-attach-hide-details = Hide Details .xref-attach-select-file = Select Reference File .xref-attach-all-files = All Reference Files + .xref-attach-filter-images = All image files + .xref-attach-filter-dwf = DWF files (*.dwfx; *.dwf) + .xref-attach-filter-dgn = MicroStation DGN (*.dgn) + .xref-attach-filter-all-dgn = All DGN Files (*.*) + .xref-attach-filter-pdf = PDF files (*.pdf) + .xref-attach-filter-point-cloud = Point Cloud (*.rcp; *.rcs) + .xref-attach-filter-all = All files (*.*) .xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays. .xref-attach-invalid-input = Invalid input. .xref-insertion-point-x = Insertion point X diff --git a/src/app/commands/blocks.rs b/src/app/commands/blocks.rs index b9a74490b..0c3fee66b 100644 --- a/src/app/commands/blocks.rs +++ b/src/app/commands/blocks.rs @@ -869,7 +869,7 @@ impl OpenCADStudio { .split_once(char::is_whitespace) .map(|(verb, rest)| (verb.to_ascii_uppercase(), rest.trim())) .unwrap_or_else(|| (cmd.to_ascii_uppercase(), "")); - if verb == "PDFATTACH" && rest.is_empty() { + if verb == "PDFATTACH" && rest.is_empty() && crate::sys::file_dia() { return Some(Task::done(Message::PdfAttachPick)); } let insunits = self.tabs[i].scene.document.header.insertion_units; diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index ba82aeeba..bb9611d88 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -712,6 +712,7 @@ inventory::submit!(crate::command::CommandRegistration { "PDFOSNAP", "UOSNAP", "FIELDDISPLAY", + "FILEDIA", "SURFACEASSOCIATIVITY", "LAYOUTCREATEVIEWPORT", "FIELDEVAL", @@ -944,6 +945,7 @@ inventory::submit!(crate::command::CommandRegistration { "XR", "XREF", "-XREF", + "-ATTACH", "XRELOAD", "ZOOM", "ZS", diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index c556efa81..116f02564 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -1210,6 +1210,7 @@ impl OpenCADStudio { | "DGNOSNAP" | "UOSNAP" | "FIELDDISPLAY" + | "FILEDIA" | "SURFACEASSOCIATIVITY" | "UCSORTHO" | "UCSVIEW" @@ -1593,6 +1594,35 @@ impl OpenCADStudio { } return Some(self.finish_dispatch(cmd)); } + if name == "FILEDIA" { + let current = i16::from(crate::sys::file_dia()); + match value + .as_deref() + .map(|v| v.trim().parse::().ok().filter(|v| (0..=1).contains(v))) + { + Some(Some(mode)) => { + if current != mode { + crate::sys::set_file_dia(mode == 1); + self.save_config(); + } + } + Some(None) => { + self.command_line + .push_error(crate::t!("Requires 0 or 1 only.").as_ref()); + self.command_line.push_output(&format!( + "Enter new value for FILEDIA <{current}>:" + )); + self.pending_setvar = Some(name.clone()); + } + None => { + self.command_line.push_output(&format!( + "Enter new value for FILEDIA <{current}>:" + )); + self.pending_setvar = Some(name.clone()); + } + } + return Some(self.finish_dispatch(cmd)); + } if matches!( name.as_str(), "PDFOSNAP" | "DWFOSNAP" | "DGNOSNAP" | "UOSNAP" diff --git a/src/app/commands/xref_attach.rs b/src/app/commands/xref_attach.rs index 5e4827744..363bdfd36 100644 --- a/src/app/commands/xref_attach.rs +++ b/src/app/commands/xref_attach.rs @@ -8,9 +8,23 @@ use crate::modules::insert::xattach::{ existing_reference, path_to_block_name, prepare_xref_definition, stored_path, XAttachCommand, XrefAttachRequest, XrefPlacement, }; +use crate::modules::insert::xattach::IMAGE_EXTENSIONS; use crate::modules::insert::xref_cmd::display_path; use crate::ui::window::xref_attach::{XrefAttachMsg, XrefAttachState}; +/// ATTACH's file types, in the reference order (the picker counts from 1). +const ATTACH_FILTERS: [(&str, &[&str]); 8] = [ + ("All image files", IMAGE_EXTENSIONS), + ("DWF files (*.dwfx; *.dwf)", &["dwfx", "dwf"]), + ("MicroStation DGN (*.dgn)", &["dgn"]), + ("All DGN Files (*.*)", &["*"]), + ("PDF files (*.pdf)", &["pdf"]), + ("Point Cloud (*.rcp; *.rcs)", &["rcp", "rcs"]), + ("Drawing (*.dwg)", &["dwg"]), + ("All files (*.*)", &["*"]), +]; +const ALL_DGN_FILTER: u32 = 4; + impl OpenCADStudio { /// DWFATTACH / DGNATTACH on the command line: with a file, from its /// sheet / model prompt; without one, from the path prompt. @@ -42,12 +56,26 @@ impl OpenCADStudio { Some((verb, rest)) => (verb.to_ascii_uppercase(), rest.trim()), None => (upper.clone(), ""), }; + // FILEDIA 0 sends the picking commands to their command-line forms. + let dialog = crate::sys::file_dia(); match (verb.as_str(), rest.is_empty()) { - ("ATTACH", true) => Some(Task::done(Message::AttachPick)), - ("DWFATTACH", true) => Some(Task::done(Message::UnderlayAttachPick( + ("ATTACH", true) if dialog => Some(Task::done(Message::AttachPick)), + ("ATTACH", true) | ("-ATTACH", _) => { + use crate::command::CadCommand; + let command = crate::modules::insert::xref_cmd::AttachCommand::new(); + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + if !rest.is_empty() { + return Some( + self.feed_command(crate::command::StepInput::Text(rest.to_string())), + ); + } + Some(Task::none()) + } + ("DWFATTACH", true) if dialog => Some(Task::done(Message::UnderlayAttachPick( codec::entities::UnderlayType::Dwf, ))), - ("DGNATTACH", true) => Some(Task::done(Message::UnderlayAttachPick( + ("DGNATTACH", true) if dialog => Some(Task::done(Message::UnderlayAttachPick( codec::entities::UnderlayType::Dgn, ))), // A file after DWFATTACH / DGNATTACH, or the - forms, go on the @@ -62,7 +90,9 @@ impl OpenCADStudio { } // POINTCLOUDATTACH picks the file; with a file, or as // -POINTCLOUDATTACH, it asks on the command line. - ("POINTCLOUDATTACH", true) => Some(Task::done(Message::PointCloudAttachPick)), + ("POINTCLOUDATTACH", true) if dialog => { + Some(Task::done(Message::PointCloudAttachPick)) + } ("POINTCLOUDATTACH" | "-POINTCLOUDATTACH", _) => { use crate::command::CadCommand; let command = crate::modules::insert::pc_attach::PointCloudAttachCommand::new(); @@ -75,7 +105,14 @@ impl OpenCADStudio { } Some(Task::none()) } - ("XATTACH", true) => Some(Task::done(Message::XAttachPick)), + ("XATTACH", true) if dialog => Some(Task::done(Message::XAttachPick)), + ("XATTACH", true) => { + let command = + crate::modules::insert::xref_cmd::XrefCommand::attach(self.saved_xref_paths(i)); + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + Some(Task::none()) + } // A file named on the command line skips the picker. // Handled at once, so the placement it starts belongs to this // command line (automation included). @@ -96,15 +133,8 @@ impl OpenCADStudio { Some(self.finish_dispatch(cmd)) } ("-XREF", true) => { - let saved: Vec<(String, String)> = self.tabs[i] - .scene - .document - .block_records - .iter() - .filter(|br| br.flags.is_xref || br.flags.is_xref_overlay) - .map(|br| (br.name.clone(), br.xref_path.clone())) - .collect(); - let command = crate::modules::insert::xref_cmd::XrefCommand::new(saved); + let command = + crate::modules::insert::xref_cmd::XrefCommand::new(self.saved_xref_paths(i)); self.command_line.push_info(&command.prompt()); self.tabs[i].active_cmd = Some(Box::new(command)); Some(Task::none()) @@ -128,6 +158,18 @@ impl OpenCADStudio { } } + /// Saved paths by reference name, for -XREF. + fn saved_xref_paths(&self, i: usize) -> Vec<(String, String)> { + self.tabs[i] + .scene + .document + .block_records + .iter() + .filter(|br| br.flags.is_xref || br.flags.is_xref_overlay) + .map(|br| (br.name.clone(), br.xref_path.clone())) + .collect() + } + /// `-XREF ?` — the reference table as the reference application prints it. fn list_xrefs(&mut self, i: usize, pattern: &str) { let rows: Vec<(String, &'static str, String)> = self.tabs[i] @@ -392,40 +434,41 @@ impl OpenCADStudio { }, Message::AttachPickResult, ), - Message::AttachPick => Task::perform( - async { - let handle = crate::sys::file_dialog() - .set_title(crate::t!("Select Reference File").as_ref()) - .add_filter( - crate::t!("All Reference Files").as_ref(), - &[ - "dwg", "dxf", "pdf", "dwf", "dwfx", "dgn", "png", "jpg", "jpeg", - "bmp", "tif", "tiff", "rcp", "rcs", - ], - ) - .add_filter(crate::t!("Drawing").as_ref(), &["dwg", "dxf"]) - .add_filter(crate::t!("PDF Files").as_ref(), &["pdf"]) - .add_filter(crate::t!("DWF Files").as_ref(), &["dwf", "dwfx"]) - .add_filter(crate::t!("DGN Files").as_ref(), &["dgn"]) - .add_filter( - crate::t!("Images").as_ref(), - &["png", "jpg", "jpeg", "bmp", "tif", "tiff"], - ) - .add_filter(crate::t!("Point Clouds").as_ref(), &["rcp", "rcs"]) - .pick_file() - .await; - match handle { - Some(h) => Ok(crate::sys::handle_path(&h)), - None => Err("Cancelled".to_string()), - } - }, - Message::AttachPickResult, - ), + Message::AttachPick => { + let filters = ATTACH_FILTERS + .iter() + .map(|(label, extensions)| (crate::t!(label).to_string(), *extensions)) + .collect(); + let title = crate::t!("Select Reference File").to_string(); + let index = crate::sys::attach_filter(); + Task::perform( + async move { + match crate::sys::pick_file_with_filter(title, filters, index).await { + Some((path, index)) => { + crate::sys::set_attach_filter(index); + Ok(path) + } + None => Err("Cancelled".to_string()), + } + }, + Message::AttachPickResult, + ) + } Message::AttachPickResult(Ok(path)) => { - let ext = path + // The file type the picker was left on is a profile setting. + self.save_config(); + let mut ext = path .extension() .map(|e| e.to_string_lossy().to_ascii_lowercase()) .unwrap_or_default(); + // Any file picked under "All DGN Files" is a DGN. + let known = ["dwg", "dxf", "pdf", "dwf", "dwfx", "dgn", "rcp", "rcs"]; + if crate::sys::attach_filter() == ALL_DGN_FILTER + && !known.contains(&ext.as_str()) + && !IMAGE_EXTENSIONS.contains(&ext.as_str()) + { + ext = "dgn".to_string(); + } match ext.as_str() { "pdf" => match std::fs::read(&path) { Ok(bytes) => self.update(Message::PdfAttachPickResult(Ok(( @@ -477,7 +520,7 @@ impl OpenCADStudio { self.open_point_cloud_attach_dialog(&path); Task::none() } - "png" | "jpg" | "jpeg" | "bmp" | "tif" | "tiff" => { + e if IMAGE_EXTENSIONS.contains(&e) => { let result = image::image_dimensions(&path) .map(|(w, h)| (path.clone(), w, h)) .map_err(|e| e.to_string()); diff --git a/src/app/settings.rs b/src/app/settings.rs index 960a2a54c..5458b1ca6 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -417,6 +417,10 @@ pub struct UserSettings { pub attdef_on_screen: bool, /// FIELDDISPLAY: fields show on a gray background. pub field_display: bool, + /// FILEDIA: file dialogs of commands that have a command-line form. + pub file_dia: bool, + /// The file type ATTACH's picker was last left on (1-based). + pub attach_filter: u32, /// SURFACEASSOCIATIVITY: new surfaces stay associative to their sources. pub surface_associativity: bool, /// UCSORTHO: an orthographic view preset also sets its orthographic UCS. @@ -842,6 +846,8 @@ impl Default for UserSettings { image_frame: 1, attdef_on_screen: true, field_display: true, + file_dia: true, + attach_filter: 7, surface_associativity: true, ucs_ortho: true, ucs_view: true, diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 9d78560ef..ec876ccd7 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -793,6 +793,8 @@ impl OpenCADStudio { image_frame: crate::scene::frame::profile_image_mode(), attdef_on_screen: crate::modules::draw::draw::attdef::session().on_screen, field_display: crate::entities::field::display(), + file_dia: crate::sys::file_dia(), + attach_filter: crate::sys::attach_filter(), surface_associativity: crate::scene::model::sweep_model::surface_associativity(), ucs_ortho: crate::modules::view::view_cmd::ucs_ortho(), ucs_view: crate::modules::view::view_cmd::ucs_view(), @@ -909,6 +911,8 @@ impl OpenCADStudio { crate::scene::frame::set_profile_image_mode(s.image_frame); crate::modules::draw::draw::attdef::session().on_screen = s.attdef_on_screen; crate::entities::field::set_display(s.field_display); + crate::sys::set_file_dia(s.file_dia); + crate::sys::set_attach_filter(s.attach_filter); crate::scene::model::sweep_model::set_surface_associativity(s.surface_associativity); crate::modules::view::view_cmd::set_ucs_ortho(s.ucs_ortho); crate::modules::view::view_cmd::set_ucs_view(s.ucs_view); diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 62719b52e..9938b2048 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -686,6 +686,13 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Hide Details" => Some(("insert", "xref-attach-hide-details")), "Select Reference File" => Some(("insert", "xref-attach-select-file")), "All Reference Files" => Some(("insert", "xref-attach-all-files")), + "All image files" => Some(("insert", "xref-attach-filter-images")), + "DWF files (*.dwfx; *.dwf)" => Some(("insert", "xref-attach-filter-dwf")), + "MicroStation DGN (*.dgn)" => Some(("insert", "xref-attach-filter-dgn")), + "All DGN Files (*.*)" => Some(("insert", "xref-attach-filter-all-dgn")), + "PDF files (*.pdf)" => Some(("insert", "xref-attach-filter-pdf")), + "Point Cloud (*.rcp; *.rcs)" => Some(("insert", "xref-attach-filter-point-cloud")), + "All files (*.*)" => Some(("insert", "xref-attach-filter-all")), "Inserts references to external files such as other drawings, raster images, and underlays." => Some(("insert", "xref-attach-help")), "Invalid input." => Some(("insert", "xref-attach-invalid-input")), "Insertion point X" => Some(("insert", "xref-insertion-point-x")), diff --git a/src/modules/insert/xattach.rs b/src/modules/insert/xattach.rs index 18966b46e..c98005b87 100644 --- a/src/modules/insert/xattach.rs +++ b/src/modules/insert/xattach.rs @@ -147,6 +147,9 @@ pub struct XAttachCommand { const NONZERO: &str = "Value must be nonzero."; +/// Raster image files ATTACH places as images. +pub const IMAGE_EXTENSIONS: &[&str] = &["bmp", "gif", "jpg", "jpeg", "png", "tif", "tiff", "webp"]; + impl XAttachCommand { pub fn new(request: XrefAttachRequest, placement: XrefPlacement, label: &'static str) -> Self { let block_name = path_to_block_name(&request.path); diff --git a/src/modules/insert/xref_cmd.rs b/src/modules/insert/xref_cmd.rs index c0628ee4d..fb7e4645d 100644 --- a/src/modules/insert/xref_cmd.rs +++ b/src/modules/insert/xref_cmd.rs @@ -36,6 +36,14 @@ impl XrefCommand { } } + /// -XREF already at its Attach file prompt (XATTACH while FILEDIA is 0). + pub fn attach(saved_paths: Vec<(String, String)>) -> Self { + Self { + step: Step::File { overlay: false }, + saved_paths, + } + } + /// Whether any reference name matches `pattern` (`*` / `?` wildcards). fn matches(&self, pattern: &str) -> bool { self.saved_paths @@ -197,6 +205,106 @@ impl CadCommand for XrefCommand { } } +/// -ATTACH (and ATTACH while FILEDIA is 0): the file on the command line, +/// then the attach flow of its file type. +pub struct AttachCommand { + /// The drawing whose reference type is asked; `None` while asking for the file. + drawing: Option, +} + +impl AttachCommand { + pub fn new() -> Self { + Self { drawing: None } + } + + fn reference(path: &str, overlay: bool) -> CmdResult { + let verb = if overlay { + "XREFOVERLAYFILE" + } else { + "XREFATTACHFILE" + }; + CmdResult::Dispatch(format!("{verb} {path}")) + } +} + +impl CadCommand for AttachCommand { + fn name(&self) -> &'static str { + "-ATTACH" + } + + fn prompt(&self) -> String { + match self.drawing { + None => "-ATTACH Path to file to attach:".to_string(), + Some(_) => { + "-ATTACH Specify reference type [Attachment/Overlay] :".to_string() + } + } + } + + fn options(&self) -> Vec { + match self.drawing { + None => Vec::new(), + Some(_) => vec![ + CmdOption::new("Attachment", "A"), + CmdOption::new("Overlay", "O"), + ], + } + } + + fn input_kind(&self) -> InputKind { + match self.drawing { + None => InputKind::FreeText, + Some(_) => InputKind::SingleToken, + } + } + + fn on_point(&mut self, _pt: DVec3) -> CmdResult { + CmdResult::NeedPoint + } + + fn on_text_input(&mut self, text: &str) -> Option { + let value = text.trim().trim_matches('"'); + if let Some(path) = self.drawing.clone() { + return Some(match value.to_uppercase().as_str() { + "A" | "ATTACHMENT" => Self::reference(&path, false), + "O" | "OVERLAY" => Self::reference(&path, true), + _ => CmdResult::ReportError("Invalid option keyword.".to_string()), + }); + } + if value.is_empty() { + return Some(self.on_enter()); + } + if !std::path::Path::new(value).is_file() { + return Some(CmdResult::ReportError(format!("{value} not found."))); + } + let ext = std::path::Path::new(value) + .extension() + .map(|e| e.to_string_lossy().to_ascii_lowercase()) + .unwrap_or_default(); + Some(match ext.as_str() { + "pdf" => CmdResult::Dispatch(format!("-PDFATTACH {value}")), + "dwf" | "dwfx" => CmdResult::Dispatch(format!("-DWFATTACH {value}")), + "dgn" => CmdResult::Dispatch(format!("-DGNATTACH {value}")), + "rcp" | "rcs" => CmdResult::Dispatch(format!("-POINTCLOUDATTACH {value}")), + // Images have no dialog after the file: ATTACH places them. + e if crate::modules::insert::xattach::IMAGE_EXTENSIONS.contains(&e) => { + CmdResult::Dispatch(format!("ATTACH {value}")) + } + _ => { + self.drawing = Some(value.to_string()); + CmdResult::NeedPoint + } + }) + } + + fn on_enter(&mut self) -> CmdResult { + match self.drawing.clone() { + None => CmdResult::ReportError("Filename cannot be blank".to_string()), + Some(path) => Self::reference(&path, false), + } + } +} + /// A stored path shown the way the platform writes it. pub fn display_path(path: &str) -> String { if cfg!(windows) { diff --git a/src/sys.rs b/src/sys.rs index 0bbcfb82d..7b6c43cf1 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -587,6 +587,121 @@ pub fn file_dialog() -> rfd::AsyncFileDialog { rfd::AsyncFileDialog::new() } +// ── FILEDIA and file pickers that keep their file type ─────────────────────── + +static FILE_DIA: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true); + +/// FILEDIA (a profile setting): commands that have a command-line form show +/// their file dialogs only while it is on. +pub fn file_dia() -> bool { + FILE_DIA.load(std::sync::atomic::Ordering::Relaxed) +} + +pub fn set_file_dia(on: bool) { + FILE_DIA.store(on, std::sync::atomic::Ordering::Relaxed); +} + +/// The file type ATTACH's picker was last left on (1-based; 7 = Drawing). +static ATTACH_FILTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(7); + +pub fn attach_filter() -> u32 { + ATTACH_FILTER.load(std::sync::atomic::Ordering::Relaxed) +} + +pub fn set_attach_filter(index: u32) { + ATTACH_FILTER.store(index, std::sync::atomic::Ordering::Relaxed); +} + +/// A file type of [`pick_file_with_filter`]: its label and extensions +/// (`"*"` for every file). +pub type FileFilter = (String, &'static [&'static str]); + +/// Open-file dialog that starts on file type `index` (1-based) and returns the +/// picked path with the file type the dialog was left on. +#[cfg(target_os = "windows")] +pub async fn pick_file_with_filter( + title: String, + filters: Vec, + index: u32, +) -> Option<(std::path::PathBuf, u32)> { + // The dialog blocks, so it runs on its own thread like the async picker. + let (tx, rx) = iced::futures::channel::oneshot::channel(); + std::thread::spawn(move || { + let _ = tx.send(open_file_dialog_blocking(&title, &filters, index)); + }); + rx.await.ok().flatten() +} + +#[cfg(target_os = "windows")] +fn open_file_dialog_blocking( + title: &str, + filters: &[FileFilter], + index: u32, +) -> Option<(std::path::PathBuf, u32)> { + use windows_sys::Win32::System::Com::{CoInitializeEx, COINIT_APARTMENTTHREADED}; + use windows_sys::Win32::UI::Controls::Dialogs::{ + GetOpenFileNameW, OFN_EXPLORER, OFN_FILEMUSTEXIST, OFN_HIDEREADONLY, OFN_NOCHANGEDIR, + OFN_PATHMUSTEXIST, OPENFILENAMEW, + }; + let wide = |s: &str| s.encode_utf16().chain([0]).collect::>(); + let mut filter = Vec::new(); + for (label, extensions) in filters { + let pattern = extensions + .iter() + .map(|e| format!("*.{e}")) + .collect::>() + .join(";"); + filter.extend(wide(label)); + filter.extend(wide(&pattern)); + } + filter.push(0); + let title = wide(title); + let dir = crate::config::last_dialog_dir().map(|d| wide(&d.to_string_lossy())); + let mut file = vec![0u16; 32768]; + let mut ofn = OPENFILENAMEW { + lStructSize: std::mem::size_of::() as u32, + lpstrFilter: filter.as_ptr(), + nFilterIndex: index, + lpstrFile: file.as_mut_ptr(), + nMaxFile: file.len() as u32, + lpstrTitle: title.as_ptr(), + lpstrInitialDir: dir.as_ref().map_or(std::ptr::null(), |d| d.as_ptr()), + Flags: OFN_EXPLORER + | OFN_FILEMUSTEXIST + | OFN_PATHMUSTEXIST + | OFN_NOCHANGEDIR + | OFN_HIDEREADONLY, + ..Default::default() + }; + // SAFETY: every pointer in `ofn` points into buffers that outlive the call. + let picked = unsafe { + CoInitializeEx(std::ptr::null(), COINIT_APARTMENTTHREADED as u32); + GetOpenFileNameW(&mut ofn) + }; + if picked == 0 { + return None; + } + let len = file.iter().position(|&c| c == 0).unwrap_or(file.len()); + let path = std::path::PathBuf::from(String::from_utf16_lossy(&file[..len])); + crate::config::remember_dialog_dir(&path); + Some((path, ofn.nFilterIndex)) +} + +/// Elsewhere the picker cannot start on a file type; the index passes through. +#[cfg(not(target_os = "windows"))] +pub async fn pick_file_with_filter( + title: String, + filters: Vec, + index: u32, +) -> Option<(std::path::PathBuf, u32)> { + let mut dialog = file_dialog().set_title(title); + for (label, extensions) in &filters { + dialog = dialog.add_filter(label, extensions); + } + let handle = dialog.pick_file().await?; + Some((handle_path(&handle), index)) +} + /// Trigger a browser download of `bytes` as `name`. Builds a Blob, points a /// hidden `` at it and clicks it programmatically — because this /// runs inside the Save button's click (a user gesture), the file downloads