diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 5fa504efe..79697d790 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -5111,6 +5111,15 @@ plot = .system-default-printer = طابعة النظام الافتراضية .what-to-plot = ما المراد طباعته .x-mm = X (مم) + .select-drawing-to-import-page-setups-from = اختر الرسم لاستيراد إعدادات الصفحة منه + .drawings-and-templates = الرسومات والقوالب + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: تم استيراد __ocs_fmt_0__ من إعدادات الصفحة، واستبدال __ocs_fmt_1__. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: لا يوجد إعداد صفحة باسم "__ocs_fmt_0__" في __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: لا يوجد ما يمكن استيراده من __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = لا توجد إعدادات صفحة مسماة في هذا الرسم. + .import-ellipsis = استيراد… + .show-the-page-setup-for-new-layouts = إظهار إعداد الصفحة للتخطيطات الجديدة .x-in = X (بوصة) .y-in = Y (بوصة) .hide-paperspace-objects = إخفاء كائنات مساحة الورق diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 8c78a7bbc..1c14e6ee3 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -5099,6 +5099,15 @@ plot = .system-default-printer = Системен принтер по подразбиране .what-to-plot = Какво да се плотира .x-mm = X (мм) + .select-drawing-to-import-page-setups-from = Изберете чертеж, от който да се импортират настройките на страницата + .drawings-and-templates = Чертежи и шаблони + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: импортирани __ocs_fmt_0__ настройки на страницата, __ocs_fmt_1__ заменени. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: няма настройка на страницата с име „__ocs_fmt_0__“ в __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: няма нищо за импортиране от __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = В този чертеж няма именувани настройки на страницата. + .import-ellipsis = Импортиране… + .show-the-page-setup-for-new-layouts = Показване на настройката на страницата за нови оформления .x-in = X (инча) .y-in = Y (инча) .hide-paperspace-objects = Скриване на обектите в пространството на листа diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 6c5e009c3..712cceb2c 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -5096,6 +5096,15 @@ plot = .system-default-printer = Výchozí tiskárna systému .what-to-plot = Co fabulovat .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Vyberte výkres pro import nastavení stránky + .drawings-and-templates = Výkresy a šablony + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: importováno __ocs_fmt_0__ nastavení stránky, __ocs_fmt_1__ nahrazeno. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: žádné nastavení stránky s názvem „__ocs_fmt_0__“ v __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: z __ocs_fmt_0__ není co importovat. + .no-named-page-setups-in-this-drawing = V tomto výkresu nejsou pojmenovaná nastavení stránky. + .import-ellipsis = Importovat… + .show-the-page-setup-for-new-layouts = Zobrazit nastavení stránky pro nová rozvržení .x-in = X (palce) .y-in = Y (palce) .hide-paperspace-objects = Skrýt objekty výkresového prostoru diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index c07116de6..5791cd6a8 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -5097,6 +5097,15 @@ plot = .system-default-printer = Systemstandarddrucker .what-to-plot = Was zu plotten .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Zeichnung für den Seiteneinrichtungs-Import wählen + .drawings-and-templates = Zeichnungen und Vorlagen + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ Seiteneinrichtung(en) importiert, __ocs_fmt_1__ ersetzt. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: keine Seiteneinrichtung namens „__ocs_fmt_0__“ in __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: nichts zu importieren aus __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Keine benannten Seiteneinrichtungen in dieser Zeichnung. + .import-ellipsis = Importieren… + .show-the-page-setup-for-new-layouts = Seiteneinrichtung für neue Layouts anzeigen .x-in = X (Zoll) .y-in = Y (Zoll) .hide-paperspace-objects = Papierbereichsobjekte ausblenden diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 90032daae..51e9d1a85 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -5099,6 +5099,15 @@ plot = .system-default-printer = Προεπιλεγμένος εκτυπωτής συστήματος .what-to-plot = Τι θα εκτυπωθεί .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Επιλογή σχεδίου για εισαγωγή page setups + .drawings-and-templates = Σχέδια και πρότυπα + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: εισήχθησαν __ocs_fmt_0__ page setup(s), __ocs_fmt_1__ αντικαταστάθηκαν. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: δεν υπάρχει page setup με όνομα «__ocs_fmt_0__» στο __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: τίποτα προς εισαγωγή από το __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Δεν υπάρχουν επώνυμα page setups σε αυτό το σχέδιο. + .import-ellipsis = Εισαγωγή… + .show-the-page-setup-for-new-layouts = Εμφάνιση του page setup για νέα layouts .x-in = X (in) .y-in = Y (in) .hide-paperspace-objects = Απόκρυψη αντικειμένων χώρου χαρτιού diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index c5c1d58c4..b4477d804 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -5100,6 +5100,15 @@ plot = .system-default-printer = System default printer .what-to-plot = What to plot .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Select Drawing to Import Page Setups From + .drawings-and-templates = Drawings and templates + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ page setup(s) imported, __ocs_fmt_1__ replaced. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: no page setup named "__ocs_fmt_0__" in __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: nothing to import from __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = No named page setups in this drawing. + .import-ellipsis = Import… + .show-the-page-setup-for-new-layouts = Show the page setup for new layouts .x-in = X (in) .y-in = Y (in) .hide-paperspace-objects = Hide paperspace objects diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 246f06cb7..2e7aa0db0 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -5099,6 +5099,15 @@ plot = .system-default-printer = Impresora predeterminada del sistema .what-to-plot = Qué trazar .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Seleccionar dibujo del que importar configuraciones de página + .drawings-and-templates = Dibujos y plantillas + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ configuración(es) de página importada(s), __ocs_fmt_1__ reemplazada(s). + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: no hay ninguna configuración de página llamada "__ocs_fmt_0__" en __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: nada que importar de __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = No hay configuraciones de página con nombre en este dibujo. + .import-ellipsis = Importar… + .show-the-page-setup-for-new-layouts = Mostrar la configuración de página para las presentaciones nuevas .x-in = X (pulg) .y-in = Y (pulg) .hide-paperspace-objects = Ocultar objetos del espacio papel diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 1d3ca926c..6a19ddd6e 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -5098,6 +5098,15 @@ plot = .system-default-printer = Järjestelmän oletustulostin .what-to-plot = Mitä piirtää .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Valitse piirustus, josta sivuasetukset tuodaan + .drawings-and-templates = Piirustukset ja mallipohjat + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ sivuasetusta tuotu, __ocs_fmt_1__ korvattu. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: sivuasetusta "__ocs_fmt_0__" ei ole tiedostossa __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: ei mitään tuotavaa tiedostosta __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Tässä piirustuksessa ei ole nimettyjä sivuasetuksia. + .import-ellipsis = Tuo… + .show-the-page-setup-for-new-layouts = Näytä sivuasetus uusille asetteluille .x-in = X (tuumaa) .y-in = Y (tuumaa) .hide-paperspace-objects = Piilota paperitilan objektit diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 9e795fb27..ec27a6a34 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -5099,6 +5099,15 @@ plot = .system-default-printer = Imprimante système par défaut .what-to-plot = Quoi faire? .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Sélectionner le dessin d'où importer les mises en page + .drawings-and-templates = Dessins et gabarits + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN : __ocs_fmt_0__ mise(s) en page importée(s), __ocs_fmt_1__ remplacée(s). + .psetupin-error = PSETUPIN : __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN : aucune mise en page nommée « __ocs_fmt_0__ » dans __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN : rien à importer depuis __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Aucune mise en page nommée dans ce dessin. + .import-ellipsis = Importer… + .show-the-page-setup-for-new-layouts = Afficher la mise en page pour les nouvelles présentations .x-in = X (po) .y-in = Y (po) .hide-paperspace-objects = Masquer les objets de l'espace papier diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 77b39e5af..a97d46f8d 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -5087,6 +5087,15 @@ plot = .system-default-printer = सिस्टम डिफ़ॉल्ट प्रिंटर .what-to-plot = क्या प्लॉट करना है .x-mm = एक्स (मिमी) + .select-drawing-to-import-page-setups-from = पेज सेटअप आयात करने के लिए ड्रॉइंग चुनें + .drawings-and-templates = ड्रॉइंग और टेम्पलेट + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ पेज सेटअप आयात किए गए, __ocs_fmt_1__ बदले गए। + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: __ocs_fmt_1__ में "__ocs_fmt_0__" नाम का कोई पेज सेटअप नहीं है। + .psetupin-nothing-to-import-from-file = PSETUPIN: __ocs_fmt_0__ से आयात करने के लिए कुछ नहीं है। + .no-named-page-setups-in-this-drawing = इस ड्रॉइंग में कोई नामित पेज सेटअप नहीं है। + .import-ellipsis = आयात करें… + .show-the-page-setup-for-new-layouts = नए लेआउट के लिए पेज सेटअप दिखाएँ .x-in = एक्स (इंच) .y-in = वाई (इंच) .hide-paperspace-objects = पेपर स्पेस ऑब्जेक्ट छिपाएँ diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 7ede49018..696441942 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -5098,6 +5098,15 @@ plot = .system-default-printer = Rendszer alapértelmezett nyomtató .what-to-plot = Mit kell kitalálni .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Válassza ki a rajzot, amelyből oldalbeállításokat importál + .drawings-and-templates = Rajzok és sablonok + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ oldalbeállítás importálva, __ocs_fmt_1__ lecserélve. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: nincs „__ocs_fmt_0__” nevű oldalbeállítás a(z) __ocs_fmt_1__ fájlban. + .psetupin-nothing-to-import-from-file = PSETUPIN: nincs mit importálni innen: __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Ebben a rajzban nincsenek névvel ellátott oldalbeállítások. + .import-ellipsis = Importálás… + .show-the-page-setup-for-new-layouts = Oldalbeállítás megjelenítése új elrendezésekhez .x-in = X (hüvelyk) .y-in = Y (hüvelyk) .hide-paperspace-objects = Papírtér-objektumok elrejtése diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 31a017143..c83d79502 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -5098,6 +5098,15 @@ plot = .system-default-printer = Stampante predefinita del sistema .what-to-plot = Cosa tracciare .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Seleziona il disegno da cui importare le impostazioni di pagina + .drawings-and-templates = Disegni e modelli + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ impostazione/i di pagina importata/e, __ocs_fmt_1__ sostituita/e. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: nessuna impostazione di pagina chiamata "__ocs_fmt_0__" in __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: niente da importare da __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Nessuna impostazione di pagina con nome in questo disegno. + .import-ellipsis = Importa… + .show-the-page-setup-for-new-layouts = Mostra l'impostazione di pagina per i nuovi layout .x-in = X (poll.) .y-in = Y (poll.) .hide-paperspace-objects = Nascondi oggetti dello spazio carta diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 54afd73ea..de80c1798 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -5087,6 +5087,15 @@ plot = .system-default-printer = システムの既定プリンター .what-to-plot = 印刷対象 .x-mm = X(mm) + .select-drawing-to-import-page-setups-from = ページ設定を読み込む図面を選択 + .drawings-and-templates = 図面とテンプレート + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ 件のページ設定を読み込み、__ocs_fmt_1__ 件を置き換えました。 + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: __ocs_fmt_1__ に「__ocs_fmt_0__」というページ設定はありません。 + .psetupin-nothing-to-import-from-file = PSETUPIN: __ocs_fmt_0__ に読み込むものがありません。 + .no-named-page-setups-in-this-drawing = この図面には名前付きページ設定がありません。 + .import-ellipsis = 読み込み… + .show-the-page-setup-for-new-layouts = 新しいレイアウトでページ設定を表示 .x-in = X(インチ) .y-in = Y(インチ) .hide-paperspace-objects = ペーパー空間のオブジェクトを隠す diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index d0898e607..535d32d18 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -5098,6 +5098,15 @@ plot = .system-default-printer = 시스템 기본 프린터 .what-to-plot = 무엇을 플롯할 것인가 .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = 페이지 설정을 가져올 도면 선택 + .drawings-and-templates = 도면 및 템플릿 + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: 페이지 설정 __ocs_fmt_0__개를 가져왔고 __ocs_fmt_1__개를 교체했습니다. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: __ocs_fmt_1__에 "__ocs_fmt_0__" 페이지 설정이 없습니다. + .psetupin-nothing-to-import-from-file = PSETUPIN: __ocs_fmt_0__에서 가져올 항목이 없습니다. + .no-named-page-setups-in-this-drawing = 이 도면에는 명명된 페이지 설정이 없습니다. + .import-ellipsis = 가져오기… + .show-the-page-setup-for-new-layouts = 새 배치에 페이지 설정 표시 .x-in = X (인치) .y-in = Y (인치) .hide-paperspace-objects = 도면 공간 객체 숨기기 diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 061ec2b8e..ccd9e73cd 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -5097,6 +5097,15 @@ plot = .system-default-printer = Standaardprinter van het systeem .what-to-plot = Wat te plannen .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Tekening kiezen om pagina-instellingen uit te importeren + .drawings-and-templates = Tekeningen en sjablonen + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ pagina-instelling(en) geïmporteerd, __ocs_fmt_1__ vervangen. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: geen pagina-instelling met de naam "__ocs_fmt_0__" in __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: niets te importeren uit __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Geen benoemde pagina-instellingen in deze tekening. + .import-ellipsis = Importeren… + .show-the-page-setup-for-new-layouts = Pagina-instelling tonen voor nieuwe lay-outs .x-in = X (inch) .y-in = Y (inch) .hide-paperspace-objects = Papierruimte-objecten verbergen diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 96b780030..8ae2d486b 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -5098,6 +5098,15 @@ plot = .system-default-printer = Domyślna drukarka systemowa .what-to-plot = Co nakreślić .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Wybierz rysunek, z którego zaimportować ustawienia strony + .drawings-and-templates = Rysunki i szablony + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: zaimportowano __ocs_fmt_0__ ustawień strony, zastąpiono __ocs_fmt_1__. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: brak ustawienia strony o nazwie „__ocs_fmt_0__” w __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: nie ma nic do zaimportowania z __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Brak nazwanych ustawień strony w tym rysunku. + .import-ellipsis = Importuj… + .show-the-page-setup-for-new-layouts = Pokaż ustawienia strony dla nowych arkuszy .x-in = X (cale) .y-in = Y (cale) .hide-paperspace-objects = Ukryj obiekty przestrzeni papieru diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 66fe92f30..d202e49b5 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -5099,6 +5099,15 @@ plot = .system-default-printer = Impressora padrão do sistema .what-to-plot = O que plotar .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Selecionar desenho de onde importar configurações de página + .drawings-and-templates = Desenhos e modelos + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ configuração(ões) de página importada(s), __ocs_fmt_1__ substituída(s). + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: nenhuma configuração de página chamada "__ocs_fmt_0__" em __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: nada para importar de __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = Nenhuma configuração de página nomeada neste desenho. + .import-ellipsis = Importar… + .show-the-page-setup-for-new-layouts = Mostrar a configuração de página para novos layouts .x-in = X (pol) .y-in = Y (pol) .hide-paperspace-objects = Ocultar objetos do espaço do papel diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 78511f428..643de32c6 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -5100,6 +5100,15 @@ plot = .system-default-printer = Системный принтер по умолчанию .what-to-plot = Что спланировать .x-mm = X (мм) + .select-drawing-to-import-page-setups-from = Выберите чертёж для импорта наборов параметров листа + .drawings-and-templates = Чертежи и шаблоны + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: импортировано наборов: __ocs_fmt_0__, заменено: __ocs_fmt_1__. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: набор параметров листа «__ocs_fmt_0__» не найден в __ocs_fmt_1__. + .psetupin-nothing-to-import-from-file = PSETUPIN: нечего импортировать из __ocs_fmt_0__. + .no-named-page-setups-in-this-drawing = В этом чертеже нет именованных наборов параметров листа. + .import-ellipsis = Импорт… + .show-the-page-setup-for-new-layouts = Показывать параметры листа для новых листов .x-in = X (дюймы) .y-in = Y (дюймы) .hide-paperspace-objects = Скрыть объекты пространства листа diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 528ca5a90..a85084753 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -5039,6 +5039,15 @@ plot = .system-default-printer = Varsayılan sistem yazıcısı .what-to-plot = Yazdırılacak Alan .x-mm = X (mm) + .select-drawing-to-import-page-setups-from = Sayfa ayarlarının içe aktarılacağı çizimi seçin + .drawings-and-templates = Çizimler ve şablonlar + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN: __ocs_fmt_0__ sayfa ayarı içe aktarıldı, __ocs_fmt_1__ değiştirildi. + .psetupin-error = PSETUPIN: __ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN: __ocs_fmt_1__ içinde "__ocs_fmt_0__" adlı sayfa ayarı yok. + .psetupin-nothing-to-import-from-file = PSETUPIN: __ocs_fmt_0__ içinden içe aktarılacak bir şey yok. + .no-named-page-setups-in-this-drawing = Bu çizimde adlandırılmış sayfa ayarı yok. + .import-ellipsis = İçe aktar… + .show-the-page-setup-for-new-layouts = Yeni yerleşimler için sayfa ayarını göster .x-in = X (inç) .y-in = Y (inç) .hide-paperspace-objects = Kağıt alanı nesnelerini gizle diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 74279af13..25122d429 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -5082,6 +5082,15 @@ plot = .system-default-printer = 系统默认打印机 .what-to-plot = 打印内容 .x-mm = X (毫米) + .select-drawing-to-import-page-setups-from = 选择要从中输入页面设置的图形 + .drawings-and-templates = 图形和样板 + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN:已输入 __ocs_fmt_0__ 个页面设置,替换 __ocs_fmt_1__ 个。 + .psetupin-error = PSETUPIN:__ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN:__ocs_fmt_1__ 中没有名为“__ocs_fmt_0__”的页面设置。 + .psetupin-nothing-to-import-from-file = PSETUPIN:__ocs_fmt_0__ 中没有可输入的内容。 + .no-named-page-setups-in-this-drawing = 此图形中没有命名页面设置。 + .import-ellipsis = 输入… + .show-the-page-setup-for-new-layouts = 为新布局显示页面设置 .x-in = X(英寸) .y-in = Y(英寸) .hide-paperspace-objects = 隐藏图纸空间对象 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 26b920b96..268c0146e 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -5094,6 +5094,15 @@ plot = .system-default-printer = 系統預設印表機 .what-to-plot = 列印內容 .x-mm = X (毫米) + .select-drawing-to-import-page-setups-from = 選擇要從中匯入頁面設定的圖面 + .drawings-and-templates = 圖面與樣板 + .psetupin-count-page-setup-s-imported-replaced-replaced = PSETUPIN:已匯入 __ocs_fmt_0__ 個頁面設定,取代 __ocs_fmt_1__ 個。 + .psetupin-error = PSETUPIN:__ocs_fmt_0__ + .psetupin-no-page-setup-named-want-in-file = PSETUPIN:__ocs_fmt_1__ 中沒有名為「__ocs_fmt_0__」的頁面設定。 + .psetupin-nothing-to-import-from-file = PSETUPIN:__ocs_fmt_0__ 中沒有可匯入的內容。 + .no-named-page-setups-in-this-drawing = 此圖面中沒有具名的頁面設定。 + .import-ellipsis = 匯入… + .show-the-page-setup-for-new-layouts = 為新配置顯示頁面設定 .x-in = X(英吋) .y-in = Y(英吋) .hide-paperspace-objects = 隱藏圖紙空間物件 diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index 8785f6a18..9661a601a 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -567,6 +567,8 @@ inventory::submit!(crate::command::CommandRegistration { "BACKGROUNDPLOT", "CTAB", "TILEMODE", + "PSETUPIN", + "-PSETUPIN", "DISPSILH", "WORLDVIEW", "LIMCHECK", diff --git a/src/app/commands/plotvars.rs b/src/app/commands/plotvars.rs index a1ac76eb0..736d2b66d 100644 --- a/src/app/commands/plotvars.rs +++ b/src/app/commands/plotvars.rs @@ -1,9 +1,11 @@ -//! Plot system variables. `PLOTOFFSET`, `PAPERUPDATE`, `PLOTROTMODE`, -//! `PLOTTRANSPARENCYOVERRIDE` and `BACKGROUNDPLOT` are plot preferences of -//! the application, kept with the Plot dialog's persisted settings; `CTAB` -//! and `TILEMODE` say which layout of the drawing is current. Each is -//! typeable on its own (`PLOTOFFSET 1`, `CTAB Layout1`) and through -//! `SETVAR`, and a bare name prompts for the value like the other variables. +//! Plot system variables and the page-setup import command. `PLOTOFFSET`, +//! `PAPERUPDATE`, `PLOTROTMODE`, `PLOTTRANSPARENCYOVERRIDE` and +//! `BACKGROUNDPLOT` are plot preferences of the application, kept with the +//! Plot dialog's persisted settings; `CTAB` and `TILEMODE` say which layout +//! of the drawing is current. Each is typeable on its own (`PLOTOFFSET 1`, +//! `CTAB Layout1`) and through `SETVAR`, and a bare name prompts for the +//! value like the other variables. `PSETUPIN` brings named page setups in +//! from another drawing (see `app::update::page_setup_import`). use crate::app::{Message, OpenCADStudio}; use iced::Task; @@ -39,6 +41,16 @@ enum Outcome { impl OpenCADStudio { pub(super) fn dispatch_plotvars(&mut self, cmd: &str, i: usize) -> Option> { + // PSETUPIN [file [* | name,name…]] — the dash form is the same + // command; both read the file and report on the command line. + for name in ["PSETUPIN", "-PSETUPIN"] { + if cmd == name { + return Some(self.on_psetupin("")); + } + if let Some(args) = cmd.strip_prefix(name).filter(|rest| rest.starts_with(' ')) { + return Some(self.on_psetupin(args)); + } + } let rest = cmd.strip_prefix("SETVAR ").map(str::trim).unwrap_or(cmd); let mut parts = rest.splitn(2, char::is_whitespace); let name = parts.next().unwrap_or("").to_ascii_uppercase(); diff --git a/src/app/mod.rs b/src/app/mod.rs index c57ff38fd..a6f7af569 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2196,6 +2196,10 @@ pub enum Message { SaveTimeChanged(i32), /// Toggle keeping a `.bak` copy when overwriting a drawing (ISAVEBAK). BackupOnSaveChanged(bool), + /// A drawing was picked to import page setups from (`PSETUPIN`). + PageSetupImportFile(std::path::PathBuf), + /// Options: open the Plot / Page Setup dialog for every new layout. + PageSetupOnNewLayoutChanged(bool), /// Toggle filled TrueType glyphs (TEXTFILL). TextFillChanged(bool), /// Change how many prompt lines sit above the command window (CLIPROMPTLINES). diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 8ab422c86..d8b032fdb 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -4443,6 +4443,7 @@ impl OpenCADStudio { Task::none() } M::PrinterProperties => self.on_printer_properties(), + M::Import(msg) => self.on_page_setup_import(msg), M::PrinterOptionsLoaded(printer, result) => { if let Some(draft) = self.plot_dialog.printer_editor.as_mut() { if draft.printer == printer { @@ -4836,7 +4837,7 @@ impl OpenCADStudio { } } - fn refresh_page_setups(&mut self) { + pub(super) fn refresh_page_setups(&mut self) { use crate::ui::window::plot::{SETUP_NONE, SETUP_PREV}; let scene = &self.tabs[self.active_tab].scene; // / , then layouts (`*name*`), then named setups. @@ -4849,7 +4850,7 @@ impl OpenCADStudio { /// Apply a page-setup list selection to the editor. Handles the pseudo /// entries (`` / ``), layout rows (`*name*`) and named /// setups. - fn select_page_setup(&mut self, name: &str) { + pub(super) fn select_page_setup(&mut self, name: &str) { use crate::ui::window::plot::{SETUP_NONE, SETUP_PREV}; self.plot_dialog.selected_setup = name.to_string(); if name == SETUP_NONE { diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 347482d1c..ad28336fb 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -94,6 +94,7 @@ mod context_menu; mod dialog; mod dynamic; mod file; +mod page_setup_import; mod style; mod util; mod viewport; @@ -7378,6 +7379,12 @@ impl OpenCADStudio { Task::none() } + Message::PageSetupImportFile(path) => self.on_page_setup_import_file(path), + Message::PageSetupOnNewLayoutChanged(enabled) => { + self.plot_dialog.page_setup_on_new_layout = enabled; + self.persist_settings_if_changed(); + Task::none() + } Message::BackupOnSaveChanged(enabled) => { self.backup_on_save = enabled; self.persist_settings_if_changed(); diff --git a/src/app/update/page_setup_import.rs b/src/app/update/page_setup_import.rs new file mode 100644 index 000000000..4894e6b95 --- /dev/null +++ b/src/app/update/page_setup_import.rs @@ -0,0 +1,347 @@ +//! Importing named page setups from another drawing (`PSETUPIN`): the +//! "Import…" chooser of the Plot dialog and the command that drives the same +//! path from the command line. + +use crate::app::{Message, OpenCADStudio}; +use crate::ui::window::plot::{PageSetupImportDraft, PageSetupImportMsg, PlotDlgMsg}; +use iced::Task; +use std::path::{Path, PathBuf}; + +/// Read the named page setups of the drawing at `path` off the UI thread. +/// The result names the file the way the chooser shows it. +fn read_page_setups_task(path: PathBuf) -> Task { + super::file::background_task( + move || { + let file = path + .file_name() + .map(|name| name.to_string_lossy().into_owned()) + .unwrap_or_else(|| path.display().to_string()); + crate::io::load_file(&path) + .map(|document| (file, crate::scene::document_page_setups(&document))) + }, + |result| Message::PlotDlg(PlotDlgMsg::Import(PageSetupImportMsg::Loaded(result))), + ) +} + +impl OpenCADStudio { + pub(super) fn on_page_setup_import(&mut self, msg: PageSetupImportMsg) -> Task { + match msg { + PageSetupImportMsg::Pick => Task::perform( + async { + let handle = rfd::AsyncFileDialog::new() + .set_title(crate::t!("Select Drawing to Import Page Setups From").as_ref()) + .add_filter( + crate::t!("Drawings and templates").as_ref(), + &["dwg", "dxf", "dwt", "DWG", "DXF", "DWT"], + ) + .pick_file() + .await; + handle.map(|h| crate::sys::handle_path(&h)) + }, + |picked| match picked { + Some(path) => Message::PageSetupImportFile(path), + None => Message::Noop, + }, + ), + PageSetupImportMsg::Loaded(result) => { + self.plot_dialog.import_draft = Some(match result { + Ok((file, setups)) => PageSetupImportDraft { + file, + setups: setups.into_iter().map(|(name, ps)| (name, ps, true)).collect(), + error: None, + }, + Err(error) => PageSetupImportDraft { + file: String::new(), + setups: Vec::new(), + error: Some(error), + }, + }); + Task::none() + } + PageSetupImportMsg::Toggle(name) => { + if let Some(draft) = self.plot_dialog.import_draft.as_mut() { + if let Some(entry) = draft.setups.iter_mut().find(|(n, _, _)| *n == name) { + entry.2 = !entry.2; + } + } + Task::none() + } + PageSetupImportMsg::All(on) => { + if let Some(draft) = self.plot_dialog.import_draft.as_mut() { + for entry in &mut draft.setups { + entry.2 = on; + } + } + Task::none() + } + PageSetupImportMsg::Apply => { + let Some(draft) = self.plot_dialog.import_draft.take() else { + return Task::none(); + }; + let chosen: Vec<(String, acadrust::objects::PlotSettings)> = draft + .selected() + .map(|(name, ps)| (name.to_string(), ps.clone())) + .collect(); + let imported = self.import_page_setups(chosen); + if let Some(first) = imported.first() { + let first = first.clone(); + self.select_page_setup(&first); + } + Task::none() + } + PageSetupImportMsg::Cancel => { + self.plot_dialog.import_draft = None; + Task::none() + } + } + } + + /// A drawing was picked for import: read it in the background; the + /// chooser appears in the Plot dialog once it is read. + pub(super) fn on_page_setup_import_file(&mut self, path: PathBuf) -> Task { + // The chooser lives in the Plot dialog; open it if the import was + // started from the command line. + let open = if self.active_modal == Some(crate::app::ModalKind::Plot) { + Task::none() + } else { + self.on_plot_dialog_open() + }; + Task::batch([open, read_page_setups_task(path)]) + } + + /// Bring the given setups into the drawing, replacing same-named ones, + /// as one undoable step. Returns the names imported. + pub(super) fn import_page_setups( + &mut self, + setups: Vec<(String, acadrust::objects::PlotSettings)>, + ) -> Vec { + if setups.is_empty() { + return Vec::new(); + } + let i = self.active_tab; + self.push_undo_snapshot(i, "PSETUPIN"); + let mut replaced = 0usize; + let existing = self.tabs[i].scene.page_setup_names(); + let mut names = Vec::with_capacity(setups.len()); + for (name, ps) in setups { + if existing.iter().any(|n| n.eq_ignore_ascii_case(&name)) { + replaced += 1; + } + self.tabs[i].scene.import_page_setup(&name, ps); + names.push(name); + } + self.tabs[i].dirty = true; + self.refresh_page_setups(); + let count = names.len(); + self.command_line.push_output( + crate::tf!("PSETUPIN: {count} page setup(s) imported, {replaced} replaced.").as_ref(), + ); + names + } + + /// `PSETUPIN` / `-PSETUPIN`: with no file, ask for one and offer its + /// setups in the Plot dialog; with ` *` or ` name,name` + /// import those straight away; with just `` open the chooser on it. + pub(in crate::app) fn on_psetupin(&mut self, args: &str) -> Task { + let args = args.trim(); + if args.is_empty() { + return self.on_page_setup_import(PageSetupImportMsg::Pick); + } + // The file may carry spaces; the setup list, when given, is the last + // word (`*` or a comma-separated list without spaces), so the + // arguments are a file followed by names only when what precedes the + // last word is itself a file. + let (file, names) = match args.rsplit_once(char::is_whitespace) { + Some((file, names)) if Path::new(file.trim()).is_file() => { + (file.trim(), Some(names.trim())) + } + _ => (args, None), + }; + let path = PathBuf::from(file); + let Some(names) = names else { + return self.on_page_setup_import_file(path); + }; + let available = match crate::io::load_file(&path) { + Ok(document) => crate::scene::document_page_setups(&document), + Err(error) => { + self.command_line + .push_error(crate::tf!("PSETUPIN: {error}").as_ref()); + return Task::none(); + } + }; + let chosen: Vec<(String, acadrust::objects::PlotSettings)> = if names == "*" { + available + } else { + let wanted: Vec<&str> = names.split(',').map(str::trim).collect(); + let mut chosen = Vec::new(); + for want in wanted { + match available.iter().find(|(n, _)| n.eq_ignore_ascii_case(want)) { + Some(found) => chosen.push(found.clone()), + None => self.command_line.push_error( + crate::tf!("PSETUPIN: no page setup named \"{want}\" in {file}.") + .as_ref(), + ), + } + } + chosen + }; + if chosen.is_empty() { + self.command_line + .push_info(crate::tf!("PSETUPIN: nothing to import from {file}.").as_ref()); + return Task::none(); + } + self.import_page_setups(chosen); + Task::none() + } +} + +#[cfg(all(test, not(target_arch = "wasm32")))] +mod tests { + use crate::app::{Message, OpenCADStudio}; + use crate::ui::window::plot::{PageSetupImportMsg as I, PlotDlgMsg}; + + const FIXTURE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/plot/page-setups-metric.dxf" + ); + + fn app() -> OpenCADStudio { + let mut app = OpenCADStudio::new_for_test(); + app.automation_op(r#"{"op":"new"}"#); + let _ = app.update(Message::LayoutSwitch("Layout1".into())); + app + } + + fn last_line(app: &OpenCADStudio) -> String { + app.command_line.history.last().map(|line| line.text.clone()).unwrap_or_default() + } + + /// The fixture's page setups as the chooser would hold them, read + /// through the same document reader the command uses. + fn fixture_setups() -> Vec<(String, acadrust::objects::PlotSettings)> { + let document = crate::io::load_file(std::path::Path::new(FIXTURE)).unwrap(); + crate::scene::document_page_setups(&document) + } + + #[test] + fn the_fixture_reader_lists_its_named_page_setups() { + // The fixture carries its page setups on the layouts, not as named + // setups; a drawing with named ones lists them in dictionary order. + assert!(fixture_setups().is_empty()); + let mut app = app(); + let i = app.active_tab; + let ps = app.tabs[i].scene.plot_settings_for("Layout1").unwrap(); + app.tabs[i].scene.page_setup_save("Site", ps.clone()); + app.tabs[i].scene.page_setup_save("Detail", ps); + let names: Vec = crate::scene::document_page_setups(&app.tabs[i].scene.document) + .into_iter() + .map(|(name, _)| name) + .collect(); + assert_eq!(names, ["Site", "Detail"]); + } + + #[test] + fn psetupin_imports_named_setups_from_a_saved_drawing() { + use acadrust::objects::PlotRotation; + // Author a source drawing with two named setups and save it. + let dir = std::env::temp_dir().join(format!("ocs-psetupin-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let source = dir.join("source.dxf"); + { + let mut app = app(); + let i = app.active_tab; + let mut ps = app.tabs[i].scene.plot_settings_for("Layout1").unwrap(); + ps.printer_name = "DWG To PDF.pc3".into(); + ps.paper_size = "ISO_A3_(297.00_x_420.00_MM)".into(); + ps.paper_width = 297.0; + ps.paper_height = 420.0; + ps.rotation = PlotRotation::Degrees90; + ps.plot_view_handle = acadrust::Handle::new(0xBEEF); + app.tabs[i].scene.page_setup_save("Site", ps.clone()); + ps.paper_size = "ISO_A2_(420.00_x_594.00_MM)".into(); + app.tabs[i].scene.page_setup_save("Overview", ps); + let save = format!(r#"{{"op":"save","path":"{}"}}"#, source.display()); + let reply = app.automation_op(&save); + assert_eq!(reply["ok"], true, "{reply}"); + } + let mut app = app(); + let i = app.active_tab; + // One named setup already exists under a name the file also has. + let own = app.tabs[i].scene.plot_settings_for("Layout1").unwrap(); + app.tabs[i].scene.page_setup_save("Site", own); + // `*` imports everything, replacing the same-named one. + let _ = app.run_command_line(&format!("PSETUPIN {} *", source.display())); + // The report is translated on localized systems; the counts are not. + let report = last_line(&app); + assert!(report.starts_with("PSETUPIN"), "{report}"); + assert!(report.contains('2') && report.contains('1'), "{report}"); + let names = app.tabs[i].scene.page_setup_names(); + assert_eq!(names, ["Site", "Overview"]); + let site = app.tabs[i].scene.page_setup_get("Site").unwrap(); + assert_eq!(site.paper_size, "ISO_A3_(297.00_x_420.00_MM)"); + assert_eq!(site.rotation, PlotRotation::Degrees90); + assert!(site.plot_view_handle.is_null(), "foreign handles are dropped"); + assert!(app.tabs[i].dirty); + // Undo takes the whole import back. + let _ = app.update(Message::Undo); + let names = app.tabs[i].scene.page_setup_names(); + assert_eq!(names, ["Site"]); + assert_ne!( + app.tabs[i].scene.page_setup_get("Site").unwrap().paper_size, + "ISO_A3_(297.00_x_420.00_MM)" + ); + // A name list imports just those and reports the unknown ones. + let _ = app.run_command_line(&format!("PSETUPIN {} Overview,Nothing", source.display())); + assert_eq!(app.tabs[i].scene.page_setup_names(), ["Site", "Overview"]); + // A missing file is an error, not a crash. + let _ = app.run_command_line("PSETUPIN /nowhere/at/all.dwg *"); + assert!(last_line(&app).starts_with("PSETUPIN:")); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + fn the_chooser_imports_the_ticked_setups_and_selects_the_first() { + let mut app = app(); + let i = app.active_tab; + let ps = app.tabs[i].scene.plot_settings_for("Layout1").unwrap(); + let _ = app.on_plot_dialog_open(); + let setups = ["A", "B", "C"] + .into_iter() + .map(|name| (name.to_string(), ps.clone())) + .collect(); + let loaded = Ok(("other.dwg".to_string(), setups)); + let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Loaded(loaded))); + let draft = app.plot_dialog.import_draft.as_ref().expect("chooser open"); + assert_eq!(draft.file, "other.dwg"); + assert_eq!(draft.selected().count(), 3, "everything ticked to start with"); + let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::All(false))); + assert_eq!(app.plot_dialog.import_draft.as_ref().unwrap().selected().count(), 0); + let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Toggle("B".into()))); + let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Toggle("C".into()))); + let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Apply)); + assert!(app.plot_dialog.import_draft.is_none(), "Apply closes the chooser"); + assert_eq!(app.tabs[i].scene.page_setup_names(), ["B", "C"]); + assert_eq!(app.plot_dialog.selected_setup, "B"); + assert!(app.plot_dialog.page_setups.iter().any(|name| name == "C")); + // A failed read shows its reason in the chooser; Cancel closes it. + let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Loaded(Err("unreadable".into())))); + assert_eq!( + app.plot_dialog.import_draft.as_ref().unwrap().error.as_deref(), + Some("unreadable") + ); + let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Cancel)); + assert!(app.plot_dialog.import_draft.is_none()); + } + + #[test] + fn a_new_layout_opens_its_page_setup_when_the_option_is_on() { + let mut app = app(); + let _ = app.update(Message::LayoutCreate); + assert_ne!(app.active_modal, Some(crate::app::ModalKind::Plot)); + let _ = app.update(Message::PageSetupOnNewLayoutChanged(true)); + assert!(app.current_config().plot.page_setup_on_new_layout, "persisted"); + let _ = app.update(Message::LayoutCreate); + assert_eq!(app.active_modal, Some(crate::app::ModalKind::Plot)); + assert_eq!(app.plot_dialog.selected_setup, "*Layout3*"); + } +} diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 6241fde5b..c249039a4 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -6047,6 +6047,12 @@ properties={:.1}ms picked={}", crate::tf!("Layout \"{new_name}\" created — use MVIEW to add a viewport") .as_ref(), ); + // The option that greets every new layout with its page setup. + let switch_task = if self.plot_dialog.page_setup_on_new_layout { + Task::batch([switch_task, self.on_plot_dialog_open()]) + } else { + switch_task + }; self.tabs[i].dirty = true; return Task::batch([cancel_task, switch_task]); } diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index a207976a8..bae3ff197 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -289,6 +289,7 @@ impl OpenCADStudio { crate::ui::window::options::AppPrefs { savetime_min: self.savetime_min, backup_on_save: self.backup_on_save, + page_setup_on_new_layout: self.plot_dialog.page_setup_on_new_layout, textfill: crate::scene::text::sdf_atlas::textfill(), cliprompt_lines: self.cliprompt_lines, commandline_fade_ms: self.commandline_fade_ms, diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 091396a24..1b7e25400 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -3144,6 +3144,15 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "World" => Some(("common", "world")), "Write Block" => Some(("insert", "write-block")), "X (mm)" => Some(("plot", "x-mm")), + "Select Drawing to Import Page Setups From" => Some(("plot", "select-drawing-to-import-page-setups-from")), + "Drawings and templates" => Some(("plot", "drawings-and-templates")), + "PSETUPIN: {count} page setup(s) imported, {replaced} replaced." => Some(("plot", "psetupin-count-page-setup-s-imported-replaced-replaced")), + "PSETUPIN: {error}" => Some(("plot", "psetupin-error")), + "PSETUPIN: no page setup named \"{want}\" in {file}." => Some(("plot", "psetupin-no-page-setup-named-want-in-file")), + "PSETUPIN: nothing to import from {file}." => Some(("plot", "psetupin-nothing-to-import-from-file")), + "No named page setups in this drawing." => Some(("plot", "no-named-page-setups-in-this-drawing")), + "Import…" => Some(("plot", "import-ellipsis")), + "Show the page setup for new layouts" => Some(("plot", "show-the-page-setup-for-new-layouts")), "X (in)" => Some(("plot", "x-in")), "Y (in)" => Some(("plot", "y-in")), "Hide paperspace objects" => Some(("plot", "hide-paperspace-objects")), diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 31d691c80..e2b373127 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -33,7 +33,7 @@ pub(crate) mod centermark; pub(crate) mod dimension_assoc; pub(crate) mod dimension_assoc_chain; pub use dimension_assoc::{ReferenceStatus, ResolvedReference}; -pub use page_setup::{apply_default_page_setup, rotated_margins}; +pub use page_setup::{apply_default_page_setup, document_page_setups, rotated_margins}; mod dwg_native_constraints; mod entity; #[cfg(test)] diff --git a/src/scene/page_setup.rs b/src/scene/page_setup.rs index 5f900d181..af3926e75 100644 --- a/src/scene/page_setup.rs +++ b/src/scene/page_setup.rs @@ -6,7 +6,7 @@ use super::Scene; use acadrust::objects::{Dictionary, Layout, ObjectType, PlotSettings}; -use acadrust::Handle; +use acadrust::{CadDocument, Handle}; /// Give a freshly created paper layout the page setup a new drawing starts /// with — ISO A4 landscape on no plotter, plotted 1:1 as a layout — stored the @@ -59,30 +59,59 @@ pub fn rotated_margins( } } -impl Scene { - /// Handle of the `ACAD_PLOTSETTINGS` dictionary, located robustly. - /// - /// The canonical path is the header's `acad_plotsettings_dict_handle`, but - /// DWGs written by other programs don't always leave that pointer resolvable - /// (the header handle points at no loaded dictionary — see - /// [`crate::scene::annotative::root_named_dict_handle`]). In that case fall - /// back to the dictionary that owns the drawing's `PlotSettings` objects, - /// mirroring [`Scene::scalelist_dict_handle`]. Returns `None` when the - /// drawing genuinely has no named page setups. - fn plotsettings_dict_handle(&self) -> Option { - let dh = self.document.header.acad_plotsettings_dict_handle; - if matches!(self.document.objects.get(&dh), Some(ObjectType::Dictionary(_))) { - return Some(dh); - } - let owner = self.document.objects.values().find_map(|o| match o { - ObjectType::PlotSettings(ps) => Some(ps.owner), +/// Handle of a document's `ACAD_PLOTSETTINGS` dictionary, located robustly. +/// +/// The canonical path is the header's `acad_plotsettings_dict_handle`, but +/// DWGs written by other programs don't always leave that pointer resolvable +/// (the header handle points at no loaded dictionary — see +/// [`crate::scene::annotative::root_named_dict_handle`]). In that case fall +/// back to the dictionary that owns the drawing's `PlotSettings` objects, +/// mirroring [`Scene::scalelist_dict_handle`]. Returns `None` when the +/// drawing genuinely has no named page setups. +fn plotsettings_dict_handle_in(document: &CadDocument) -> Option { + let dh = document.header.acad_plotsettings_dict_handle; + if matches!(document.objects.get(&dh), Some(ObjectType::Dictionary(_))) { + return Some(dh); + } + let owner = document.objects.values().find_map(|o| match o { + ObjectType::PlotSettings(ps) => Some(ps.owner), + _ => None, + })?; + matches!(document.objects.get(&owner), Some(ObjectType::Dictionary(_))).then_some(owner) +} + +/// The named page setups of any document — `(name, settings)` in dictionary +/// order. Reads a drawing that is not open in the editor (the source of a +/// `PSETUPIN` import) the same way the editor reads its own. +pub fn document_page_setups(document: &CadDocument) -> Vec<(String, PlotSettings)> { + let Some(dh) = plotsettings_dict_handle_in(document) else { + return Vec::new(); + }; + let Some(ObjectType::Dictionary(dict)) = document.objects.get(&dh) else { + return Vec::new(); + }; + dict.entries + .iter() + .filter_map(|(name, handle)| match document.objects.get(handle) { + Some(ObjectType::PlotSettings(ps)) => Some((name.clone(), ps.clone())), _ => None, - })?; - matches!( - self.document.objects.get(&owner), - Some(ObjectType::Dictionary(_)) - ) - .then_some(owner) + }) + .collect() +} + +/// A page setup copied out of another drawing, made safe for this one: the +/// handles it pointed at (a plot view, a visual style) meant something only +/// in its source, so they are dropped while the names, which travel, stay. +pub fn detach_page_setup(ps: &mut PlotSettings) { + ps.plot_view_handle = Handle::NULL; + ps.visual_style_handle = Handle::NULL; + ps.reactors.clear(); + ps.xdictionary_handle = None; +} + +impl Scene { + fn plotsettings_dict_handle(&self) -> Option { + plotsettings_dict_handle_in(&self.document) } /// Names of the document's named page setups, in dictionary order. @@ -93,6 +122,13 @@ impl Scene { } } + /// Bring a page setup from another drawing in under `name`, replacing + /// one of that name the way `PSETUPIN` redefines it. + pub fn import_page_setup(&mut self, name: &str, mut ps: PlotSettings) { + detach_page_setup(&mut ps); + self.page_setup_save(name, ps); + } + /// Clone the named page setup's `PlotSettings`, or `None` if absent. pub fn page_setup_get(&self, name: &str) -> Option { let dh = self.plotsettings_dict_handle()?; diff --git a/src/ui/window/options.rs b/src/ui/window/options.rs index 1c1f35322..56fb4972d 100644 --- a/src/ui/window/options.rs +++ b/src/ui/window/options.rs @@ -77,6 +77,8 @@ pub struct AppPrefs { pub right_click_mode: RightClickMode, /// SHORTCUTMENUDURATION: time-sensitive hold threshold, ms. pub right_click_hold_ms: i32, + /// Open the Plot / Page Setup dialog for every new layout. + pub page_setup_on_new_layout: bool, } /// The fixed locations the Files page lists. @@ -361,6 +363,29 @@ pub fn view_window<'a>( ] .spacing(10) .align_y(iced::Center), + Space::new().height(22), + text(crate::t!("Plotting")).size(15), + Space::new().height(10), + row![ + text(crate::t!("Plot device, paper, scale and plot styles")) + .size(12) + .width(Fill), + button(text(crate::t!("Plot and Page Setup…")).size(11)) + .on_press(Message::PlotDialogOpen) + .padding([4, 10]) + .style(button::secondary), + ] + .spacing(10) + .align_y(iced::Center), + Space::new().height(10), + row![ + iced::widget::checkbox(prefs.page_setup_on_new_layout) + .on_toggle(Message::PageSetupOnNewLayoutChanged) + .size(15), + text(crate::t!("Show the page setup for new layouts")).size(12), + ] + .spacing(8) + .align_y(iced::Center), ] .spacing(0) .width(sizing.width); @@ -456,20 +481,6 @@ pub fn view_window<'a>( ] .spacing(8) .align_y(iced::Center), - Space::new().height(22), - text(crate::t!("Plotting")).size(15), - Space::new().height(10), - row![ - text(crate::t!("Plot device, paper, scale and plot styles")) - .size(12) - .width(Fill), - button(text(crate::t!("Plot and Page Setup…")).size(11)) - .on_press(Message::PlotDialogOpen) - .padding([4, 10]) - .style(button::secondary), - ] - .spacing(10) - .align_y(iced::Center), ] .spacing(0) .width(sizing.width); diff --git a/src/ui/window/plot.rs b/src/ui/window/plot.rs index 392ae8704..2389b1be8 100644 --- a/src/ui/window/plot.rs +++ b/src/ui/window/plot.rs @@ -151,6 +151,43 @@ pub enum PlotFlag { SaveToLayout, } +/// The page setups of another drawing, offered for import (`PSETUPIN`). +#[derive(Debug, Clone, PartialEq)] +pub struct PageSetupImportDraft { + /// The drawing they come from, as shown to the user. + pub file: String, + /// Each setup with whether it is ticked for import. The settings are + /// carried along so the source file is read once. + pub setups: Vec<(String, acadrust::objects::PlotSettings, bool)>, + /// Why the file gave nothing, when it did not. + pub error: Option, +} + +impl PageSetupImportDraft { + pub fn selected(&self) -> impl Iterator { + self.setups + .iter() + .filter(|(_, _, on)| *on) + .map(|(name, ps, _)| (name.as_str(), ps)) + } +} + +/// Edits of the page-setup import chooser. +#[derive(Debug, Clone)] +pub enum PageSetupImportMsg { + /// Ask for the drawing to import from. + Pick, + /// The drawing was read (or could not be): its name and page setups. + Loaded(Result<(String, Vec<(String, acadrust::objects::PlotSettings)>), String>), + /// Tick or untick one setup. + Toggle(String), + /// Tick or untick every setup. + All(bool), + /// Import the ticked setups and close the chooser. + Apply, + Cancel, +} + /// Every edit the Plot dialog can emit. Wrapped in `Message::PlotDlg` so the /// top-level match stays a single arm. #[derive(Debug, Clone)] @@ -209,6 +246,8 @@ pub enum PlotDlgMsg { CopySetup, /// Begin an inline rename of the given page setup row. RenameStart(String), + /// Import named page setups from another drawing (`PSETUPIN`). + Import(PageSetupImportMsg), /// Delete the selected named page setup. DeleteSetup, /// Live edit of the new/rename name field. @@ -397,6 +436,12 @@ pub struct PlotDialogState { /// The in-line printer-properties editor while it is open. #[serde(skip)] pub printer_editor: Option, + /// The page-setup import chooser while it is open. + #[serde(skip)] + pub import_draft: Option, + /// Open the Plot / Page Setup dialog whenever a new layout is created, + /// the way a page-setup manager is expected to greet a new layout. + pub page_setup_on_new_layout: bool, /// Chosen printer name, or `None` for the system default. pub printer: Option, /// Output goes to a PDF file instead of a printer. @@ -502,6 +547,8 @@ impl Default for PlotDialogState { custom_editor: None, driver_options: std::collections::BTreeMap::new(), printer_editor: None, + import_draft: None, + page_setup_on_new_layout: false, printer: None, to_file: false, paper: paper_catalog::default_paper().canonical.to_string(), @@ -720,6 +767,58 @@ fn check_enabled<'a>( .into() } +/// The import chooser: the source drawing's name, one tick per page setup, +/// All / None, and Import / Cancel. +fn page_setup_import_chooser<'a>( + draft: &'a PageSetupImportDraft, + height: Length, +) -> Element<'a, Message> { + let msg = |m: PageSetupImportMsg| Message::PlotDlg(PlotDlgMsg::Import(m)); + let mut rows = column![].spacing(3); + if let Some(error) = &draft.error { + rows = rows.push(text(error.clone()).size(10).style(muted_style)); + } else if draft.setups.is_empty() { + let none = text(t!("No named page setups in this drawing.")).size(10); + rows = rows.push(none.style(muted_style)); + } + for (name, _, on) in &draft.setups { + let name = name.clone(); + rows = rows.push( + checkbox(*on) + .label(name.clone()) + .on_toggle(move |_| msg(PageSetupImportMsg::Toggle(name.clone()))) + .size(13) + .text_size(11), + ); + } + let all = !draft.setups.is_empty() && draft.setups.iter().all(|(_, _, on)| *on); + let any = draft.setups.iter().any(|(_, _, on)| *on); + let mut import = button(text(t!("Import")).size(11)).style(btn(true)).padding([4, 8]); + if any { + import = import.on_press(msg(PageSetupImportMsg::Apply)); + } + column![ + text(draft.file.clone()).size(10).style(muted_style), + scrollable(rows).height(height), + checkbox(all) + .label(t!("All")) + .on_toggle(move |on| msg(PageSetupImportMsg::All(on))) + .size(13) + .text_size(11), + row![ + import, + button(text(t!("Cancel")).size(11)) + .on_press(msg(PageSetupImportMsg::Cancel)) + .style(btn(false)) + .padding([4, 8]), + ] + .spacing(4), + ] + .spacing(6) + .padding(4) + .into() +} + /// The custom-scale row: `Custom: [paper] mm = [drawing] units`. Editing /// either side replaces the picked scale with the typed ratio. fn custom_scale_row<'a>(s: &'a PlotDialogState, enabled: bool) -> Element<'a, Message> { @@ -1011,6 +1110,12 @@ pub fn view_window( } else { Space::new().height(0).into() }; + // While an import is being chosen, the list shows the other drawing's + // setups with a tick each instead of this drawing's. + let list_body: Element<'_, Message> = match &s.import_draft { + Some(draft) => page_setup_import_chooser(draft, height), + None => list_body, + }; let list_panel = container( column![ text(t!("Page setups")).size(10).style(muted_style), @@ -1062,10 +1167,18 @@ pub fn view_window( if is_named && !print_all_options { delete_button = delete_button.on_press(Message::PlotDlg(PlotDlgMsg::DeleteSetup)); } + let mut import_button = button(text(t!("Import…")).size(11)) + .style(btn(false)) + .padding([4, 12]); + if !print_all_options && s.import_draft.is_none() { + import_button = import_button + .on_press(Message::PlotDlg(PlotDlgMsg::Import(PageSetupImportMsg::Pick))); + } let left_bar = row![ new_button, copy_button, delete_button, + import_button, ] .spacing(4); diff --git a/tests/fixtures/plot/.page-setups-metric.dxf.ocs.lock b/tests/fixtures/plot/.page-setups-metric.dxf.ocs.lock index 89d6cf93e..09e9f4707 100644 --- a/tests/fixtures/plot/.page-setups-metric.dxf.ocs.lock +++ b/tests/fixtures/plot/.page-setups-metric.dxf.ocs.lock @@ -1 +1 @@ -Open CAD Studio pid=842697 path=/home/panos/OpenCADStudio/tests/fixtures/plot/page-setups-metric.dxf +Open CAD Studio pid=854778 path=/home/panos/OpenCADStudio/tests/fixtures/plot/page-setups-metric.dxf