From 5c9f19a4dce1a9086a2b5af7e3293f76a0325da3 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:08:37 +0300 Subject: [PATCH] Sheet sets: import statuses, view navigation fields, drawing lock files Import Layouts as Sheets shows the reference's statuses (already part of a sheet set / may belong to another sheet set / available) and Browse replaces the list; the Field dialog SheetView tree lists sheet views with their properties, scale formats and hyperlinks; drawings open in tabs get .dwl/.dwl2 lock files that other instances read as open; the codec pin moves to the revision with sheet views and the default output folder. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 3 +- locales/bg-BG/opencadstudio.ftl | 3 +- locales/cs-CZ/opencadstudio.ftl | 3 +- locales/de-DE/opencadstudio.ftl | 3 +- locales/el-GR/opencadstudio.ftl | 3 +- locales/en-US/opencadstudio.ftl | 3 +- locales/es-ES/opencadstudio.ftl | 3 +- locales/fi-FI/opencadstudio.ftl | 3 +- locales/fr-FR/opencadstudio.ftl | 3 +- locales/hi-IN/opencadstudio.ftl | 3 +- locales/hu-HU/opencadstudio.ftl | 3 +- locales/it-IT/opencadstudio.ftl | 3 +- locales/ja-JP/opencadstudio.ftl | 3 +- locales/ko-KR/opencadstudio.ftl | 3 +- locales/nl-NL/opencadstudio.ftl | 3 +- locales/pl-PL/opencadstudio.ftl | 3 +- locales/pt-BR/opencadstudio.ftl | 3 +- locales/ru-RU/opencadstudio.ftl | 3 +- locales/tr-TR/opencadstudio.ftl | 3 +- locales/zh-CN/opencadstudio.ftl | 3 +- locales/zh-TW/opencadstudio.ftl | 3 +- src/app/commands/sheet_set.rs | 107 +++++++++++++++++++++++++++----- src/app/update/file.rs | 4 ++ src/app/view/mod.rs | 10 ++- src/locale_catalog.rs | 3 +- src/ui/window/field_dialog.rs | 82 +++++++++++++++++++++--- src/ui/window/sheet_set.rs | 25 ++++++-- 29 files changed, 236 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c8b0f296..f8e3c0368 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4206,7 +4206,7 @@ dependencies = [ [[package]] name = "opencadcodec" version = "0.5.5" -source = "git+https://github.com/ramox81/cadcodec.git?rev=d86f57d#d86f57d5d179bc57fe7826d70454bece40c20320" +source = "git+https://github.com/ramox81/cadcodec.git?rev=29aff7e#29aff7e8e6f4669cd1f41fc82d39521127c07d03" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index fdb912af0..dfc938973 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "d86f57d" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "29aff7e" } [patch.crates-io] iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index f53bed195..4c5bdb59e 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -4407,6 +4407,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4427,7 +4429,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index e2e9bbfa8..e26e17328 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 72691ae7f..4d93bc6e8 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -4392,6 +4392,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4412,7 +4414,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index f7bc3e6df..420a23a23 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -4393,6 +4393,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4413,7 +4415,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 915802762..96bd2fd4b 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4415,7 +4417,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 8e2758d61..6d902d025 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -4396,6 +4396,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4416,7 +4418,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index f73d3dfbd..ac7d25f50 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4415,7 +4417,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index d1165dea6..d40962493 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 2aa271973..e2145f0f2 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4415,7 +4417,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 024cb6214..d8bb65f77 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -4383,6 +4383,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4403,7 +4405,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 7e3230eec..4cd1b0160 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 9bea307db..12e30fc29 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 8e551ef06..d071f58ba 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -4383,6 +4383,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4403,7 +4405,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 62ed4f766..f3eb7cef7 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 1cb7147ef..93e5149a2 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -4393,6 +4393,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4413,7 +4415,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 834d3a027..1839c8d4f 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 454ca237e..72b4d61f5 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4415,7 +4417,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index b6ba273b7..e983502d3 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -4396,6 +4396,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4416,7 +4418,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index c5264c1f7..54e4faf27 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -4355,6 +4355,8 @@ view = .ss-new-sheet-location = Yeni sayfa konumu .ss-remove-subset = Alt Kümeyi Kaldır .ss-rename-subset = Alt Kümeyi Yeniden Adlandır... + .ss-layout-already-part = Bu düzen zaten bir sayfa kümesinde - içe aktarılamaz. + .ss-layout-may-belong = Uyarı: bu düzen başka bir sayfa kümesine ait olabilir. .ss-status-missing = yok .ss-status-open = açık .ss-view-by-category = Kategoriye göre @@ -4375,7 +4377,6 @@ view = .ss-prefix-with-sheet-number = Önüne sayfa numarası .ss-rename-drawing-file-to-match = Çizim dosyasını eşleşecek şekilde adlandır: .ss-available-for-import = Hazır - .ss-belongs-to-sheet-set = Başka kümede: __ocs_fmt_0__ .ss-drawings = Çizimler .ss-select-drawing-files-containing-layouts = Düzen içeren çizim dosyalarını seçin .ss-browse-for-drawings = Çizimlere Gözat… diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 508d87136..2c28c8be9 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -4378,6 +4378,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4398,7 +4400,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 497b58348..8d879e2db 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -4390,6 +4390,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4410,7 +4412,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs index da1036de1..9b236444a 100644 --- a/src/app/commands/sheet_set.rs +++ b/src/app/commands/sheet_set.rs @@ -9,6 +9,7 @@ use crate::ui::window::sheet_set::{ }; use codec::sheet_set::{self as ss, ComponentKind, LayoutReference, SheetSetData, SheetSetDatabase}; use iced::Task; +use std::collections::HashSet; use std::path::{Path, PathBuf}; /// The variables this family answers for. @@ -63,6 +64,55 @@ fn folder_of(path: &str) -> String { /// Template of a component for new sheets: its own `DefDwtLayout`, else /// the nearest parent's. +/// The lock files' date: the long date of the user's locale, two spaces and +/// the 24-hour time (`5 Ekim 2026 Pazartesi 09:26:39`). +#[cfg(windows)] +fn lock_stamp() -> String { + use windows_sys::Win32::Globalization::{GetDateFormatEx, GetTimeFormatEx, DATE_LONGDATE}; + let wide = |s: &str| s.encode_utf16().chain(std::iter::once(0)).collect::>(); + let mut date = [0u16; 128]; + let mut time = [0u16; 64]; + let format = wide("HH:mm:ss"); + // SAFETY: the buffers outlive the calls and their lengths are passed. + let (d, t) = unsafe { + ( + GetDateFormatEx(std::ptr::null(), DATE_LONGDATE, std::ptr::null(), std::ptr::null(), date.as_mut_ptr(), date.len() as i32, std::ptr::null()), + GetTimeFormatEx(std::ptr::null(), 0, std::ptr::null(), format.as_ptr(), time.as_mut_ptr(), time.len() as i32), + ) + }; + let text = |b: &[u16], n: i32| String::from_utf16_lossy(&b[..(n.max(1) as usize - 1)]); + format!("{} {}", text(&date, d), text(&time, t)) +} + +#[cfg(not(windows))] +fn lock_stamp() -> String { + String::new() +} + +/// `.dwl` (user, machine, date lines) and `.dwl2` (the same +/// as XML), as the reference writes them for a drawing it has open. +// ponytail: .dwl is written as UTF-8; the reference's encoding of non-ASCII +// day names was not measured. +#[cfg(not(target_arch = "wasm32"))] +fn write_lock(path: &Path) -> std::io::Result<()> { + let user = std::env::var("USERNAME").or_else(|_| std::env::var("USER")).unwrap_or_default(); + let machine = std::env::var("COMPUTERNAME").unwrap_or_default(); + let stamp = lock_stamp(); + std::fs::write(path.with_extension("dwl"), format!("{user}\n{machine} \n{stamp}"))?; + std::fs::write( + path.with_extension("dwl2"), + format!( + "\n\n{user}\n{machine} \n\n{stamp}\n" + ), + ) +} + +#[cfg(not(target_arch = "wasm32"))] +pub(in crate::app) fn release_lock(path: &Path) { + let _ = std::fs::remove_file(path.with_extension("dwl")); + let _ = std::fs::remove_file(path.with_extension("dwl2")); +} + /// Whether new sheets of `component` prompt for their template: its own /// "Prompt for template", else the nearest parent's. fn prompts_for_template(db: &SheetSetDatabase, component: &str) -> bool { @@ -92,16 +142,15 @@ fn import_rows(path: &Path, db: &SheetSetDatabase) -> Result, Str let mine = db.sheet_for(&drawing, Some(&layout)).is_some_and(|s| { s.named("Layout").and_then(|r| r.prop("Name")).is_some_and(|n| n.eq_ignore_ascii_case(&layout)) }); - let owner = if mine { - Some(db.name().to_string()) - } else { - link.as_ref().filter(|l| l.layout_name.eq_ignore_ascii_case(&layout) && !l.sheet_set_file_name.is_empty()).map(|l| { - SheetSetDatabase::read(&l.sheet_set_file_name) - .map(|d| d.name().to_string()) - .unwrap_or_else(|_| Path::new(&l.sheet_set_file_name).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default()) - }) - }; - ImportRow { drawing: drawing.clone(), layout, handle, on: owner.is_none(), owner } + // A layout of this set is taken; one the drawing's sheet link + // names for another set is only warned about (still importable). + let warn = !mine + && link.as_ref().is_some_and(|l| { + l.layout_name.eq_ignore_ascii_case(&layout) + && !l.sheet_set_file_name.is_empty() + && db.path.as_deref().is_none_or(|p| ss::path_key(p) != ss::path_key(&l.sheet_set_file_name)) + }); + ImportRow { drawing: drawing.clone(), layout, handle, on: !mine, taken: mine, warn } }) .collect()) } @@ -331,6 +380,30 @@ impl OpenCADStudio { Ok(()) } + /// Lock files for the drawings open in tabs, as other instances expect + /// them (the sheet status shows such a sheet as open); a drawing that + /// already has someone else's lock file is left alone. + #[cfg(not(target_arch = "wasm32"))] + fn sync_drawing_locks(&mut self) { + let open: HashSet = self.tabs.iter().filter_map(|t| t.current_path.clone()).filter(|p| p.exists()).collect(); + let gone: Vec = self.sheet_set.locks.difference(&open).cloned().collect(); + for p in gone { + release_lock(&p); + self.sheet_set.locks.remove(&p); + } + for p in open { + if self.sheet_set.locks.contains(&p) || p.with_extension("dwl").exists() { + continue; + } + if write_lock(&p).is_ok() { + self.sheet_set.locks.insert(p); + } + } + } + + #[cfg(target_arch = "wasm32")] + fn sync_drawing_locks(&mut self) {} + /// Reload every open set whose `.dst` was changed by someone else. fn poll_sheet_sets(&mut self) { let mut changed = false; @@ -463,6 +536,7 @@ impl OpenCADStudio { } } SheetSetMsg::Poll => { + self.sync_drawing_locks(); self.poll_sheet_sets(); // SSMSHEETSTATUS 2: the status is taken again every SSMPOLLTIME seconds. let every = std::time::Duration::from_secs(u64::from(self.sheet_set.settings.poll_time.max(20))); @@ -605,7 +679,7 @@ impl OpenCADStudio { (FieldId::ImportPrefix, Some(SsDialog::Form(f))) => f.prefix = v, (FieldId::ImportRow(k), Some(SsDialog::Form(f))) => { if let Some(r) = f.rows.get_mut(k) { - r.on = v && r.owner.is_none(); + r.on = v && !r.taken; } } (FieldId::RenameOption(k), Some(SsDialog::Form(f))) => { @@ -1098,11 +1172,10 @@ impl OpenCADStudio { Err(e) => self.command_line.push_error(&format!("{}: {e}", p.display())), } } + // The drawings picked replace the list. if let Some(SsDialog::Form(f)) = self.sheet_set.dialog.as_mut() { - for r in added { - if !f.rows.iter().any(|o| ss::path_key(&o.drawing) == ss::path_key(&r.drawing) && o.layout == r.layout) { - f.rows.push(r); - } + if !added.is_empty() { + f.rows = added; } } } @@ -1309,7 +1382,7 @@ impl OpenCADStudio { let path = Path::new(&r.file_name); let state = if !path.exists() { Some(SheetStatus::Missing) - } else if path.with_extension("dwl").exists() || self.tab_showing(path).is_some() { + } else if path.with_extension("dwl").exists() { Some(SheetStatus::Locked) } else { None @@ -1500,7 +1573,7 @@ impl OpenCADStudio { } } FormKind::ImportLayout => { - let chosen: Vec<&ImportRow> = f.rows.iter().filter(|r| r.on && r.owner.is_none()).collect(); + let chosen: Vec<&ImportRow> = f.rows.iter().filter(|r| r.on && !r.taken).collect(); if chosen.is_empty() { f.error = Some(crate::t!("The drawing has no layout to import.").into_owned()); self.sheet_set.dialog = Some(SsDialog::Form(f)); diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 7e6218914..0e26bcc77 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -3308,6 +3308,10 @@ impl OpenCADStudio { for i in 0..self.tabs.len() { let _ = std::fs::remove_file(self.autosave_target(i)); } + #[cfg(not(target_arch = "wasm32"))] + for path in &self.sheet_set.locks { + crate::app::commands::sheet_set::release_lock(path); + } } /// Remove the autosave recovery files, then quit the application. diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 25e2df580..f875ffd8f 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -2646,12 +2646,10 @@ impl OpenCADStudio { let plugin_drain = Subscription::none(); // Open sheet sets reload when their .dst changes on disk. #[cfg(not(target_arch = "wasm32"))] - let sheet_set_poll = if self.sheet_set.sets.is_empty() { - Subscription::none() - } else { - iced::time::every(std::time::Duration::from_secs(1)) - .map(|_| Message::SheetSet(crate::ui::window::sheet_set::SheetSetMsg::Poll)) - }; + // Open sheet sets reload when their .dst changes on disk; the + // drawings' .dwl lock files follow the open tabs. + let sheet_set_poll = iced::time::every(std::time::Duration::from_secs(1)) + .map(|_| Message::SheetSet(crate::ui::window::sheet_set::SheetSetMsg::Poll)); #[cfg(target_arch = "wasm32")] let sheet_set_poll = Subscription::none(); let hatch_pattern_keys = if self.tabs[self.active_tab] diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index bc088b692..eb7532270 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -50,6 +50,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "New sheet location" => Some(("view", "ss-new-sheet-location")), "Remove Subset" => Some(("view", "ss-remove-subset")), "Rename Subset..." => Some(("view", "ss-rename-subset")), + "This layout is already part of a sheet set - not available for import." => Some(("view", "ss-layout-already-part")), + "Warning: this layout may belong to another sheet set." => Some(("view", "ss-layout-may-belong")), "missing" => Some(("view", "ss-status-missing")), "open" => Some(("view", "ss-status-open")), "View by category" => Some(("view", "ss-view-by-category")), @@ -70,7 +72,6 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Prefix with sheet number" => Some(("view", "ss-prefix-with-sheet-number")), "Rename drawing file to match:" => Some(("view", "ss-rename-drawing-file-to-match")), "Available for import" => Some(("view", "ss-available-for-import")), - "Belongs to sheet set {}" => Some(("view", "ss-belongs-to-sheet-set")), "Drawings" => Some(("view", "ss-drawings")), "Select drawing files containing layouts" => Some(("view", "ss-select-drawing-files-containing-layouts")), "Browse for Drawings..." => Some(("view", "ss-browse-for-drawings")), diff --git a/src/ui/window/field_dialog.rs b/src/ui/window/field_dialog.rs index 3a5e8e966..f66ace484 100644 --- a/src/ui/window/field_dialog.rs +++ b/src/ui/window/field_dialog.rs @@ -97,8 +97,21 @@ pub fn nav_nodes(db: &codec::sheet_set::SheetSetDatabase, views: bool) -> Vec<(u let set = db.sheet_set(); let mut out = vec![(0, set.id().to_string(), db.name().to_string(), false)]; if views { - for c in db.view_categories() { + // Views of the unnamed (default) category sit under the set, then each + // named category with its views. + let all: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect(); + let named: Vec<&SsElement> = db.view_categories(); + let in_named = |v: &SsElement| { + codec::sheet_set::SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c)) + }; + for v in all.iter().filter(|v| !in_named(v)) { + out.push((1, v.id().to_string(), number_and_title(v), true)); + } + for c in &named { out.push((1, c.id().to_string(), c.prop("Name").unwrap_or("").to_string(), false)); + for v in all.iter().filter(|v| codec::sheet_set::SheetSetDatabase::view_category_of(v) == Some(c.id())) { + out.push((2, v.id().to_string(), number_and_title(v), true)); + } } return out; } @@ -156,10 +169,32 @@ pub fn nav_props(db: &codec::sheet_set::SheetSetDatabase, id: &str) -> Vec<(Stri v.extend(custom(CUSTOM_SHEET_PROP)); v } + None if el.name == "AcSmSheetView" => fixed(&[ + ("ViewNumberAndTitle", "NumberAndTitle", ""), + ("ViewTitle", "Title", ""), + ("ViewNumber", "Number", ""), + ("ViewportScale", "ViewportScale", ".16.2"), + ]), None => fixed(&[("ViewCategoryName", "Name", "")]), } } +/// The sheet drawing, named view and layout a sheet or sheet view node links to. +pub fn nav_link(db: &codec::sheet_set::SheetSetDatabase, id: &str) -> Option<(String, String, String)> { + let el = db.find(id)?; + let (sheet, view) = if el.name == "AcSmSheetView" { + let views = db.parent_of(id)?; + let sheet = db.parent_of(views.id())?; + (sheet.id().to_string(), el.named("NamedView").and_then(|v| v.prop("Name")).unwrap_or("").to_string()) + } else if codec::sheet_set::ComponentKind::of(el) == Some(codec::sheet_set::ComponentKind::Sheet) { + (id.to_string(), String::new()) + } else { + return None; + }; + let r = db.layout_reference(&sheet, "Layout")?; + Some((r.file_name, view, r.name)) +} + impl FieldDialogState { /// `\AcSm Database("…").SheetSet("…")[.Component("…")].Property` of the /// picked node, with the text case and, on a sheet, the hyperlink to it. @@ -170,19 +205,36 @@ impl FieldDialogState { }; let set = db.sheet_set().id().to_string(); let path = codec::sheet_set::native_path(db.path.as_deref().unwrap_or("")); + // ViewportScale takes a scale format (and its version), not a text case. + let scale = prop == "ViewportScale"; + let version = if scale { + codec::fields::PLOT_SCALE_FORMATS + .get(self.ss_scale_format) + .map_or("", |(_, c)| if c.starts_with("\\AcVar.16.2") { ".16.2" } else { "" }) + } else { + version + }; let mut code = format!("\\AcSm{version} Database(\"{path}\").SheetSet(\"{set}\")"); if self.ss_node != set { code.push_str(&format!(".Component(\"{}\")", self.ss_node)); } code.push('.'); code.push_str(&prop); + if scale { + let f = viewport_scale_picture(self.ss_scale_format); + if !f.is_empty() { + code.push(' '); + code.push_str(&f); + } + return code; + } if self.text_case != 0 { code.push_str(&format!(" \\f \"%tc{}\"", self.text_case)); } - let sheet = db.find(&self.ss_node).and_then(codec::sheet_set::ComponentKind::of) == Some(codec::sheet_set::ComponentKind::Sheet); - if sheet && self.ss_href { - if let Some(r) = db.layout_reference(&self.ss_node, "Layout") { - code.push_str(&format!(" \\href \"{}#,{}##1\"", r.file_name, r.name)); + // A sheet links to its layout, a view to its named view on the sheet. + if self.ss_href { + if let Some((file, view, layout)) = nav_link(db, &self.ss_node) { + code.push_str(&format!(" \\href \"{file}#{view},{layout}##1\"")); } } code @@ -959,11 +1011,23 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> { props.iter().map(|(l, _, _)| (l.clone(), *l == state.ss_prop, FieldDialogMsg::SsProp(l.clone()))).collect(), 92.0, ); - let sheet = nodes.iter().any(|(_, id, _, sheet)| *sheet && *id == state.ss_node); - let mut href = checkbox(state.ss_href && sheet).label(t!("Associate hyperlink").into_owned()).text_size(12).size(14); - if sheet { + let linkable = db.is_some_and(|d| nav_link(d, &state.ss_node).is_some()) && state.ss_prop != "ViewportScale"; + let mut href = checkbox(state.ss_href && linkable).label(t!("Associate hyperlink").into_owned()).text_size(12).size(14); + if linkable { href = href.on_toggle(|v| msg(FieldDialogMsg::SsHref(v))); } + let format: Element<'a, Message> = if state.ss_prop == "ViewportScale" { + list( + codec::fields::PLOT_SCALE_FORMATS + .iter() + .enumerate() + .map(|(i, (n, _))| (t!(*n).into_owned(), state.ss_scale_format == i, FieldDialogMsg::SsScaleFormat(i))) + .collect(), + 120.0, + ) + } else { + case_list(state) + }; column![ text(t!("Sheet set")).size(12), set_row, @@ -972,7 +1036,7 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> { text(t!("Property")).size(12), prop_list, text(t!("Format")).size(12), - case_list(state), + format, href, ] .spacing(8) diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index e4f0d3170..418e9efd9 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -72,6 +72,8 @@ pub struct SheetSetManager { pub by_category: bool, /// Model Views: location id, folder and its drawings. pub locations: Vec<(String, String, Vec)>, + /// Drawings this application holds `.dwl` / `.dwl2` lock files for. + pub locks: HashSet, } /// A sheet whose drawing is missing, or open (a `.dwl` lock file, or a tab here). @@ -294,8 +296,10 @@ pub struct ImportRow { pub drawing: String, pub layout: String, pub handle: String, - /// The sheet set the layout already belongs to (cannot be imported). - pub owner: Option, + /// Already a sheet of this set: cannot be imported. + pub taken: bool, + /// The drawing's sheet link names another sheet set (importable, warned). + pub warn: bool, pub on: bool, } @@ -1277,11 +1281,20 @@ fn form_view<'a>(f: &'a Form) -> Element<'a, Message> { let mut list = vec![head.into()]; for (k, r) in f.rows.iter().enumerate() { let file = std::path::Path::new(&r.drawing).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default(); - let status: Element<'a, Message> = match &r.owner { - None => text(t!("Available for import").into_owned()).size(12).style(|t: &Theme| text::Style { color: Some(t.palette().success.base.color) }).into(), - Some(owner) => text(crate::tf!("Belongs to sheet set {}", owner).into_owned()).size(12).style(|t: &Theme| text::Style { color: Some(t.palette().warning.base.color) }).into(), + let (label, warn) = if r.taken { + ("This layout is already part of a sheet set - not available for import.", true) + } else if r.warn { + ("Warning: this layout may belong to another sheet set.", true) + } else { + ("Available for import", false) }; - let enabled = r.owner.is_none(); + let status: Element<'a, Message> = text(t!(label).into_owned()) + .size(12) + .style(move |t: &Theme| text::Style { + color: Some(if warn { t.palette().warning.base.color } else { t.palette().success.base.color }), + }) + .into(); + let enabled = !r.taken; let cb = checkbox(r.on).size(14); let cb: Element<'a, Message> = if enabled { cb.on_toggle(move |v| msg(SheetSetMsg::Toggle(FieldId::ImportRow(k), v))).into() } else { cb.into() }; let name = text(r.layout.clone()).size(12).width(Fill);