From b1d4c9bd9b208c91c4180f0f8e16ec3a4e400358 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 13 Oct 2026 15:06:58 +0300 Subject: [PATCH] feat(clipboard): PASTESPEC pastes in a chosen format PASTESPEC offers the formats on the clipboard: Objects and Block for objects copied in the application (the PASTECLIP and PASTEBLOCK paths), and Text for the system clipboard's text, placed as multi-line text in the current text style with its lines as paragraphs. Refs #645 Co-Authored-By: Claude Opus 5.5 --- locales/ar-SA/opencadstudio.ftl | 2 + locales/bg-BG/opencadstudio.ftl | 2 + locales/cs-CZ/opencadstudio.ftl | 2 + locales/de-DE/opencadstudio.ftl | 2 + locales/el-GR/opencadstudio.ftl | 2 + locales/en-US/opencadstudio.ftl | 2 + locales/es-ES/opencadstudio.ftl | 2 + locales/fi-FI/opencadstudio.ftl | 2 + locales/fr-FR/opencadstudio.ftl | 2 + locales/hi-IN/opencadstudio.ftl | 2 + locales/hu-HU/opencadstudio.ftl | 2 + locales/it-IT/opencadstudio.ftl | 2 + locales/ja-JP/opencadstudio.ftl | 2 + locales/ko-KR/opencadstudio.ftl | 2 + locales/lo-LA/opencadstudio.ftl | 2 + locales/nl-NL/opencadstudio.ftl | 2 + locales/pl-PL/opencadstudio.ftl | 2 + locales/pt-BR/opencadstudio.ftl | 2 + locales/ru-RU/opencadstudio.ftl | 2 + locales/tr-TR/opencadstudio.ftl | 2 + locales/zh-CN/opencadstudio.ftl | 2 + locales/zh-TW/opencadstudio.ftl | 2 + src/app/commands/blocks.rs | 30 ++++++ src/app/commands/count.rs | 2 +- src/app/mod.rs | 2 + src/app/update/mod.rs | 26 +++++ src/locale_catalog.rs | 2 + src/modules/draw/clipboard/mod.rs | 1 + src/modules/draw/clipboard/pastespec.rs | 136 ++++++++++++++++++++++++ 29 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 src/modules/draw/clipboard/pastespec.rs diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index f01c22786..506cc6a3a 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -3619,6 +3619,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE حدد نقطة الإدراج: insert = + .pastespec-specify-insertion-point = PASTESPEC حدد نقطة الإدراج: + .pastespec-paste-as = PASTESPEC لصق كـ [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 509be26cc..491097bb2 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -3606,6 +3606,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Посочи точка на вмъкване: insert = + .pastespec-specify-insertion-point = PASTESPEC Задайте точка на вмъкване: + .pastespec-paste-as = PASTESPEC Поставяне като [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 11f3c6a68..678e9179d 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -3604,6 +3604,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Zadejte bod vložení: insert = + .pastespec-specify-insertion-point = PASTESPEC Zadejte bod vložení: + .pastespec-paste-as = PASTESPEC Vložit jako [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index ac0531932..b5d3e3009 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -3607,6 +3607,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Einführstelle angeben: insert = + .pastespec-specify-insertion-point = PASTESPEC Einfügepunkt angeben: + .pastespec-paste-as = PASTESPEC Einfügen als [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 1c2f0d3c2..3cd9057c4 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -3607,6 +3607,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Ορίστε σημείο εισαγωγής: insert = + .pastespec-specify-insertion-point = PASTESPEC Καθορίστε σημείο εισαγωγής: + .pastespec-paste-as = PASTESPEC Επικόλληση ως [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 7129bf9ba..47dd8e2b0 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -3607,6 +3607,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Specify insertion point: insert = + .pastespec-specify-insertion-point = PASTESPEC Specify insertion point: + .pastespec-paste-as = PASTESPEC Paste as [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 558fcf07a..5ee417586 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -3607,6 +3607,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Especifique el punto de inserción: insert = + .pastespec-specify-insertion-point = PASTESPEC Precise el punto de inserción: + .pastespec-paste-as = PASTESPEC Pegar como [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 389c34957..9af55c14a 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -3606,6 +3606,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Määritä lisäyskohta: insert = + .pastespec-specify-insertion-point = PASTESPEC Määritä lisäyspiste: + .pastespec-paste-as = PASTESPEC Liitä muodossa [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 52be9d1a1..08b0e100d 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -3607,6 +3607,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Spécifier le point d'insertion: insert = + .pastespec-specify-insertion-point = PASTESPEC Spécifiez le point d’insertion : + .pastespec-paste-as = PASTESPEC Coller comme [Objects/Block/Text] : .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 1061a7622..c520b2135 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -3595,6 +3595,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE निर्दिष्ट सम्मिलन बिंदु: insert = + .pastespec-specify-insertion-point = PASTESPEC सम्मिलन बिंदु निर्दिष्ट करें: + .pastespec-paste-as = PASTESPEC इस रूप में चिपकाएँ [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 0bce1d572..9b40b57ca 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -3605,6 +3605,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Beszúrási pont megadása: insert = + .pastespec-specify-insertion-point = PASTESPEC Adja meg a beszúrási pontot: + .pastespec-paste-as = PASTESPEC Beillesztés mint [Objects/Block/Text]: .pdfui-pdf-scale = PDF-lépték: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Adja meg a beszúrási pontot: .pdfui-no-pdf-underlays = Nem található PDF-alávetítés. diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index a6e68a91e..b494306f2 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -3606,6 +3606,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Specificare il punto di inserimento: insert = + .pastespec-specify-insertion-point = PASTESPEC Specificare il punto di inserimento: + .pastespec-paste-as = PASTESPEC Incolla come [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 1a58d56ab..88381d6b0 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -3597,6 +3597,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE 挿入点を指定: insert = + .pastespec-specify-insertion-point = PASTESPEC 挿入点を指定: + .pastespec-paste-as = PASTESPEC 貼り付け形式 [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index f154d8638..265f722ce 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -3606,6 +3606,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE 삽입점 지정: insert = + .pastespec-specify-insertion-point = PASTESPEC 삽입점 지정: + .pastespec-paste-as = PASTESPEC 붙여넣기 형식 [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/lo-LA/opencadstudio.ftl b/locales/lo-LA/opencadstudio.ftl index d44fddbab..f419faa2a 100644 --- a/locales/lo-LA/opencadstudio.ftl +++ b/locales/lo-LA/opencadstudio.ftl @@ -3608,6 +3608,8 @@ annotate = insert = + .pastespec-specify-insertion-point = PASTESPEC ກຳນົດຈຸດແຊກ: + .pastespec-paste-as = PASTESPEC ວາງເປັນ [Objects/Block/Text]: .xref-unresolved-external-references = XREF ການອ້າງອີງພາຍນອກທີ່ບໍ່ພົບ: __ocs_fmt_0__ — ລາກໄຟລ໌ DWG/DXF ທີ່ອ້າງອີງມາວາງໃສ່ໜ້າເພື່ອລົງທະບຽນ, ຈາກນັ້ນເປີດແບບແຕ້ມນີ້ຄືນ ຫຼື ເອີ້ນໃຊ້ XRELOAD. .pdfui-pdf-scale = ມາດຕາສ່ວນ PDF: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = ກໍານົດຈຸດແຊກ: diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 2a53a42e9..64b4faa7c 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -3605,6 +3605,8 @@ annotate = .dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Selecteer een boog, cirkel of polylijnboog: insert = + .pastespec-specify-insertion-point = PASTESPEC Geef invoegpunt op: + .pastespec-paste-as = PASTESPEC Plakken als [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 092e157f3..e0c88c3a4 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -3606,6 +3606,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Określ punkt wstawienia: insert = + .pastespec-specify-insertion-point = PASTESPEC Określ punkt wstawienia: + .pastespec-paste-as = PASTESPEC Wklej jako [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index a499e2648..18d9cd8cd 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -3607,6 +3607,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Especifique o ponto de inserção: insert = + .pastespec-specify-insertion-point = PASTESPEC Especifique o ponto de inserção: + .pastespec-paste-as = PASTESPEC Colar como [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 5ab664934..df43b135a 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -3608,6 +3608,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Укажите точку вставки: insert = + .pastespec-specify-insertion-point = PASTESPEC Укажите точку вставки: + .pastespec-paste-as = PASTESPEC Вставить как [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 5e255ced1..83e54cc6a 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -3580,6 +3580,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE Ekleme noktasını belirtin: insert = + .pastespec-specify-insertion-point = PASTESPEC Ekleme noktasını belirtin: + .pastespec-paste-as = PASTESPEC Şu biçimde yapıştır [Objects/Block/Text]: .pdfui-pdf-scale = PDF ölçeği: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Ekleme noktasını belirtin: .pdfui-no-pdf-underlays = PDF altlığı bulunamadı. diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 2b85b94b0..373580f22 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -3594,6 +3594,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE 指定插入点: insert = + .pastespec-specify-insertion-point = PASTESPEC 指定插入点: + .pastespec-paste-as = PASTESPEC 粘贴为 [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 99ee6edbb..fe9ae1a53 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -3606,6 +3606,8 @@ annotate = .tolerance-specify-insertion-point = TOLERANCE 指定插入點: insert = + .pastespec-specify-insertion-point = PASTESPEC 指定插入點: + .pastespec-paste-as = PASTESPEC 貼上為 [Objects/Block/Text]: .pdfui-pdf-scale = PDF scale: __ocs_fmt_0__:1 .pdfui-specify-insertion-point = Specify insertion point: .pdfui-no-pdf-underlays = No PDF underlays found. diff --git a/src/app/commands/blocks.rs b/src/app/commands/blocks.rs index d6b680840..b9a74490b 100644 --- a/src/app/commands/blocks.rs +++ b/src/app/commands/blocks.rs @@ -95,6 +95,23 @@ impl OpenCADStudio { } } + /// PASTESPEC with the formats on hand: the copied objects, and `text` + /// from the system clipboard once it has been read. + pub(in crate::app) fn paste_special_command( + &self, + i: usize, + text: Option<&str>, + ) -> Box { + let document = &self.tabs[i].scene.document; + let defaults = crate::scene::creation_style::current_text_defaults(document); + Box::new(crate::modules::draw::clipboard::pastespec::PasteSpecCommand::new( + !self.clipboard.is_empty(), + text, + defaults.style_name, + defaults.height, + )) + } + pub(super) fn dispatch_blocks(&mut self, cmd: &str, i: usize) -> Option> { match cmd { // ── BASE — drawing insertion base point ─────────────────────── @@ -284,6 +301,19 @@ impl OpenCADStudio { } } + // PASTESPEC — offer the copied objects now; the system clipboard's + // text joins the formats once read (Message::PasteSpecial). + "PASTESPEC" => { + let command = self.paste_special_command(i, None); + let started = self.start_count_command(i, command); + #[cfg(target_arch = "wasm32")] + let read = Task::perform(crate::sys::read_clipboard_text(), Message::PasteSpecial); + #[cfg(not(target_arch = "wasm32"))] + let read = iced::clipboard::read_text() + .map(|result| Message::PasteSpecial(result.ok().map(|text| (*text).clone()))); + return Some(Task::batch([started, read])); + } + // PASTEORIG — paste at the entities' original coordinates (no pick). "PASTEORIG" => { if self.clipboard.is_empty() { diff --git a/src/app/commands/count.rs b/src/app/commands/count.rs index 88674128c..52ba30aff 100644 --- a/src/app/commands/count.rs +++ b/src/app/commands/count.rs @@ -188,7 +188,7 @@ impl OpenCADStudio { } } - fn start_count_command(&mut self, i: usize, command: Box) -> Task { + pub(in crate::app) fn start_count_command(&mut self, i: usize, command: Box) -> Task { self.reset_command_start_state(i); self.command_line.push_info(&command.prompt()); self.tabs[i].active_cmd = Some(command); diff --git a/src/app/mod.rs b/src/app/mod.rs index b40c2fa1e..df546b25a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2360,6 +2360,8 @@ pub enum Message { PasteShortcut, /// Which text input held focus when `PasteShortcut` arrived. PasteShortcutResolved(PasteFocus), + /// Completion of the system text clipboard read PASTESPEC starts. + PasteSpecial(Option), /// Completion of a system text clipboard read requested by PASTECLIP. SystemClipboardPaste(SystemClipboardText), /// Web: completion of the clipboard-image read the paste fallback starts diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 33bc51dac..c6e57431a 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -6815,6 +6815,32 @@ impl OpenCADStudio { } } + Message::PasteSpecial(text) => { + let i = self.active_tab; + let waiting = self.tabs[i] + .active_cmd + .as_ref() + .is_some_and(|command| command.name() == "PASTESPEC"); + if !waiting { + return Task::none(); + } + match text.filter(|text| !text.trim().is_empty()) { + Some(text) => { + // Same prompt, one more option: swap without echoing it. + self.tabs[i].active_cmd = Some(self.paste_special_command(i, Some(&text))); + self.push_ucs_to_cmd(i); + Task::none() + } + None if self.clipboard.is_empty() => { + self.tabs[i].active_cmd = None; + self.command_line + .push_error(crate::tr!("clipboard", "no-supported-content").as_ref()); + Task::none() + } + None => Task::none(), + } + } + Message::SystemClipboardPaste(result) => { use super::SystemClipboardText as Text; diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index a9010ebe6..62719b52e 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -2380,6 +2380,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "PAPER" => Some(("ui", "paper")), "PAPER: double-click viewport to enter MSPACE\nMODEL: click to switch to Model Space" => Some(("ui", "paper-double-click-viewport-to-enter-mspace-mo")), "PASTEBLOCK: clipboard is empty." => Some(("insert", "pasteblock-clipboard-is-empty")), + "PASTESPEC Paste as [Objects/Block/Text]:" => Some(("insert", "pastespec-paste-as")), + "PASTESPEC Specify insertion point:" => Some(("insert", "pastespec-specify-insertion-point")), "PASTEBLOCK: {e}" => Some(("insert", "pasteblock-e")), "PASTECLIP Pick insertion point:" => Some(("clipboard", "pasteclip-pick-insertion-point")), "PASTEORIG: clipboard is empty." => Some(("insert", "pasteorig-clipboard-is-empty")), diff --git a/src/modules/draw/clipboard/mod.rs b/src/modules/draw/clipboard/mod.rs index 0b72b361d..5122d99c2 100644 --- a/src/modules/draw/clipboard/mod.rs +++ b/src/modules/draw/clipboard/mod.rs @@ -2,3 +2,4 @@ pub mod copy_base; pub mod copy_clip; pub mod cut; pub mod paste; +pub mod pastespec; diff --git a/src/modules/draw/clipboard/pastespec.rs b/src/modules/draw/clipboard/pastespec.rs new file mode 100644 index 000000000..c23cbd38b --- /dev/null +++ b/src/modules/draw/clipboard/pastespec.rs @@ -0,0 +1,136 @@ +// PASTESPEC — paste the clipboard in a chosen format: the copied objects, the +// copied objects as a block, or the system clipboard's text as multi-line text. + +use crate::t; +use codec::entities::mtext::AttachmentPoint; +use codec::entities::MText; +use codec::types::Vector3; +use codec::EntityType; +use glam::DVec3; + +use crate::command::{CadCommand, CmdOption, CmdResult, WorkingPlane}; + +pub struct PasteSpecCommand { + /// Objects copied inside the application are waiting on the clipboard. + objects: bool, + /// The system clipboard's text, already escaped for MTEXT. + text: Option, + style: String, + height: f64, + placing_text: bool, + plane: WorkingPlane, +} + +impl PasteSpecCommand { + pub fn new(objects: bool, text: Option<&str>, style: String, height: f64) -> Self { + Self { + objects, + text: text.map(mtext_value), + style, + height, + placing_text: false, + plane: WorkingPlane::default(), + } + } +} + +/// Plain text as an MTEXT value: control characters escaped, lines as +/// paragraphs. +fn mtext_value(text: &str) -> String { + text.trim_end_matches(['\r', '\n']) + .replace('\\', "\\\\") + .replace('{', "\\{") + .replace('}', "\\}") + .replace("\r\n", "\\P") + .replace(['\r', '\n'], "\\P") +} + +impl CadCommand for PasteSpecCommand { + fn name(&self) -> &'static str { + "PASTESPEC" + } + + fn set_working_plane(&mut self, plane: WorkingPlane) { + self.plane = plane; + } + + fn prompt(&self) -> String { + if self.placing_text { + t!("PASTESPEC Specify insertion point:").into_owned() + } else { + t!("PASTESPEC Paste as [Objects/Block/Text]:").into_owned() + } + } + + fn options(&self) -> Vec { + if self.placing_text { + return Vec::new(); + } + let mut options = Vec::new(); + if self.objects { + options.push(CmdOption::new("Objects", "O")); + options.push(CmdOption::new("Block", "B")); + } + if self.text.is_some() { + options.push(CmdOption::new("Text", "T")); + } + options + } + + fn wants_text_input(&self) -> bool { + !self.placing_text + } + + fn on_text_input(&mut self, text: &str) -> Option { + if self.placing_text { + return None; + } + match text.trim().trim_start_matches('_').to_ascii_uppercase().as_str() { + "O" | "OBJECTS" if self.objects => Some(CmdResult::Dispatch("PASTECLIP".into())), + "B" | "BLOCK" if self.objects => Some(CmdResult::Dispatch("PASTEBLOCK".into())), + "T" | "TEXT" if self.text.is_some() => { + self.placing_text = true; + Some(CmdResult::NeedPoint) + } + _ => None, + } + } + + fn on_point(&mut self, pt: DVec3) -> CmdResult { + let Some(value) = self.text.clone().filter(|_| self.placing_text) else { + return CmdResult::NeedPoint; + }; + let p = self.plane.to_local(pt); + let mut mtext = MText::new(); + mtext.value = value; + mtext.insertion_point = Vector3::new(p.x, p.y, p.z); + mtext.height = self.height; + mtext.style = self.style.clone(); + mtext.attachment_point = AttachmentPoint::TopLeft; + mtext.rectangle_width = 0.0; + CmdResult::CommitAndExit(self.plane.place_entity(EntityType::MText(mtext))) + } + + fn on_enter(&mut self) -> CmdResult { + if self.placing_text { + return CmdResult::NeedPoint; + } + // Enter takes the first format offered. + let first = self.options().first().map(|o| o.keyword.clone()); + first + .and_then(|keyword| self.on_text_input(&keyword)) + .unwrap_or(CmdResult::Cancel) + } +} + +inventory::submit!(crate::command::CommandRegistration { names: &["PASTESPEC"] }); + +#[cfg(test)] +mod tests { + use super::mtext_value; + + #[test] + fn plain_text_becomes_escaped_paragraphs() { + assert_eq!(mtext_value("a{b}\\c\r\nline 2\n"), "a\\{b\\}\\\\c\\Pline 2"); + } +}