Merge PR #1585 from ramox81/codex/field-command: FIELD command and Field dialog; attribute definition field buttons

The PR pinned the codec to its fork through [patch]; codec #75 is merged
upstream and main already points at it, so the patch section is dropped.
The two windows-sys features it adds (Globalization, Identity) stay.
This commit is contained in:
Hakan Seven committed 2026-10-02 14:14:15 +03:00
commit 0de1e91f99
45 files changed
+2456 -15

No files matched your search

+1 -1
View File
@@ -74,7 +74,7 @@ naga = { version = "27", features = ["wgsl-in"] }
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
libloading = "0.8" libloading = "0.8"
windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_UI_Input", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_Security", "Win32_System_Com", "Win32_System_Registry", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Graphics_Printing", "Win32_Graphics_Gdi", "Win32_Storage_Xps"] } windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_UI_Input", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_Security", "Win32_System_Com", "Win32_System_Registry", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Graphics_Printing", "Win32_Graphics_Gdi", "Win32_Storage_Xps", "Win32_Globalization", "Win32_Security_Authentication_Identity"] }
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
ashpd = { version = "0.13.13", default-features = false, features = ["async-io", "wayland"] } ashpd = { version = "0.13.13", default-features = false, features = ["async-io", "wayland"] }
+38
View File
@@ -4087,6 +4087,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = إنشاء .create = إنشاء
.boolean = عمليات منطقية .boolean = عمليات منطقية
+38
View File
@@ -4074,6 +4074,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Създаване .create = Създаване
.boolean = Булеви операции .boolean = Булеви операции
+38
View File
@@ -4072,6 +4072,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Vytvořit .create = Vytvořit
.boolean = Booleovské operace .boolean = Booleovské operace
+38
View File
@@ -4073,6 +4073,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Erstellen .create = Erstellen
.boolean = Boolesche Operationen .boolean = Boolesche Operationen
+38
View File
@@ -4075,6 +4075,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Δημιουργία .create = Δημιουργία
.boolean = Δυαδική πράξη .boolean = Δυαδική πράξη
+38
View File
@@ -4076,6 +4076,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Create .create = Create
.boolean = Boolean .boolean = Boolean
+38
View File
@@ -4075,6 +4075,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Crear .create = Crear
.boolean = Booleanas .boolean = Booleanas
+38
View File
@@ -4074,6 +4074,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Luo .create = Luo
.boolean = Boolen operaatiot .boolean = Boolen operaatiot
+38
View File
@@ -4075,6 +4075,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Créer .create = Créer
.boolean = Opérations booléennes .boolean = Opérations booléennes
+38
View File
@@ -4063,6 +4063,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = बनाएँ .create = बनाएँ
.boolean = बूलियन संक्रियाएँ .boolean = बूलियन संक्रियाएँ
+38
View File
@@ -4074,6 +4074,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Létrehozás .create = Létrehozás
.boolean = Logikai műveletek .boolean = Logikai műveletek
+38
View File
@@ -4074,6 +4074,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Crea .create = Crea
.boolean = Operazioni booleane .boolean = Operazioni booleane
+38
View File
@@ -4063,6 +4063,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = 作成 .create = 作成
.boolean = ブール演算 .boolean = ブール演算
+38
View File
@@ -4074,6 +4074,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = 생성 .create = 생성
.boolean = 불리언 연산 .boolean = 불리언 연산
+38
View File
@@ -4073,6 +4073,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Maken .create = Maken
.boolean = Booleaanse bewerkingen .boolean = Booleaanse bewerkingen
+38
View File
@@ -4074,6 +4074,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Utwórz .create = Utwórz
.boolean = Operacje boolowskie .boolean = Operacje boolowskie
+38
View File
@@ -4075,6 +4075,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Criar .create = Criar
.boolean = Operações booleanas .boolean = Operações booleanas
+38
View File
@@ -4076,6 +4076,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = Создание .create = Создание
.boolean = Булевы операции .boolean = Булевы операции
+38
View File
@@ -4035,6 +4035,44 @@ insert =
.attdef-tag-spaces = Etiket boşluk içeremez. .attdef-tag-spaces = Etiket boşluk içeremez.
.attdef-positive = Değer pozitif ve sıfırdan farklı olmalı. .attdef-positive = Değer pozitif ve sıfırdan farklı olmalı.
.attdef-angle = Geçerli bir sayısal açı veya ikinci nokta gerekir. .attdef-angle = Geçerli bir sayısal açı veya ikinci nokta gerekir.
.field-title = Alan
.field-category = Kategori
.field-date-format = Tarih biçimi
.field-examples = Örnekler
.field-file-extension = Dosya uzantısını göster
.field-filename-only = Yalnız dosya adı
.field-path-only = Yalnız yol
.field-path-filename = Yol ve dosya adı
.field-bytes = Bayt
.field-kilobytes = Kilobayt
.field-megabytes = Megabayt
.field-system-variable = Sistem değişkeni
.field-diesel = Diesel ifadesi
.field-named-type = Adlandırılmış nesne türü
.field-object-type = Nesne türü
.field-select-object = Nesne seç
.field-expression = Alan ifadesi
.field-help = Bir alan ekler: çizimden, tarihten veya başka bir kaynaktan güncellenen metin.
.field-cat-linked = Bağlantılı
.field-current-precision = Geçerli hassasiyet
.field-evaluate = Hesapla
.field-text-to-display = Görüntülenecek metin
.field-address = Adres
.field-select-link-file = Bağlanacak dosyayı seçin
.field-current-units = Geçerli birimler
.field-use-scale-name = Ölçek adını kullan
.field-cat-date = Tarih ve Saat
.field-cat-document = Belge
.field-cat-other = Diğer
.field-case-upper = Büyük harf
.field-case-lower = Küçük harf
.field-case-first = İlk harf büyük
.field-case-title = Başlık
.field-named-dimstyle = Ölçü stili
.field-named-tablestyle = Tablo stili
.field-named-textstyle = Metin stili
.field-prop-endpoint = Bitiş noktası
.field-prop-startpoint = Başlangıç noktası
model = model =
.create = Oluştur .create = Oluştur
.boolean = Boole işlemleri .boolean = Boole işlemleri
+38
View File
@@ -4058,6 +4058,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = 创建 .create = 创建
.boolean = 布尔运算 .boolean = 布尔运算
+38
View File
@@ -4070,6 +4070,44 @@ insert =
.attdef-tag-spaces = The tag may not contain spaces. .attdef-tag-spaces = The tag may not contain spaces.
.attdef-positive = Value must be positive and nonzero. .attdef-positive = Value must be positive and nonzero.
.attdef-angle = Requires valid numeric angle or second point. .attdef-angle = Requires valid numeric angle or second point.
.field-title = Field
.field-category = Category
.field-date-format = Date format
.field-examples = Examples
.field-file-extension = Display file extension
.field-filename-only = Filename only
.field-path-only = Path only
.field-path-filename = Path and filename
.field-bytes = Bytes
.field-kilobytes = Kilobytes
.field-megabytes = Megabytes
.field-system-variable = System variable
.field-diesel = Diesel expression
.field-named-type = Named object type
.field-object-type = Object type
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
.field-text-to-display = Text to display
.field-address = Address
.field-select-link-file = Select a file to link
.field-current-units = Current units
.field-use-scale-name = Use scale name
.field-cat-date = Date & Time
.field-cat-document = Document
.field-cat-other = Other
.field-case-upper = Uppercase
.field-case-lower = Lowercase
.field-case-first = First capital
.field-case-title = Title case
.field-named-dimstyle = Dimstyle
.field-named-tablestyle = Tablestyle
.field-named-textstyle = Textstyle
.field-prop-endpoint = EndPoint
.field-prop-startpoint = StartPoint
model = model =
.create = 建立 .create = 建立
.boolean = 布林運算 .boolean = 布林運算
+18 -1
View File
@@ -226,8 +226,17 @@ impl OpenCADStudio {
self.tabs[i].scene.document.header.text_height = attribute.height; self.tabs[i].scene.document.header.text_height = attribute.height;
} }
} }
// A field the committed text shows (FIELD, an ATTDEF Default from the
// Field dialog) is attached once the entity has its handle; the field
// objects are not part of the add delta, so the step is a snapshot.
let text_field = self.tabs[i].active_cmd.as_ref().and_then(|cmd| cmd.text_field());
let label = self.history_label_from_active_cmd(i, "ENTITY"); let label = self.history_label_from_active_cmd(i, "ENTITY");
let delta_safe = self.delta_add_safe(i, &entity); let delta_safe = text_field.is_none() && self.delta_add_safe(i, &entity);
let field_value = match &entity {
codec::EntityType::MText(m) => m.value.clone(),
codec::EntityType::AttributeDefinition(a) => a.default_value.clone(),
_ => String::new(),
};
let pending = self.begin_undo(i, label, 1, delta_safe); let pending = self.begin_undo(i, label, 1, delta_safe);
let is_associative_dimension = matches!( let is_associative_dimension = matches!(
entity, entity,
@@ -247,6 +256,14 @@ impl OpenCADStudio {
.attach_dimension_association(handle, sources); .attach_dimension_association(handle, sources);
} }
} }
if let (Some(handle), Some(field)) = (committed, text_field) {
crate::entities::field::set_text_field(
&mut self.tabs[i].scene.document,
handle,
Some(field),
&field_value,
);
}
if let Some(handle) = committed { if let Some(handle) = committed {
self.apply_continuous_constraints(i, &[handle]); self.apply_continuous_constraints(i, &[handle]);
} }
+34 -3
View File
@@ -54,6 +54,7 @@ impl OpenCADStudio {
if let Some(state) = self.attdef_dialog.as_mut() { if let Some(state) = self.attdef_dialog.as_mut() {
if let Some(value) = value { if let Some(value) = value {
state.default = value; state.default = value;
state.field = None;
} }
self.active_modal = Some(crate::app::ModalKind::AttDef); self.active_modal = Some(crate::app::ModalKind::AttDef);
} }
@@ -134,6 +135,7 @@ impl OpenCADStudio {
align_below: false, align_below: false,
can_align_below: session.last.is_some(), can_align_below: session.last.is_some(),
error: None, error: None,
field: None,
}); });
drop(session); drop(session);
self.tabs[i].active_cmd = None; self.tabs[i].active_cmd = None;
@@ -154,6 +156,8 @@ impl OpenCADStudio {
default: a.default_value.clone(), default: a.default_value.clone(),
constant: a.flags.constant, constant: a.flags.constant,
error: None, error: None,
field: None,
field_changed: false,
}); });
self.active_modal = Some(crate::app::ModalKind::AttDefEdit); self.active_modal = Some(crate::app::ModalKind::AttDefEdit);
} }
@@ -164,7 +168,15 @@ impl OpenCADStudio {
match m { match m {
AttdefDialogMsg::EditTag(v) => edit.tag = v, AttdefDialogMsg::EditTag(v) => edit.tag = v,
AttdefDialogMsg::EditPrompt(v) => edit.prompt = v, AttdefDialogMsg::EditPrompt(v) => edit.prompt = v,
AttdefDialogMsg::EditDefault(v) => edit.default = v, AttdefDialogMsg::EditDefault(v) => {
edit.default = v;
edit.field = None;
edit.field_changed = true;
}
AttdefDialogMsg::EditInsertField => {
self.active_modal = None;
self.open_field_dialog(crate::ui::window::field_dialog::FieldTarget::AttdefEdit);
}
AttdefDialogMsg::Help => self.command_line.push_info( AttdefDialogMsg::Help => self.command_line.push_info(
crate::t!("Changes the tag, prompt and default value of an attribute definition.") crate::t!("Changes the tag, prompt and default value of an attribute definition.")
.as_ref(), .as_ref(),
@@ -189,7 +201,15 @@ impl OpenCADStudio {
AttdefDialogMsg::Annotative(v) => state.annotative = v, AttdefDialogMsg::Annotative(v) => state.annotative = v,
AttdefDialogMsg::Tag(v) => state.tag = v, AttdefDialogMsg::Tag(v) => state.tag = v,
AttdefDialogMsg::Prompt(v) => state.prompt = v, AttdefDialogMsg::Prompt(v) => state.prompt = v,
AttdefDialogMsg::Default(v) => state.default = v, AttdefDialogMsg::Default(v) => {
state.default = v;
state.field = None;
}
AttdefDialogMsg::InsertField => {
self.active_modal = None;
self.open_field_dialog(crate::ui::window::field_dialog::FieldTarget::AttdefDefault);
return Task::none();
}
AttdefDialogMsg::Justify(c) => state.justify = c.0, AttdefDialogMsg::Justify(c) => state.justify = c.0,
AttdefDialogMsg::Style(v) => state.style = v, AttdefDialogMsg::Style(v) => state.style = v,
AttdefDialogMsg::Height(v) => state.height = v, AttdefDialogMsg::Height(v) => state.height = v,
@@ -279,6 +299,7 @@ impl OpenCADStudio {
oblique_angle: defaults.oblique_angle, oblique_angle: defaults.oblique_angle,
boundary_width: width, boundary_width: width,
line_spacing: 1.0, line_spacing: 1.0,
field: state.field.clone(),
}; };
self.attdef_dialog = None; self.attdef_dialog = None;
self.close_active_modal(); self.close_active_modal();
@@ -288,6 +309,8 @@ impl OpenCADStudio {
session.annotative = spec.annotative; session.annotative = spec.annotative;
session.on_screen = state.on_screen; session.on_screen = state.on_screen;
} }
// The choice is kept with the user settings, as the reference keeps it.
self.persist_settings_if_changed();
self.reset_command_start_state(i); self.reset_command_start_state(i);
if state.align_below { if state.align_below {
let previous = attdef::session().last.clone(); let previous = attdef::session().last.clone();
@@ -336,7 +359,7 @@ impl OpenCADStudio {
self.tabs[i].scene.document.get_entity(state.handle), self.tabs[i].scene.document.get_entity(state.handle),
Some(codec::EntityType::AttributeDefinition(a)) Some(codec::EntityType::AttributeDefinition(a))
if a.tag != attdef::normalize_tag(&state.tag) || a.prompt != state.prompt || a.default_value != state.default if a.tag != attdef::normalize_tag(&state.tag) || a.prompt != state.prompt || a.default_value != state.default
); ) || state.field_changed;
if changed && !self.reject_locked_edit(i, state.handle) { if changed && !self.reject_locked_edit(i, state.handle) {
self.push_undo_snapshot(i, "DDEDIT"); self.push_undo_snapshot(i, "DDEDIT");
if let Some(codec::EntityType::AttributeDefinition(a)) = if let Some(codec::EntityType::AttributeDefinition(a)) =
@@ -348,6 +371,14 @@ impl OpenCADStudio {
} }
a.default_value = state.default.clone(); a.default_value = state.default.clone();
} }
if state.field_changed {
crate::entities::field::set_text_field(
&mut self.tabs[i].scene.document,
state.handle,
state.field.clone(),
&state.default,
);
}
self.tabs[i] self.tabs[i]
.scene .scene
.bump_entities(&[(state.handle, crate::scene::ChangeKind::Modified)]); .bump_entities(&[(state.handle, crate::scene::ChangeKind::Modified)]);
+228
View File
@@ -0,0 +1,228 @@
//! FIELD and the Field dialog behind the ƒ buttons of the attribute
//! definition dialogs.
use crate::app::{Message, OpenCADStudio};
use crate::command::CadCommand;
use crate::modules::annotate::field_cmd::{FieldObjectPickCommand, FieldPlaceCommand};
use crate::ui::window::field_dialog::{
fields_of, object_properties, FieldDialogMsg, FieldDialogState, FieldTarget, DATE_FORMATS,
NAMED_TYPES,
};
use iced::Task;
impl OpenCADStudio {
pub(super) fn dispatch_field(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
match cmd {
"FIELD" => {
self.open_field_dialog(FieldTarget::NewText);
Some(Task::none())
}
cmd if cmd == "_FIELD_OBJECT" || cmd.starts_with("_FIELD_OBJECT ") => {
let handle = cmd
.split_whitespace()
.nth(1)
.and_then(|h| u64::from_str_radix(h, 16).ok())
.map(codec::Handle::new);
if let Some(state) = self.field_dialog.as_mut() {
if let Some(entity) = handle.and_then(|h| self.tabs[i].scene.document.get_entity(h)) {
let props = object_properties(entity);
state.object = Some((entity.common().handle, crate::t!(crate::entities::names::ui_name(entity)).into_owned()));
state.object_prop = props.first().copied();
state.object_props = props;
}
self.active_modal = Some(crate::app::ModalKind::Field);
self.refresh_field_preview();
}
Some(Task::none())
}
_ => None,
}
}
/// Open the Field dialog for `target`.
pub(in crate::app) fn open_field_dialog(&mut self, target: FieldTarget) {
let mut state = FieldDialogState::new(target);
state.examples = DATE_FORMATS
.iter()
.map(|f| self.field_value(&format!("\\AcVar Date \\f \"{f}\""), &[]))
.collect();
self.field_dialog = Some(state);
self.fill_named_objects();
self.refresh_field_preview();
self.active_modal = Some(crate::app::ModalKind::Field);
}
fn field_value(&self, code: &str, objects: &[codec::Handle]) -> String {
crate::entities::field::evaluate(&self.tabs[self.active_tab].scene.document, code, objects, None)
.unwrap_or_else(|| "----".to_string())
}
fn refresh_field_preview(&mut self) {
let Some(state) = self.field_dialog.as_ref() else {
return;
};
let (code, objects) = state.code();
let preview = if state.complete() { self.field_value(&code, &objects) } else { String::new() };
if let Some(state) = self.field_dialog.as_mut() {
state.preview = preview;
}
}
/// The names of the chosen named-object type, sorted.
fn fill_named_objects(&mut self) {
let Some(state) = self.field_dialog.as_mut() else {
return;
};
let document = &self.tabs[self.active_tab].scene.document;
let mut names: Vec<(String, codec::Handle)> = match NAMED_TYPES[state.named_type] {
"Block" => document
.block_records
.iter()
.filter(|b| !b.name.starts_with('*'))
.map(|b| (b.name.clone(), b.handle))
.collect(),
"Dimstyle" => document.dim_styles.iter().map(|s| (s.name.clone(), s.handle)).collect(),
"Layer" => document.layers.iter().map(|l| (l.name.clone(), l.handle)).collect(),
"Linetype" => document.line_types.iter().map(|l| (l.name.clone(), l.handle)).collect(),
"Textstyle" => document
.text_styles
.iter()
.filter(|s| !s.name.is_empty())
.map(|s| (s.name.clone(), s.handle))
.collect(),
"View" => document.views.iter().map(|v| (v.name.clone(), v.handle)).collect(),
_ => Vec::new(),
};
names.sort_by_key(|(n, _)| n.to_lowercase());
state.named_names = names;
state.named = None;
}
pub(in crate::app) fn on_field_dialog(&mut self, m: FieldDialogMsg) -> Task<Message> {
let i = self.active_tab;
let Some(state) = self.field_dialog.as_mut() else {
return Task::none();
};
match m {
FieldDialogMsg::Category(c) => {
state.category = c;
let names = fields_of(c);
if !names.contains(&state.name) {
state.name = names.first().copied().unwrap_or("Date");
}
}
FieldDialogMsg::Name(n) => {
state.name = n;
state.text_case = 0;
}
FieldDialogMsg::DateFormat(f) => state.date_format = f,
FieldDialogMsg::DateExample(k) => {
if let Some(f) = DATE_FORMATS.get(k) {
state.date_format = f.to_string();
}
}
FieldDialogMsg::TextCase(k) => state.text_case = k,
FieldDialogMsg::FileParts(b) => state.file_parts = b,
FieldDialogMsg::FileExtension(v) => state.file_extension = v,
FieldDialogMsg::SizeUnit(k) => state.size_unit = k,
FieldDialogMsg::SysVar(v) => state.sysvar = v,
FieldDialogMsg::Diesel(v) => state.diesel = v,
FieldDialogMsg::NamedType(k) => {
state.named_type = k;
self.fill_named_objects();
}
FieldDialogMsg::Named(k) => state.named = Some(k),
FieldDialogMsg::ObjectProp(p) => state.object_prop = Some(p),
FieldDialogMsg::Formula(v) => state.formula = v,
FieldDialogMsg::FormulaFormat(k) => state.formula_format = k,
FieldDialogMsg::FormulaPrecision(k) => state.formula_precision = k,
FieldDialogMsg::Evaluate => {}
FieldDialogMsg::HyperlinkText(v) => state.hyperlink_text = v,
FieldDialogMsg::HyperlinkUrl(v) => state.hyperlink_url = v,
FieldDialogMsg::BrowseHyperlink => {
return Task::perform(
async {
rfd::AsyncFileDialog::new()
.set_title(crate::t!("Select a file to link").as_ref())
.pick_file()
.await
.map(|h| crate::sys::handle_path(&h))
},
|path| match path {
Some(path) => Message::FieldDialog(FieldDialogMsg::HyperlinkUrl(path.to_string_lossy().into_owned())),
None => Message::Noop,
},
);
}
FieldDialogMsg::PlotScale(k) => state.plot_scale = k,
FieldDialogMsg::SelectObject => {
// The dialog waits while one object is picked.
self.active_modal = None;
let command = FieldObjectPickCommand;
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
return Task::none();
}
FieldDialogMsg::Help => self.command_line.push_info(
crate::t!("Inserts a field: text that updates from the drawing, the date or another source.").as_ref(),
),
FieldDialogMsg::Ok => return self.field_dialog_ok(i),
}
self.refresh_field_preview();
Task::none()
}
fn field_dialog_ok(&mut self, i: usize) -> Task<Message> {
let Some(state) = self.field_dialog.take() else {
return Task::none();
};
let (code, objects) = state.code();
let value = state.preview.clone();
let field = Some((code, objects));
match state.target {
FieldTarget::NewText => {
self.close_active_modal();
let defaults =
crate::scene::creation_style::current_text_defaults(&self.tabs[i].scene.document);
let command = FieldPlaceCommand::new(
value,
field.expect("set above"),
defaults.style_name,
defaults.height,
);
self.reset_command_start_state(i);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
self.push_ucs_to_cmd(i);
return self.focus_cmd_input();
}
FieldTarget::AttdefDefault => {
if let Some(dialog) = self.attdef_dialog.as_mut() {
dialog.default = value;
dialog.field = field;
}
self.active_modal = Some(crate::app::ModalKind::AttDef);
}
FieldTarget::AttdefEdit => {
if let Some(edit) = self.attdef_edit.as_mut() {
edit.default = value;
edit.field = field;
edit.field_changed = true;
}
self.active_modal = Some(crate::app::ModalKind::AttDefEdit);
}
}
Task::none()
}
/// The dialogs a Field dialog was opened from come back when it closes.
pub(in crate::app) fn close_field_dialog(&mut self) {
let target = self.field_dialog.take().map(|s| s.target);
self.close_active_modal();
self.active_modal = match target {
Some(FieldTarget::AttdefDefault) if self.attdef_dialog.is_some() => Some(crate::app::ModalKind::AttDef),
Some(FieldTarget::AttdefEdit) if self.attdef_edit.is_some() => Some(crate::app::ModalKind::AttDefEdit),
_ => None,
};
}
}
+5
View File
@@ -5,6 +5,7 @@ use iced::Task;
use std::path::PathBuf; use std::path::PathBuf;
mod attdef; mod attdef;
mod field;
mod blocks; mod blocks;
mod xref_attach; mod xref_attach;
mod dim; mod dim;
@@ -292,6 +293,9 @@ impl OpenCADStudio {
if let Some(t) = self.dispatch_attdef(cmd, i) { if let Some(t) = self.dispatch_attdef(cmd, i) {
return Some(t); return Some(t);
} }
if let Some(t) = self.dispatch_field(cmd, i) {
return Some(t);
}
if let Some(t) = self.dispatch_draw(cmd, i) { if let Some(t) = self.dispatch_draw(cmd, i) {
return Some(t); return Some(t);
} }
@@ -664,6 +668,7 @@ inventory::submit!(crate::command::CommandRegistration {
"DGNFRAME", "DGNFRAME",
"PDFOSNAP", "PDFOSNAP",
"UOSNAP", "UOSNAP",
"FIELDDISPLAY",
"PDFIMPORTMODE", "PDFIMPORTMODE",
"PDFIMPORTFILTER", "PDFIMPORTFILTER",
"PDFIMPORTLAYERS", "PDFIMPORTLAYERS",
+35
View File
@@ -1025,6 +1025,7 @@ impl OpenCADStudio {
| "DWFOSNAP" | "DWFOSNAP"
| "DGNOSNAP" | "DGNOSNAP"
| "UOSNAP" | "UOSNAP"
| "FIELDDISPLAY"
| "PDFIMPORTMODE" | "PDFIMPORTMODE"
| "PDFIMPORTFILTER" | "PDFIMPORTFILTER"
| "PDFIMPORTLAYERS" | "PDFIMPORTLAYERS"
@@ -1232,6 +1233,40 @@ impl OpenCADStudio {
} }
return Some(self.finish_dispatch(cmd)); return Some(self.finish_dispatch(cmd));
} }
if name == "FIELDDISPLAY" {
let current = i16::from(crate::entities::field::display());
match value.as_deref().map(|v| v.trim().parse::<i16>().ok().filter(|v| (0..=1).contains(v))) {
Some(Some(mode)) => {
if current != mode {
crate::entities::field::set_display(mode == 1);
// A profile setting: redraw the fields of every open drawing.
for tab in &mut self.tabs {
let changes: Vec<_> = tab
.scene
.document
.entities()
.filter(|e| crate::entities::field::hosts_field(&tab.scene.document, e))
.map(|e| (e.common().handle, crate::scene::ChangeKind::Modified))
.collect();
if !changes.is_empty() {
tab.scene.bump_entities(&changes);
}
}
self.save_config();
}
}
Some(None) => {
self.command_line.push_error(crate::t!("Requires 0 or 1 only.").as_ref());
self.command_line.push_output(&format!("Enter new value for FIELDDISPLAY <{current}>:"));
self.pending_setvar = Some(name.clone());
}
None => {
self.command_line.push_output(&format!("Enter new value for FIELDDISPLAY <{current}>:"));
self.pending_setvar = Some(name.clone());
}
}
return Some(self.finish_dispatch(cmd));
}
if matches!(name.as_str(), "PDFOSNAP" | "DWFOSNAP" | "DGNOSNAP" | "UOSNAP") { if matches!(name.as_str(), "PDFOSNAP" | "DWFOSNAP" | "DGNOSNAP" | "UOSNAP") {
use crate::scene::model::pdf_vector as pv; use crate::scene::model::pdf_vector as pv;
use codec::entities::UnderlayType; use codec::entities::UnderlayType;
+42
View File
@@ -81,6 +81,7 @@ pub(super) const NAMES: &[&str] = &[
"ribbon_dropdown", "ribbon_dropdown",
"dialog_ok", "dialog_ok",
"attdef_dialog", "attdef_dialog",
"field_dialog",
"close_document", "close_document",
"toggle_properties", "toggle_properties",
"toggle_layers", "toggle_layers",
@@ -958,6 +959,8 @@ impl OpenCADStudio {
"align_below" => M::AlignBelow(on), "align_below" => M::AlignBelow(on),
"edit_value" => M::EditValue, "edit_value" => M::EditValue,
"dismiss" => M::DismissError, "dismiss" => M::DismissError,
"insert_field" if editing => M::EditInsertField,
"insert_field" => M::InsertField,
"x" => M::Coord(0, v.into()), "x" => M::Coord(0, v.into()),
"y" => M::Coord(1, v.into()), "y" => M::Coord(1, v.into()),
"z" => M::Coord(2, v.into()), "z" => M::Coord(2, v.into()),
@@ -975,6 +978,42 @@ impl OpenCADStudio {
_ => return Err(failure("invalid_value", "Unknown attdef dialog field")), _ => return Err(failure("invalid_value", "Unknown attdef dialog field")),
}) })
} }
// One field of the open Field dialog: `category=<index>`, `name=Date`,
// `date=yyyy-MM-dd`, `case=0..4`, `file=1|2|3`, `ext=0|1`, `size=0..2`,
// `sysvar=dimscale`, `diesel=…`, `named_type=<index>`, `named=<index>`,
// `prop=Area`, `select_object`, `formula=(1+2)*3`, `formula_format=<index>`,
// `precision=<index>`, `link_text=…`, `link_url=…`, `plot_scale=<index>`.
"field_dialog" => {
use crate::ui::window::field_dialog::{self as fd, FieldDialogMsg as F};
let value = string(req, "value")?;
let (key, v) = value.split_once('=').unwrap_or((value, ""));
let index = || v.parse::<usize>().map_err(|_| failure("invalid_value", "index expected"));
let known = |list: &[&'static str]| {
list.iter().copied().find(|n| n.eq_ignore_ascii_case(v)).ok_or_else(|| failure("invalid_value", "unknown name"))
};
Message::FieldDialog(match key {
"category" => F::Category(index()?),
"name" => F::Name(known(&fd::fields_of(0))?),
"date" => F::DateFormat(v.into()),
"case" => F::TextCase(index()?),
"file" => F::FileParts(v.parse::<u8>().map_err(|_| failure("invalid_value", "1|2|3"))?),
"ext" => F::FileExtension(v == "1"),
"size" => F::SizeUnit(index()?),
"sysvar" => F::SysVar(v.into()),
"diesel" => F::Diesel(v.into()),
"named_type" => F::NamedType(index()?),
"named" => F::Named(index()?),
"prop" => F::ObjectProp(known(&["Area", "Center", "Circumference", "Diameter", "EndPoint", "Length", "Radius", "StartPoint"])?),
"select_object" => F::SelectObject,
"formula" => F::Formula(v.into()),
"formula_format" => F::FormulaFormat(index()?),
"precision" => F::FormulaPrecision(index()?),
"link_text" => F::HyperlinkText(v.into()),
"link_url" => F::HyperlinkUrl(v.into()),
"plot_scale" => F::PlotScale(index()?),
_ => return Err(failure("invalid_value", "Unknown field dialog key")),
})
}
"ribbon_dropdown" => Message::ToggleRibbonDropdown(string(req, "value")?.into()), "ribbon_dropdown" => Message::ToggleRibbonDropdown(string(req, "value")?.into()),
"pdf_dialog_ok" => { "pdf_dialog_ok" => {
use crate::ui::window::pdf_dialogs::PdfDialogMsg; use crate::ui::window::pdf_dialogs::PdfDialogMsg;
@@ -1001,6 +1040,9 @@ impl OpenCADStudio {
Some(crate::app::ModalKind::AttDefEdit) => Message::AttdefDialog( Some(crate::app::ModalKind::AttDefEdit) => Message::AttdefDialog(
crate::ui::window::attdef_dialog::AttdefDialogMsg::EditOk, crate::ui::window::attdef_dialog::AttdefDialogMsg::EditOk,
), ),
Some(crate::app::ModalKind::Field) => Message::FieldDialog(
crate::ui::window::field_dialog::FieldDialogMsg::Ok,
),
_ => return Err(failure("no_dialog", "No dialog with an OK button is open")), _ => return Err(failure("no_dialog", "No dialog with an OK button is open")),
}, },
"close_document" => Message::TabClose(self.tabs[self.active_tab].id), "close_document" => Message::TabClose(self.tabs[self.active_tab].id),
+5
View File
@@ -501,6 +501,8 @@ pub(super) struct OpenCADStudio {
/// ATTDEF dialog and Edit Attribute Definition dialog; `None` while closed. /// ATTDEF dialog and Edit Attribute Definition dialog; `None` while closed.
attdef_dialog: Option<crate::ui::window::attdef_dialog::AttdefDialogState>, attdef_dialog: Option<crate::ui::window::attdef_dialog::AttdefDialogState>,
attdef_edit: Option<crate::ui::window::attdef_dialog::AttdefEditState>, attdef_edit: Option<crate::ui::window::attdef_dialog::AttdefEditState>,
/// Field dialog working copy; `None` while closed.
field_dialog: Option<crate::ui::window::field_dialog::FieldDialogState>,
point_cloud_color_map: Option<crate::ui::window::pdf_dialogs::PointCloudColorMapState>, point_cloud_color_map: Option<crate::ui::window::pdf_dialogs::PointCloudColorMapState>,
pc_section: Option<crate::ui::window::pdf_dialogs::PcSectionState>, pc_section: Option<crate::ui::window::pdf_dialogs::PcSectionState>,
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>, underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
@@ -1938,6 +1940,7 @@ pub enum ModalKind {
GeometricTolerance, GeometricTolerance,
AttDef, AttDef,
AttDefEdit, AttDefEdit,
Field,
DraftingSettings, DraftingSettings,
AutoConstrainSettings, AutoConstrainSettings,
LayerStateEditor, LayerStateEditor,
@@ -3891,6 +3894,7 @@ pub enum Message {
/// An edit in one of the PDF dialogs. /// An edit in one of the PDF dialogs.
PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg), PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg),
AttdefDialog(crate::ui::window::attdef_dialog::AttdefDialogMsg), AttdefDialog(crate::ui::window::attdef_dialog::AttdefDialogMsg),
FieldDialog(crate::ui::window::field_dialog::FieldDialogMsg),
/// A click or search in the Point Cloud Manager. /// A click or search in the Point Cloud Manager.
PcManager(crate::ui::window::pc_manager::PcManagerMsg), PcManager(crate::ui::window::pc_manager::PcManagerMsg),
PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>), PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
@@ -4086,6 +4090,7 @@ impl OpenCADStudio {
point_cloud_attach: None, point_cloud_attach: None,
attdef_dialog: None, attdef_dialog: None,
attdef_edit: None, attdef_edit: None,
field_dialog: None,
point_cloud_color_map: None, point_cloud_color_map: None,
pc_section: None, pc_section: None,
underlay_layers: None, underlay_layers: None,
+6
View File
@@ -406,6 +406,10 @@ pub struct UserSettings {
/// IMAGEFRAME of the profile: used by new drawings and by drawings that /// IMAGEFRAME of the profile: used by new drawings and by drawings that
/// carry no image variables of their own. /// carry no image variables of their own.
pub image_frame: i16, pub image_frame: i16,
/// The Attribute Definition dialog's "Specify on-screen" choice.
pub attdef_on_screen: bool,
/// FIELDDISPLAY: fields show on a gray background.
pub field_display: bool,
/// Nested-copy symbol handling: false inserts, true binds. /// Nested-copy symbol handling: false inserts, true binds.
pub ncopy_bind: bool, pub ncopy_bind: bool,
/// Last Options page; unknown saved names fall back without rejecting the config. /// Last Options page; unknown saved names fall back without rejecting the config.
@@ -784,6 +788,8 @@ impl Default for UserSettings {
right_click_hold_ms: 250, right_click_hold_ms: 250,
grip_object_limit: DEFAULT_GRIP_OBJECT_LIMIT, grip_object_limit: DEFAULT_GRIP_OBJECT_LIMIT,
image_frame: 1, image_frame: 1,
attdef_on_screen: true,
field_display: true,
ncopy_bind: false, ncopy_bind: false,
cursor_type: CursorType::Crosshair, cursor_type: CursorType::Crosshair,
crosshair_color: None, crosshair_color: None,
+4
View File
@@ -676,6 +676,8 @@ impl OpenCADStudio {
right_click_hold_ms: self.right_click_hold_ms, right_click_hold_ms: self.right_click_hold_ms,
grip_object_limit: self.grip_object_limit, grip_object_limit: self.grip_object_limit,
image_frame: crate::scene::frame::profile_image_mode(), image_frame: crate::scene::frame::profile_image_mode(),
attdef_on_screen: crate::modules::draw::draw::attdef::session().on_screen,
field_display: crate::entities::field::display(),
ncopy_bind: self.ncopy_bind, ncopy_bind: self.ncopy_bind,
cursor_type: self.cursor_type, cursor_type: self.cursor_type,
crosshair_color: self.crosshair_color, crosshair_color: self.crosshair_color,
@@ -774,6 +776,8 @@ impl OpenCADStudio {
self.right_click_hold_ms = super::super::settings::clamp_right_click_hold_ms(s.right_click_hold_ms); self.right_click_hold_ms = super::super::settings::clamp_right_click_hold_ms(s.right_click_hold_ms);
self.grip_object_limit = s.grip_object_limit.clamp(0, 32767); self.grip_object_limit = s.grip_object_limit.clamp(0, 32767);
crate::scene::frame::set_profile_image_mode(s.image_frame); crate::scene::frame::set_profile_image_mode(s.image_frame);
crate::modules::draw::draw::attdef::session().on_screen = s.attdef_on_screen;
crate::entities::field::set_display(s.field_display);
self.ncopy_bind = s.ncopy_bind; self.ncopy_bind = s.ncopy_bind;
self.cursor_type = s.cursor_type; self.cursor_type = s.cursor_type;
self.crosshair_color = s.crosshair_color; self.crosshair_color = s.crosshair_color;
+5
View File
@@ -1817,6 +1817,7 @@ impl OpenCADStudio {
Message::PdfDialog(message) => self.update_pdf_dialog(message), Message::PdfDialog(message) => self.update_pdf_dialog(message),
Message::AttdefDialog(message) => self.on_attdef_dialog(message), Message::AttdefDialog(message) => self.on_attdef_dialog(message),
Message::FieldDialog(message) => self.on_field_dialog(message),
Message::PcManager(message) => self.update_pc_manager(message), Message::PcManager(message) => self.update_pc_manager(message),
Message::RibbonSelectTab(idx) => { Message::RibbonSelectTab(idx) => {
self.ribbon.select(idx); self.ribbon.select(idx);
@@ -8520,6 +8521,10 @@ impl OpenCADStudio {
} }
Message::CloseModal => { Message::CloseModal => {
if self.active_modal == Some(super::ModalKind::Field) {
self.close_field_dialog();
return Task::none();
}
if matches!( if matches!(
self.active_modal, self.active_modal,
Some(super::ModalKind::AttDef | super::ModalKind::AttDefEdit) Some(super::ModalKind::AttDef | super::ModalKind::AttDefEdit)
+5
View File
@@ -40,6 +40,7 @@ impl OpenCADStudio {
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(), Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
Some(K::AttDef) => crate::t!("Attribute Definition").into_owned(), Some(K::AttDef) => crate::t!("Attribute Definition").into_owned(),
Some(K::AttDefEdit) => crate::t!("Edit Attribute Definition").into_owned(), Some(K::AttDefEdit) => crate::t!("Edit Attribute Definition").into_owned(),
Some(K::Field) => crate::t!("Field").into_owned(),
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(), Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
Some(K::AutoConstrainSettings) => crate::t!("Constraint Settings").into_owned(), Some(K::AutoConstrainSettings) => crate::t!("Constraint Settings").into_owned(),
Some(K::LayerStateEditor) => crate::tr!("modal", "edit-layer-state"), Some(K::LayerStateEditor) => crate::tr!("modal", "edit-layer-state"),
@@ -579,6 +580,10 @@ impl OpenCADStudio {
let state = self.attdef_dialog.as_ref()?; let state = self.attdef_dialog.as_ref()?;
sized_flow(ex, 720, 520, |flow| crate::ui::window::attdef_dialog::view(state, flow)) sized_flow(ex, 720, 520, |flow| crate::ui::window::attdef_dialog::view(state, flow))
} }
super::super::ModalKind::Field => {
let state = self.field_dialog.as_ref()?;
sized_flow(ex, 760, 660, |flow| crate::ui::window::field_dialog::view(state, flow))
}
super::super::ModalKind::AttDefEdit => { super::super::ModalKind::AttDefEdit => {
let state = self.attdef_edit.as_ref()?; let state = self.attdef_edit.as_ref()?;
sized_flow(ex, 460, 220, |flow| crate::ui::window::attdef_dialog::view_edit(state, flow)) sized_flow(ex, 460, 220, |flow| crate::ui::window::attdef_dialog::view_edit(state, flow))
+6
View File
@@ -2634,6 +2634,12 @@ pub trait CadCommand: Send {
/// Resume the command with collected rich text. /// Resume the command with collected rich text.
fn on_editor_text(&mut self, _value: String) {} fn on_editor_text(&mut self, _value: String) {}
/// A field (code and referenced objects) to attach to the entity this
/// command commits.
fn text_field(&self) -> Option<(String, Vec<Handle>)> {
None
}
fn on_editor_display_height(&mut self, _height: f64) {} fn on_editor_display_height(&mut self, _height: f64) {}
/// Called when the user clicks and `needs_entity_pick()` is true. /// Called when the user clicks and `needs_entity_pick()` is true.
+290 -7
View File
@@ -11,16 +11,35 @@ use codec::fields::FieldContext;
use codec::types::Handle; use codec::types::Handle;
use codec::CadDocument; use codec::CadDocument;
/// OCS's environment provider for field evaluation. /// OCS's environment provider for field evaluation. With the document at
struct OcsFieldContext; /// hand it also answers the system variables the engine asks the host for.
struct OcsFieldContext<'a>(Option<&'a CadDocument>);
impl FieldContext for OcsFieldContext { impl FieldContext for OcsFieldContext<'_> {
fn now_julian(&self) -> f64 { fn now_julian(&self) -> f64 {
// Unix epoch is Julian Day 2440587.5. // Fields show local time. Unix epoch is Julian Day 2440587.5.
epoch_secs() as f64 / 86_400.0 + 2_440_587.5 now_utc_julian() + utc_offset_days()
}
fn file_times(&self) -> Option<(f64, f64)> {
#[cfg(not(target_arch = "wasm32"))]
{
let meta = std::fs::metadata(self.0?.source_path.as_deref()?).ok()?;
let julian = |t: std::time::SystemTime| -> Option<f64> {
let secs = t.duration_since(std::time::UNIX_EPOCH).ok()?.as_secs_f64();
Some(secs / 86_400.0 + 2_440_587.5 + utc_offset_days())
};
return Some((julian(meta.created().ok()?)?, julian(meta.modified().ok()?)?));
}
#[cfg(target_arch = "wasm32")]
None
} }
fn login(&self) -> Option<String> { fn login(&self) -> Option<String> {
// The account's display name, as the reference shows it.
if let Some(name) = display_name() {
return Some(name);
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
{ {
for var in ["USER", "LOGNAME", "USERNAME"] { for var in ["USER", "LOGNAME", "USERNAME"] {
@@ -34,6 +53,24 @@ impl FieldContext for OcsFieldContext {
None None
} }
fn getvar(&self, name: &str) -> Option<String> {
self.0.and_then(|document| sysvar(document, name))
}
fn file_size(&self) -> Option<u64> {
#[cfg(not(target_arch = "wasm32"))]
{
let path = self.0?.source_path.as_deref()?;
return std::fs::metadata(path).ok().map(|m| m.len());
}
#[cfg(target_arch = "wasm32")]
None
}
fn date_locale(&self) -> codec::fields::DateLocale {
os_date_locale()
}
fn getenv(&self, name: &str) -> Option<String> { fn getenv(&self, name: &str) -> Option<String> {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
{ {
@@ -50,7 +87,7 @@ impl FieldContext for OcsFieldContext {
/// Re-evaluate the field hosted by entity `host` (usually an MTEXT), or `None` /// Re-evaluate the field hosted by entity `host` (usually an MTEXT), or `None`
/// to keep the cached text. Thin wrapper over the library engine. /// to keep the cached text. Thin wrapper over the library engine.
pub fn resolve(document: &CadDocument, host: Handle) -> Option<String> { pub fn resolve(document: &CadDocument, host: Handle) -> Option<String> {
codec::fields::resolve(document, host, &OcsFieldContext) codec::fields::resolve(document, host, &OcsFieldContext(Some(document)))
} }
pub fn resolve_handle( pub fn resolve_handle(
@@ -58,7 +95,88 @@ pub fn resolve_handle(
field: Handle, field: Handle,
host: Handle, host: Handle,
) -> Option<String> { ) -> Option<String> {
codec::fields::resolve_handle(document, field, host, &OcsFieldContext) codec::fields::resolve_handle(document, field, host, &OcsFieldContext(Some(document)))
}
/// Evaluate one field code (`\AcVar Date \f "yyyy-MM-dd"`, or the `%<…>%`
/// form) for a preview; `None` when it cannot be evaluated.
pub fn evaluate(
document: &CadDocument,
code: &str,
objects: &[Handle],
host: Option<Handle>,
) -> Option<String> {
codec::fields::evaluate_code(document, code, objects, host, &OcsFieldContext(Some(document)))
}
/// The system variables the Field dialog lists, in its order (lower case).
pub const SYSVARS: &[&str] = &[
"aunits", "auprec", "celtscale", "chamfera", "chamferb", "chamferc", "chamferd",
"clayer", "dimscale", "dimtxt", "dwgname", "dwgprefix", "elevation", "filletrad",
"insbase", "insunits", "limmax", "limmin", "ltscale", "lunits", "luprec",
"measurement", "mirrtext", "pdmode", "pdsize", "textsize", "textstyle", "thickness",
"useri1", "useri2", "useri3", "useri4", "useri5", "userr1", "userr2", "userr3",
"userr4", "userr5",
];
/// Real system variables show six decimals, as the reference shows them
/// (TEXTSIZE 2.500000).
fn num(v: f64) -> String {
format!("{v:.6}")
}
/// A system variable's value as a field shows it.
pub fn sysvar(document: &CadDocument, name: &str) -> Option<String> {
let h = &document.header;
let path = document.source_path.as_deref().unwrap_or("");
let (folder, file) = match path.rfind(['/', '\\']) {
Some(i) => (&path[..=i], &path[i + 1..]),
None => ("", path),
};
Some(match name.trim().to_ascii_lowercase().as_str() {
"aunits" => h.angular_unit_format.to_string(),
"auprec" => h.angular_unit_precision.to_string(),
"celtscale" => num(h.current_entity_linetype_scale),
"chamfera" => num(h.chamfer_distance_a),
"chamferb" => num(h.chamfer_distance_b),
"chamferc" => num(h.chamfer_length),
"chamferd" => num(h.chamfer_angle),
"clayer" => h.current_layer_name.clone(),
"dimscale" => num(h.dim_scale),
"dimtxt" => num(h.dim_text_height),
"dwgname" => if file.is_empty() { "Drawing1.dwg".to_string() } else { file.to_string() },
"dwgprefix" => folder.to_string(),
"elevation" => num(h.elevation),
"filletrad" => num(h.fillet_radius),
"insbase" => {
let p = h.model_space_insertion_base;
format!("{},{},{}", num(p.x), num(p.y), num(p.z))
}
"insunits" => h.insertion_units.to_string(),
"limmax" => format!("{},{}", num(h.model_space_limits_max.x), num(h.model_space_limits_max.y)),
"limmin" => format!("{},{}", num(h.model_space_limits_min.x), num(h.model_space_limits_min.y)),
"ltscale" => num(h.linetype_scale),
"lunits" => h.linear_unit_format.to_string(),
"luprec" => h.linear_unit_precision.to_string(),
"measurement" => h.measurement.to_string(),
"mirrtext" => i16::from(h.mirror_text).to_string(),
"pdmode" => h.point_display_mode.to_string(),
"pdsize" => num(h.point_display_size),
"textsize" => num(h.text_height),
"textstyle" => h.current_text_style_name.clone(),
"thickness" => num(h.thickness),
"useri1" => h.user_int1.to_string(),
"useri2" => h.user_int2.to_string(),
"useri3" => h.user_int3.to_string(),
"useri4" => h.user_int4.to_string(),
"useri5" => h.user_int5.to_string(),
"userr1" => num(h.user_real1),
"userr2" => num(h.user_real2),
"userr3" => num(h.user_real3),
"userr4" => num(h.user_real4),
"userr5" => num(h.user_real5),
_ => return None,
})
} }
/// Seconds since the Unix epoch. Native uses the system clock; wasm uses the JS /// Seconds since the Unix epoch. Native uses the system clock; wasm uses the JS
@@ -77,3 +195,168 @@ fn epoch_secs() -> i64 {
(js_sys::Date::now() / 1000.0) as i64 (js_sys::Date::now() / 1000.0) as i64
} }
} }
/// Attach `field` (its code and referenced objects) to `host`, showing
/// `value`; `None` removes the host's field.
pub fn set_text_field(
document: &mut CadDocument,
host: Handle,
field: Option<(String, Vec<Handle>)>,
value: &str,
) {
match field {
Some((code, objects)) => {
let mut child = codec::fields::NewField::new(code, value);
child.objects = objects;
document.set_text_field(host, "%<\\_FldIdx 0>%", vec![child]);
}
None => {
document.remove_text_field(host);
}
}
}
/// Month and day names and the regional date / time pictures, as date
/// fields show them: the operating system's regional settings.
fn os_date_locale() -> codec::fields::DateLocale {
#[cfg(target_os = "windows")]
{
use windows_sys::Win32::Globalization::GetLocaleInfoEx;
let info = |kind: u32| -> Option<String> {
let mut buffer = [0u16; 128];
// A null locale name is the user's default locale.
let len = unsafe { GetLocaleInfoEx(std::ptr::null(), kind, buffer.as_mut_ptr(), buffer.len() as i32) };
(len > 1).then(|| String::from_utf16_lossy(&buffer[..len as usize - 1]))
};
let mut locale = codec::fields::DateLocale::default();
for k in 0..12u32 {
if let Some(v) = info(0x38 + k) {
locale.months[k as usize] = v; // LOCALE_SMONTHNAME1..
}
if let Some(v) = info(0x44 + k) {
locale.months_abbr[k as usize] = v; // LOCALE_SABBREVMONTHNAME1..
}
}
// LOCALE_SDAYNAME1 is Monday; the codec lists Sunday first.
for k in 0..7u32 {
let slot = ((k + 1) % 7) as usize;
if let Some(v) = info(0x2A + k) {
locale.days[slot] = v;
}
if let Some(v) = info(0x31 + k) {
locale.days_abbr[slot] = v;
}
}
if let Some(v) = info(0x28) {
locale.am = v; // LOCALE_S1159
}
if let Some(v) = info(0x29) {
locale.pm = v; // LOCALE_S2359
}
if let Some(v) = info(0x1F) {
locale.short_date = v; // LOCALE_SSHORTDATE
}
if let Some(v) = info(0x20) {
locale.long_date = v; // LOCALE_SLONGDATE
}
if let Some(v) = info(0x1003) {
locale.long_time = v; // LOCALE_STIMEFORMAT
}
locale
}
#[cfg(not(target_os = "windows"))]
codec::fields::DateLocale::default()
}
/// FIELDDISPLAY: whether fields show on a gray background (not plotted).
/// A profile setting, so one value for every drawing.
static FIELD_DISPLAY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
pub fn display() -> bool {
FIELD_DISPLAY.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn set_display(on: bool) {
FIELD_DISPLAY.store(on, std::sync::atomic::Ordering::Relaxed);
}
/// The gray the reference draws behind a field.
pub const BACKGROUND: [f32; 4] = [203.0 / 255.0, 203.0 / 255.0, 203.0 / 255.0, 1.0];
/// Whether `entity` hosts a field: its extension dictionary has ACAD_FIELD.
pub fn hosts_field(document: &CadDocument, entity: &codec::entities::EntityType) -> bool {
let Some(xdict) = entity.common().xdictionary_handle else {
return false;
};
matches!(
document.objects.get(&xdict),
Some(codec::objects::ObjectType::Dictionary(d)) if d.get("ACAD_FIELD").is_some()
)
}
fn now_utc_julian() -> f64 {
epoch_secs() as f64 / 86_400.0 + 2_440_587.5
}
/// Local time minus UTC, in days (the zone's current offset).
pub fn utc_offset_days() -> f64 {
#[cfg(target_os = "windows")]
{
use windows_sys::Win32::Foundation::FILETIME;
use windows_sys::Win32::Storage::FileSystem::FileTimeToLocalFileTime;
// 100 ns ticks since 1601-01-01.
let ticks = (epoch_secs() as u64 + 11_644_473_600) * 10_000_000;
let utc = FILETIME { dwLowDateTime: ticks as u32, dwHighDateTime: (ticks >> 32) as u32 };
let mut local = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
if unsafe { FileTimeToLocalFileTime(&utc, &mut local) } != 0 {
let local = ((local.dwHighDateTime as u64) << 32) | local.dwLowDateTime as u64;
return (local as i64 - ticks as i64) as f64 / 864_000_000_000.0;
}
0.0
}
#[cfg(not(target_os = "windows"))]
0.0
}
/// A DWG stores its header dates in universal time only; the local TDCREATE
/// and TDUPDATE come from the zone, as the reference derives them on load.
pub fn local_header_dates(document: &mut CadDocument) {
let h = &mut document.header;
let offset = utc_offset_days();
if h.universal_create_date_julian != 0.0 && h.create_date_julian == h.universal_create_date_julian {
h.create_date_julian = h.universal_create_date_julian + offset;
}
if h.universal_update_date_julian != 0.0 && h.update_date_julian == h.universal_update_date_julian {
h.update_date_julian = h.universal_update_date_julian + offset;
}
}
/// Stamp the header's update date (and the creation date of a new drawing)
/// in local and universal time before a save.
pub fn stamp_save_dates(document: &mut CadDocument) {
let utc = now_utc_julian();
let local = utc + utc_offset_days();
let h = &mut document.header;
if h.create_date_julian == 0.0 && h.universal_create_date_julian == 0.0 {
h.create_date_julian = local;
h.universal_create_date_julian = utc;
}
h.update_date_julian = local;
h.universal_update_date_julian = utc;
}
/// The signed-in account's display name, when it has one.
fn display_name() -> Option<String> {
#[cfg(target_os = "windows")]
{
use windows_sys::Win32::Security::Authentication::Identity::{GetUserNameExW, NameDisplay};
let mut buffer = [0u16; 256];
let mut len = buffer.len() as u32;
if unsafe { GetUserNameExW(NameDisplay, buffer.as_mut_ptr(), &mut len) } && len > 0 {
return Some(String::from_utf16_lossy(&buffer[..len as usize])).filter(|n| !n.trim().is_empty());
}
None
}
#[cfg(not(target_os = "windows"))]
None
}
+4
View File
@@ -723,6 +723,7 @@ async fn load_web_bytes(
} }
fix_viewport_status_flags(&mut doc); fix_viewport_status_flags(&mut doc);
fix_current_style_names(&mut doc); fix_current_style_names(&mut doc);
crate::entities::field::local_header_dates(&mut doc);
progress.set(crate::app::OPEN_PHASE_CACHING, 7000, 0, 1); progress.set(crate::app::OPEN_PHASE_CACHING, 7000, 0, 1);
let dropped = purge_corrupt_entities(&mut doc); let dropped = purge_corrupt_entities(&mut doc);
if !recovery_mode && dropped > 0 { if !recovery_mode && dropped > 0 {
@@ -764,6 +765,7 @@ pub fn load_bytes(name: &str, bytes: Vec<u8>) -> Result<CadDocument, String> {
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
fix_viewport_status_flags(&mut doc); fix_viewport_status_flags(&mut doc);
fix_current_style_names(&mut doc); fix_current_style_names(&mut doc);
crate::entities::field::local_header_dates(&mut doc);
Ok(doc) Ok(doc)
} }
"dxf" => { "dxf" => {
@@ -775,6 +777,7 @@ pub fn load_bytes(name: &str, bytes: Vec<u8>) -> Result<CadDocument, String> {
fix_dxf_layout_plot_settings(&mut doc); fix_dxf_layout_plot_settings(&mut doc);
fix_viewport_status_flags(&mut doc); fix_viewport_status_flags(&mut doc);
fix_current_style_names(&mut doc); fix_current_style_names(&mut doc);
crate::entities::field::local_header_dates(&mut doc);
Ok(doc) Ok(doc)
} }
_ => Err(format!("Unsupported file format: .{ext}")), _ => Err(format!("Unsupported file format: .{ext}")),
@@ -1040,6 +1043,7 @@ fn finalize_loaded_outcome(
} }
fix_viewport_status_flags(doc); fix_viewport_status_flags(doc);
fix_current_style_names(doc); fix_current_style_names(doc);
crate::entities::field::local_header_dates(doc);
resolve_raster_image_paths(doc, path.parent()); resolve_raster_image_paths(doc, path.parent());
doc.source_path = Some(path.to_string_lossy().into_owned()); doc.source_path = Some(path.to_string_lossy().into_owned());
Ok(outcome) Ok(outcome)
+38
View File
@@ -217,6 +217,44 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"The tag may not contain spaces." => Some(("insert", "attdef-tag-spaces")), "The tag may not contain spaces." => Some(("insert", "attdef-tag-spaces")),
"Value must be positive and nonzero." => Some(("insert", "attdef-positive")), "Value must be positive and nonzero." => Some(("insert", "attdef-positive")),
"Requires valid numeric angle or second point." => Some(("insert", "attdef-angle")), "Requires valid numeric angle or second point." => Some(("insert", "attdef-angle")),
"Field" => Some(("insert", "field-title")),
"Category" => Some(("insert", "field-category")),
"Date format" => Some(("insert", "field-date-format")),
"Examples" => Some(("insert", "field-examples")),
"Display file extension" => Some(("insert", "field-file-extension")),
"Filename only" => Some(("insert", "field-filename-only")),
"Path only" => Some(("insert", "field-path-only")),
"Path and filename" => Some(("insert", "field-path-filename")),
"Bytes" => Some(("insert", "field-bytes")),
"Kilobytes" => Some(("insert", "field-kilobytes")),
"Megabytes" => Some(("insert", "field-megabytes")),
"System variable" => Some(("insert", "field-system-variable")),
"Diesel expression" => Some(("insert", "field-diesel")),
"Named object type" => Some(("insert", "field-named-type")),
"Object type" => Some(("insert", "field-object-type")),
"Select object" => Some(("insert", "field-select-object")),
"Field expression" => Some(("insert", "field-expression")),
"Inserts a field: text that updates from the drawing, the date or another source." => Some(("insert", "field-help")),
"Linked" => Some(("insert", "field-cat-linked")),
"Current precision" => Some(("insert", "field-current-precision")),
"Evaluate" => Some(("insert", "field-evaluate")),
"Text to display" => Some(("insert", "field-text-to-display")),
"Address" => Some(("insert", "field-address")),
"Select a file to link" => Some(("insert", "field-select-link-file")),
"Current units" => Some(("insert", "field-current-units")),
"Use scale name" => Some(("insert", "field-use-scale-name")),
"Date & Time" => Some(("insert", "field-cat-date")),
"Document" => Some(("insert", "field-cat-document")),
"Other" => Some(("insert", "field-cat-other")),
"Uppercase" => Some(("insert", "field-case-upper")),
"Lowercase" => Some(("insert", "field-case-lower")),
"First capital" => Some(("insert", "field-case-first")),
"Title case" => Some(("insert", "field-case-title")),
"Dimstyle" => Some(("insert", "field-named-dimstyle")),
"Tablestyle" => Some(("insert", "field-named-tablestyle")),
"Textstyle" => Some(("insert", "field-named-textstyle")),
"EndPoint" => Some(("insert", "field-prop-endpoint")),
"StartPoint" => Some(("insert", "field-prop-startpoint")),
"Stylization" => Some(("properties", "stylization")), "Stylization" => Some(("properties", "stylization")),
"Color scheme" => Some(("properties", "color-scheme")), "Color scheme" => Some(("properties", "color-scheme")),
"Scan Colors" => Some(("properties", "scan-colors")), "Scan Colors" => Some(("properties", "scan-colors")),
+192
View File
@@ -0,0 +1,192 @@
// FIELD — places the field chosen in the Field dialog as a multi-line text,
// and the object pick behind the dialog's "Select object" button.
use codec::entities::mtext::AttachmentPoint;
use codec::entities::MText;
use codec::types::{Handle, Vector3};
use codec::EntityType;
use glam::DVec3;
use crate::command::{CadCommand, CmdResult, InputKind, WorkingPlane};
/// A field to attach to the entity a command commits: the field code and the
/// objects it refers to.
pub type TextField = (String, Vec<Handle>);
#[derive(Clone, Copy, PartialEq)]
enum Step {
Point,
Height,
Justify,
}
/// `Specify start point or [Height/Justify]:` → an MTEXT holding the field.
pub struct FieldPlaceCommand {
step: Step,
value: String,
field: TextField,
style: String,
height: f64,
attachment: AttachmentPoint,
plane: WorkingPlane,
}
impl FieldPlaceCommand {
pub fn new(value: String, field: TextField, style: String, height: f64) -> Self {
Self {
step: Step::Point,
value,
field,
style,
height,
attachment: AttachmentPoint::TopLeft,
plane: WorkingPlane::default(),
}
}
}
fn short(v: f64) -> String {
let t = format!("{v:.4}");
t.trim_end_matches('0').trim_end_matches('.').to_string()
}
impl CadCommand for FieldPlaceCommand {
fn name(&self) -> &'static str {
"FIELD"
}
fn set_working_plane(&mut self, plane: WorkingPlane) {
self.plane = plane;
}
fn prompt(&self) -> String {
match self.step {
Step::Point => "Specify start point or [Height/Justify]:".into(),
Step::Height => format!("Specify height <{}>:", short(self.height)),
Step::Justify => "Enter justification [TL/TC/TR/ML/MC/MR/BL/BC/BR] <TL>:".into(),
}
}
fn input_kind(&self) -> InputKind {
match self.step {
Step::Justify => InputKind::SingleToken,
_ => InputKind::Point,
}
}
fn point_step_accepts_keywords(&self) -> bool {
self.step == Step::Point
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
let word = text.trim().trim_start_matches('_').to_ascii_uppercase();
match self.step {
Step::Point => match word.as_str() {
"H" | "HEIGHT" => self.step = Step::Height,
"J" | "JUSTIFY" => self.step = Step::Justify,
"" => return Some(CmdResult::ReportError("Point or option keyword required.".into())),
_ => return Some(CmdResult::ReportError("Point or option keyword required.".into())),
},
Step::Height => {
if word.is_empty() {
self.step = Step::Point;
return Some(CmdResult::NeedPoint);
}
match word.parse::<f64>() {
Ok(v) if v > 0.0 => {
self.height = v;
self.step = Step::Point;
}
Ok(_) => return Some(CmdResult::ReportError("Value must be positive and nonzero.".into())),
Err(_) => return Some(CmdResult::ReportError("Requires numeric distance or two points.".into())),
}
}
Step::Justify => {
self.attachment = match word.as_str() {
"" | "TL" => AttachmentPoint::TopLeft,
"TC" => AttachmentPoint::TopCenter,
"TR" => AttachmentPoint::TopRight,
"ML" => AttachmentPoint::MiddleLeft,
"MC" => AttachmentPoint::MiddleCenter,
"MR" => AttachmentPoint::MiddleRight,
"BL" => AttachmentPoint::BottomLeft,
"BC" => AttachmentPoint::BottomCenter,
"BR" => AttachmentPoint::BottomRight,
_ => return Some(CmdResult::ReportError("Invalid option keyword.".into())),
};
self.step = Step::Point;
}
}
Some(CmdResult::NeedPoint)
}
fn on_point(&mut self, pt: DVec3) -> CmdResult {
if self.step == Step::Height {
return CmdResult::NeedPoint;
}
let p = self.plane.to_local(pt);
let mut mtext = MText::new();
mtext.value = self.value.clone();
mtext.insertion_point = Vector3::new(p.x, p.y, p.z);
mtext.height = self.height;
mtext.style = self.style.clone();
mtext.attachment_point = self.attachment;
// No defined width: the field text never wraps.
mtext.rectangle_width = 0.0;
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::MText(mtext)))
}
fn on_enter(&mut self) -> CmdResult {
match self.step {
Step::Point => CmdResult::ReportError("Point or option keyword required.".into()),
_ => self.on_text_input("").unwrap_or(CmdResult::NeedPoint),
}
}
fn text_field(&self) -> Option<TextField> {
Some(self.field.clone())
}
}
/// The Field dialog's "Select object": one object is picked and handed back
/// (`_FIELD_OBJECT <handle>`); Esc returns without one.
pub struct FieldObjectPickCommand;
impl CadCommand for FieldObjectPickCommand {
fn name(&self) -> &'static str {
"FIELD"
}
fn prompt(&self) -> String {
"Select object:".into()
}
fn needs_entity_pick(&self) -> bool {
true
}
fn entity_pick_highlights_hover(&self) -> bool {
true
}
fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult {
if handle.is_null() {
return CmdResult::NeedPoint;
}
CmdResult::Dispatch(format!("_FIELD_OBJECT {:X}", handle.value()))
}
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
CmdResult::NeedPoint
}
fn on_enter(&mut self) -> CmdResult {
CmdResult::Dispatch("_FIELD_OBJECT".into())
}
fn on_escape(&mut self) -> CmdResult {
CmdResult::Dispatch("_FIELD_OBJECT".into())
}
}
inventory::submit!(crate::command::CommandRegistration { names: &["FIELD"] });
+1
View File
@@ -7,6 +7,7 @@ pub mod angular_dim;
pub mod data_extract; pub mod data_extract;
pub mod data_link; pub mod data_link;
pub mod ddedit; pub mod ddedit;
pub mod field_cmd;
pub mod diameter_dim; pub mod diameter_dim;
pub mod dim_baseline; pub mod dim_baseline;
pub mod dim_continue; pub mod dim_continue;
+7
View File
@@ -218,6 +218,8 @@ pub struct AttdefSpec {
/// Multi-line boundary width (0 = none). /// Multi-line boundary width (0 = none).
pub boundary_width: f64, pub boundary_width: f64,
pub line_spacing: f64, pub line_spacing: f64,
/// A field the default value comes from: its code and referenced objects.
pub field: Option<(String, Vec<codec::Handle>)>,
} }
impl AttdefSpec { impl AttdefSpec {
@@ -447,6 +449,7 @@ impl AttdefCommand {
oblique_angle, oblique_angle,
boundary_width: 0.0, boundary_width: 0.0,
line_spacing: 1.0, line_spacing: 1.0,
field: None,
}, },
lines: Vec::new(), lines: Vec::new(),
styles, styles,
@@ -952,6 +955,10 @@ impl CadCommand for AttdefPlaceCommand {
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
Some(cross_preview(pt, self.plane)) Some(cross_preview(pt, self.plane))
} }
fn text_field(&self) -> Option<(String, Vec<codec::Handle>)> {
self.spec.field.clone()
}
} }
/// A small cross at the cursor while a placement point is asked. /// A small cross at the cursor while a placement point is asked.
+53
View File
@@ -1408,6 +1408,59 @@ pub fn tessellate(
// also carries the glyph quads built above. // also carries the glyph quads built above.
if bins.is_empty() { if bins.is_empty() {
let mut wires: Vec<WireModel> = Vec::new(); let mut wires: Vec<WireModel> = Vec::new();
// FIELDDISPLAY: a field shows on a gray box behind its
// glyphs, on screen only.
if text_aabb != WireModel::UNBOUNDED_AABB
&& crate::entities::field::display()
&& crate::entities::field::hosts_field(document, entity)
{
let corner_groups = match entity {
EntityType::MText(m) => {
let rotation = stroke_groups
.iter()
.find_map(|group| group.run.as_ref().map(|run| run.rotation as f64))
.unwrap_or(m.rotation);
oriented_mtext_corner_groups(&sdf_verts, m, rotation, 0.0, anno)
}
EntityType::Text(t) => vec![oriented_text_corners(
&sdf_verts,
[t.insertion_point.x, t.insertion_point.y],
t.rotation,
0.0,
)],
EntityType::AttributeEntity(a) => vec![oriented_text_corners(
&sdf_verts,
[a.insertion_point.x, a.insertion_point.y],
a.rotation,
0.0,
)],
_ => Vec::new(),
};
let mut ft = Vec::with_capacity(6 * corner_groups.len());
let mut ftl = Vec::with_capacity(6 * corner_groups.len());
for corners in &corner_groups {
for &k in &[0usize, 1, 2, 0, 2, 3] {
let (h, lo) = split_ds_xyz(corners[k][0], corners[k][1], elev_v);
ft.push(h);
ftl.push(lo);
}
}
if !ft.is_empty() {
wires.push(WireModel {
display_visible: true,
plot_visible: false,
name: name.clone(),
color: crate::entities::field::BACKGROUND,
selected,
line_weight_px,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
fill_tris: ft,
fill_tris_low: ftl,
..Default::default()
});
}
}
// MTEXT background and frame follow the glyph bounds. // MTEXT background and frame follow the glyph bounds.
if text_aabb != WireModel::UNBOUNDED_AABB { if text_aabb != WireModel::UNBOUNDED_AABB {
if let EntityType::MText(m) = entity { if let EntityType::MText(m) = entity {
+1
View File
@@ -2533,6 +2533,7 @@ impl super::Scene {
} }
} }
crate::io::xref::strip_resolved_xref_content(&mut document); crate::io::xref::strip_resolved_xref_content(&mut document);
crate::entities::field::stamp_save_dates(&mut document);
document document
} }
+11 -3
View File
@@ -34,6 +34,9 @@ pub enum AttdefDialogMsg {
PickHeight, PickHeight,
PickRotation, PickRotation,
PickWidth, PickWidth,
/// The ƒ button: pick a field for the Default value.
InsertField,
EditInsertField,
/// Edit the default value in the multi-line text editor. /// Edit the default value in the multi-line text editor.
EditValue, EditValue,
DismissError, DismissError,
@@ -76,6 +79,8 @@ pub struct AttdefDialogState {
pub can_align_below: bool, pub can_align_below: bool,
/// Validation message shown above the buttons. /// Validation message shown above the buttons.
pub error: Option<String>, pub error: Option<String>,
/// The field the Default value shows, when one was inserted.
pub field: Option<(String, Vec<codec::Handle>)>,
} }
/// The Edit Attribute Definition dialog's fields. /// The Edit Attribute Definition dialog's fields.
@@ -87,6 +92,10 @@ pub struct AttdefEditState {
pub default: String, pub default: String,
pub constant: bool, pub constant: bool,
pub error: Option<String>, pub error: Option<String>,
pub field: Option<(String, Vec<codec::Handle>)>,
/// The Default was retyped or a field inserted: the stored field is
/// replaced on Apply.
pub field_changed: bool,
} }
fn msg(m: AttdefDialogMsg) -> Message { fn msg(m: AttdefDialogMsg) -> Message {
@@ -248,8 +257,7 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
Some(AttdefDialogMsg::EditValue), Some(AttdefDialogMsg::EditValue),
)); ));
} }
// No field picker in this application yet. value.push(pick(text("fx").size(12).into(), t!("Insert field").into_owned(), Some(AttdefDialogMsg::InsertField)))
value.push(pick(text("fx").size(12).into(), t!("Insert field").into_owned(), None))
} }
.spacing(6) .spacing(6)
.into(), .into(),
@@ -381,7 +389,7 @@ pub fn view_edit<'a>(state: &'a AttdefEditState, sizing: crate::ui::modal::Modal
true, true,
AttdefDialogMsg::EditDefault AttdefDialogMsg::EditDefault
), ),
pick(text("fx").size(12).into(), t!("Insert field").into_owned(), None), pick(text("fx").size(12).into(), t!("Insert field").into_owned(), Some(AttdefDialogMsg::EditInsertField)),
] ]
.spacing(6) .spacing(6)
.align_y(iced::Center), .align_y(iced::Center),
+666
View File
@@ -0,0 +1,666 @@
//! Field dialog (FIELD and the ƒ buttons of the attribute definition
//! dialogs): a category and field list on the left, the chosen field's
//! format on the right, a live preview and the field expression below.
use iced::widget::{button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space};
use iced::{Element, Fill, Length};
use crate::app::Message;
use crate::t;
use crate::ui::style::common::muted_style;
use crate::ui::style::form::{button_style, dialog_button, field_style};
use crate::ui::window::pdf_dialogs::{accent_text, card_style};
/// Where the chosen field goes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldTarget {
/// FIELD: a new multi-line text placed on screen.
NewText,
/// The Default of the Attribute Definition dialog.
AttdefDefault,
/// The Default of the Edit Attribute Definition dialog.
AttdefEdit,
}
/// The categories the dialog offers, with their fields (only the ones the
/// application evaluates).
pub const CATEGORIES: &[(&str, &[&str])] = &[
("All", &[]),
("Date & Time", &["CreateDate", "Date", "PlotDate", "SaveDate"]),
(
"Document",
&["Author", "Comments", "Filename", "Filesize", "HyperlinkBase", "Keywords", "LastSavedBy", "Subject", "Title"],
),
("Linked", &["Hyperlink"]),
("Objects", &["Formula", "NamedObject", "Object"]),
("Other", &["DieselExpression", "SystemVariable"]),
(
"Plot",
&["DeviceName", "Login", "PageSetupName", "PaperSize", "PlotDate", "PlotOrientation", "PlotScale", "PlotStyleTable"],
),
];
/// The fields of a category; "All" lists every field once, sorted.
pub fn fields_of(category: usize) -> Vec<&'static str> {
if category == 0 {
let mut all: Vec<&'static str> = CATEGORIES.iter().flat_map(|(_, f)| f.iter().copied()).collect();
all.sort_unstable_by_key(|s| s.to_ascii_lowercase());
all.dedup();
return all;
}
CATEGORIES.get(category).map(|(_, f)| f.to_vec()).unwrap_or_default()
}
/// The date formats the dialog lists, in the reference order.
pub const DATE_FORMATS: &[&str] = &[
"M/d/yyyy", "dddd, MMMM dd, yyyy", "MMMM d, yyyy", "M/d/yy", "yyyy-MM-dd", "d-MMM-yy",
"M.d.yyyy", "MMM. d, yy", "d MMMM yyyy", "dd.MM.yyyy", "dd/MM/yyyy", "yyyy/MM/dd",
"yyyy-M-d", "MMMM yy", "MMM-yy", "M/d/yyyy h:mm tt", "M/d/yyyy h:mm:ss tt", "h:mm tt",
"h:mm:ss tt", "HH:mm", "HH:mm:ss", "%#x", "%#c", "%x", "%c", "%X",
];
pub const TEXT_CASES: &[&str] = &["(none)", "Uppercase", "Lowercase", "First capital", "Title case"];
pub const SIZE_UNITS: &[&str] = &["Bytes", "Kilobytes", "Megabytes"];
pub const NAMED_TYPES: &[&str] = &["Block", "Dimstyle", "Layer", "Linetype", "Tablestyle", "Textstyle", "View"];
/// How a field is formatted, by its name.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldKind {
Date,
Text,
Filename,
Filesize,
SystemVariable,
Diesel,
NamedObject,
Object,
Formula,
Hyperlink,
PlotScale,
}
/// Formula precisions: the current one, then 0 to 8 decimals.
pub const FORMULA_PRECISIONS: &[&str] =
&["Current precision", "0", "0.0", "0.00", "0.000", "0.0000", "0.00000", "0.000000", "0.0000000", "0.00000000"];
pub fn kind_of(name: &str) -> FieldKind {
match name {
"CreateDate" | "Date" | "PlotDate" | "SaveDate" => FieldKind::Date,
"Filename" => FieldKind::Filename,
"Filesize" => FieldKind::Filesize,
"SystemVariable" => FieldKind::SystemVariable,
"DieselExpression" => FieldKind::Diesel,
"NamedObject" => FieldKind::NamedObject,
"Object" => FieldKind::Object,
"Formula" => FieldKind::Formula,
"Hyperlink" => FieldKind::Hyperlink,
"PlotScale" => FieldKind::PlotScale,
_ => FieldKind::Text,
}
}
/// The Field dialog's working copy.
#[derive(Debug, Clone)]
pub struct FieldDialogState {
pub target: FieldTarget,
pub category: usize,
pub name: &'static str,
pub date_format: String,
pub text_case: usize,
/// 1 = path only, 2 = file name only, 3 = path and file name.
pub file_parts: u8,
pub file_extension: bool,
pub size_unit: usize,
pub sysvar: String,
pub diesel: String,
pub named_type: usize,
pub named_names: Vec<(String, codec::Handle)>,
pub named: Option<usize>,
/// The object picked for an Object field, its type and properties.
pub object: Option<(codec::Handle, String)>,
pub object_props: Vec<&'static str>,
pub object_prop: Option<&'static str>,
pub formula: String,
/// Indexes into codec FORMULA_FORMATS and FORMULA_PRECISIONS.
pub formula_format: usize,
pub formula_precision: usize,
pub hyperlink_text: String,
pub hyperlink_url: String,
/// Index into codec PLOT_SCALE_FORMATS.
pub plot_scale: usize,
pub preview: String,
/// Today in each of DATE_FORMATS, for the Examples list.
pub examples: Vec<String>,
}
impl FieldDialogState {
pub fn new(target: FieldTarget) -> Self {
Self {
target,
category: 0,
name: "Date",
date_format: "%x".into(),
text_case: 0,
file_parts: 3,
file_extension: true,
size_unit: 0,
sysvar: crate::entities::field::SYSVARS[0].into(),
diesel: String::new(),
named_type: 2,
named_names: Vec::new(),
named: None,
object: None,
object_props: Vec::new(),
object_prop: None,
formula: String::new(),
formula_format: 0,
formula_precision: 0,
hyperlink_text: String::new(),
hyperlink_url: String::new(),
plot_scale: 0,
preview: String::new(),
examples: Vec::new(),
}
}
/// The field code (without the `%<…>%` wrapper) and the objects it refers to.
pub fn code(&self) -> (String, Vec<codec::Handle>) {
let case = |base: String| match self.text_case {
0 => base,
n => format!("{base} \\f \"%tc{n}\""),
};
match kind_of(self.name) {
FieldKind::Date => (format!("\\AcVar {} \\f \"{}\"", self.name, self.date_format), vec![]),
FieldKind::Text => (case(format!("\\AcVar {}", self.name)), vec![]),
FieldKind::Filename => {
let bits = self.file_parts + if self.file_extension && self.file_parts & 2 != 0 { 4 } else { 0 };
// The text case goes first in the same format: "%tc1%fn6".
let case = match self.text_case {
0 => String::new(),
n => format!("%tc{n}"),
};
(format!("\\AcVar Filename \\f \"{case}%fn{bits}\""), vec![])
}
// Bytes are whole; kilo- and megabytes keep two decimals.
FieldKind::Filesize => (
match self.size_unit {
0 => "\\AcVar Filesize \\f \"%ld%by1\"".to_string(),
n => format!("\\AcVar Filesize \\f \"%.2f%by{}\"", n + 1),
},
vec![],
),
FieldKind::Formula => {
let format = codec::fields::FORMULA_FORMATS.get(self.formula_format).map_or("", |f| f.1);
let precision = match self.formula_precision {
0 => String::new(),
n => format!("%pr{}", n - 1),
};
let body = format!("\\AcExpr ({})", self.formula.trim());
match format!("{format}{precision}") {
f if f.is_empty() => (body, vec![]),
f => (format!("{body} \\f \"{f}\""), vec![]),
}
}
FieldKind::Hyperlink => (
format!("\\AcVar \\href \"{}##{}#0\"", self.hyperlink_url.trim(), self.hyperlink_text),
vec![],
),
FieldKind::PlotScale => (
codec::fields::PLOT_SCALE_FORMATS
.get(self.plot_scale)
.map_or_else(|| "\\AcVar PlotScale".to_string(), |f| f.1.to_string()),
vec![],
),
FieldKind::SystemVariable => (format!("\\AcVar {}", self.sysvar), vec![]),
FieldKind::Diesel => (format!("\\AcDiesel {}", self.diesel), vec![]),
FieldKind::NamedObject => match self.named.and_then(|i| self.named_names.get(i)) {
Some((_, handle)) => (case("\\AcObjProp Object(%<\\_ObjIdx 0>%).Name".into()), vec![*handle]),
None => ("\\AcObjProp".into(), vec![]),
},
FieldKind::Object => match (&self.object, self.object_prop) {
(Some((handle, _)), Some(prop)) => {
(format!("\\AcObjProp Object(%<\\_ObjIdx 0>%).{prop}"), vec![*handle])
}
_ => ("\\AcObjProp".into(), vec![]),
},
}
}
/// The field can be inserted (an object field needs its object and
/// property, a named object its name, a DIESEL field an expression).
pub fn complete(&self) -> bool {
match kind_of(self.name) {
FieldKind::NamedObject => self.named.is_some(),
FieldKind::Object => self.object.is_some() && self.object_prop.is_some(),
FieldKind::Diesel => !self.diesel.trim().is_empty(),
FieldKind::Formula => !self.formula.trim().is_empty(),
FieldKind::Hyperlink => !self.hyperlink_url.trim().is_empty(),
_ => true,
}
}
}
/// The geometric properties a field can show for an object of this type.
pub fn object_properties(entity: &codec::EntityType) -> Vec<&'static str> {
use codec::EntityType as E;
match entity {
E::Line(_) => vec!["Center", "EndPoint", "Length", "StartPoint"],
E::Circle(_) => vec!["Area", "Center", "Circumference", "Diameter", "Radius"],
E::Arc(_) => vec!["Center", "Length", "Radius"],
E::Ellipse(_) => vec!["Area", "Center"],
E::LwPolyline(_) | E::Polyline2D(_) | E::Spline(_) => vec!["Area", "Length"],
E::Hatch(_) => vec!["Area"],
_ => vec![],
}
}
#[derive(Debug, Clone)]
pub enum FieldDialogMsg {
Category(usize),
Name(&'static str),
DateFormat(String),
DateExample(usize),
TextCase(usize),
FileParts(u8),
FileExtension(bool),
SizeUnit(usize),
SysVar(String),
Diesel(String),
NamedType(usize),
Named(usize),
SelectObject,
ObjectProp(&'static str),
Formula(String),
FormulaFormat(usize),
FormulaPrecision(usize),
Evaluate,
HyperlinkText(String),
HyperlinkUrl(String),
BrowseHyperlink,
PlotScale(usize),
Help,
Ok,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Pick(usize, String);
impl std::fmt::Display for Pick {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.1)
}
}
fn msg(m: FieldDialogMsg) -> Message {
Message::FieldDialog(m)
}
fn card<'a>(title: String, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
container(column![text(title.to_uppercase()).size(10).style(accent_text), content.into()].spacing(8))
.padding([10, 12])
.width(Fill)
.style(card_style)
.into()
}
/// A selectable list: one row per item, the chosen one highlighted.
fn list<'a>(rows: Vec<(String, bool, FieldDialogMsg)>, height: f32) -> Element<'a, Message> {
let rows = rows.into_iter().map(|(label, on, m)| {
button(text(label).size(12))
.on_press(msg(m))
.style(button_style(on))
.padding([3, 8])
.width(Fill)
.into()
});
container(scrollable(iced::widget::Column::with_children(rows.collect::<Vec<Element<'a, Message>>>()).spacing(1)))
.height(Length::Fixed(height))
.padding(2)
.style(|theme: &iced::Theme| field_style(theme, text_input::Status::Active).into_container())
.into()
}
trait IntoContainer {
fn into_container(self) -> container::Style;
}
impl IntoContainer for text_input::Style {
fn into_container(self) -> container::Style {
container::Style {
background: Some(self.background),
border: self.border,
..Default::default()
}
}
}
fn case_list<'a>(state: &FieldDialogState) -> Element<'a, Message> {
list(
TEXT_CASES
.iter()
.enumerate()
.map(|(i, c)| (t!(*c).into_owned(), state.text_case == i, FieldDialogMsg::TextCase(i)))
.collect(),
130.0,
)
}
fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
let examples = &state.examples;
match kind_of(state.name) {
FieldKind::Date => column![
row![
text(t!("Date format")).size(12).width(Length::Fixed(96.0)),
text_input("", &state.date_format)
.size(12)
.padding([5, 8])
.style(field_style)
.on_input(|v| msg(FieldDialogMsg::DateFormat(v))),
]
.spacing(8)
.align_y(iced::Center),
text(t!("Examples")).size(12),
list(
examples
.iter()
.enumerate()
.map(|(i, e)| (e.clone(), DATE_FORMATS.get(i) == Some(&state.date_format.as_str()), FieldDialogMsg::DateExample(i)))
.collect(),
220.0,
),
]
.spacing(8)
.into(),
FieldKind::Text => column![text(t!("Format")).size(12), case_list(state)].spacing(8).into(),
FieldKind::Filename => {
let part = |label: &str, bits: u8| {
checkbox(state.file_parts == bits)
.label(t!(label).into_owned())
.text_size(12)
.size(14)
.on_toggle(move |_| msg(FieldDialogMsg::FileParts(bits)))
};
let mut ext = checkbox(state.file_extension)
.label(t!("Display file extension").into_owned())
.text_size(12)
.size(14);
if state.file_parts & 2 != 0 {
ext = ext.on_toggle(|v| msg(FieldDialogMsg::FileExtension(v)));
}
column![
part("Filename only", 2),
part("Path only", 1),
part("Path and filename", 3),
ext,
text(t!("Format")).size(12),
case_list(state),
]
.spacing(8)
.into()
}
FieldKind::Filesize => column![
text(t!("Format")).size(12),
list(
SIZE_UNITS
.iter()
.enumerate()
.map(|(i, u)| (t!(*u).into_owned(), state.size_unit == i, FieldDialogMsg::SizeUnit(i)))
.collect(),
90.0,
),
]
.spacing(8)
.into(),
FieldKind::SystemVariable => column![
text(t!("System variable")).size(12),
list(
crate::entities::field::SYSVARS
.iter()
.map(|v| (v.to_string(), state.sysvar == *v, FieldDialogMsg::SysVar(v.to_string())))
.collect(),
250.0,
),
]
.spacing(8)
.into(),
FieldKind::Diesel => column![
text(t!("Diesel expression")).size(12),
text_input("$(getvar,dwgname)", &state.diesel)
.size(12)
.padding([5, 8])
.style(field_style)
.on_input(|v| msg(FieldDialogMsg::Diesel(v))),
]
.spacing(8)
.into(),
FieldKind::NamedObject => column![
row![
text(t!("Named object type")).size(12).width(Length::Fixed(120.0)),
pick_list(
Some(Pick(state.named_type, t!(NAMED_TYPES[state.named_type]).into_owned())),
NAMED_TYPES.iter().enumerate().map(|(i, n)| Pick(i, t!(*n).into_owned())).collect::<Vec<_>>(),
|p: &Pick| p.1.clone(),
)
.on_select(|p| msg(FieldDialogMsg::NamedType(p.0)))
.text_size(12)
.padding([5, 8])
.width(Fill),
]
.spacing(8)
.align_y(iced::Center),
text(t!("Name")).size(12),
list(
state
.named_names
.iter()
.enumerate()
.map(|(i, (n, _))| (n.clone(), state.named == Some(i), FieldDialogMsg::Named(i)))
.collect(),
150.0,
),
text(t!("Format")).size(12),
case_list(state),
]
.spacing(8)
.into(),
FieldKind::Formula => {
let formats: Vec<Pick> = codec::fields::FORMULA_FORMATS
.iter()
.enumerate()
.map(|(i, (n, _))| Pick(i, t!(*n).into_owned()))
.collect();
let precisions: Vec<Pick> =
FORMULA_PRECISIONS.iter().enumerate().map(|(i, n)| Pick(i, t!(*n).into_owned())).collect();
column![
row![
text(t!("Format")).size(12).width(Length::Fixed(80.0)),
pick_list(Some(formats[state.formula_format].clone()), formats.clone(), |p: &Pick| p.1.clone())
.on_select(|p| msg(FieldDialogMsg::FormulaFormat(p.0)))
.text_size(12)
.padding([5, 8])
.width(Fill),
]
.spacing(8)
.align_y(iced::Center),
row![
text(t!("Precision")).size(12).width(Length::Fixed(80.0)),
pick_list(
Some(precisions[state.formula_precision].clone()),
precisions.clone(),
|p: &Pick| p.1.clone(),
)
.on_select(|p| msg(FieldDialogMsg::FormulaPrecision(p.0)))
.text_size(12)
.padding([5, 8])
.width(Fill),
]
.spacing(8)
.align_y(iced::Center),
]
.spacing(8)
.into()
}
FieldKind::Hyperlink => column![
row![
text(t!("Text to display")).size(12).width(Length::Fixed(120.0)),
text_input("", &state.hyperlink_text)
.size(12)
.padding([5, 8])
.style(field_style)
.on_input(|v| msg(FieldDialogMsg::HyperlinkText(v))),
]
.spacing(8)
.align_y(iced::Center),
row![
text(t!("Address")).size(12).width(Length::Fixed(120.0)),
text_input("https://", &state.hyperlink_url)
.size(12)
.padding([5, 8])
.font(iced::Font::MONOSPACE)
.style(field_style)
.on_input(|v| msg(FieldDialogMsg::HyperlinkUrl(v))),
button(text("…").size(12))
.on_press(msg(FieldDialogMsg::BrowseHyperlink))
.style(button_style(false))
.padding([5, 10]),
]
.spacing(8)
.align_y(iced::Center),
]
.spacing(8)
.into(),
FieldKind::PlotScale => column![
text(t!("Format")).size(12),
list(
codec::fields::PLOT_SCALE_FORMATS
.iter()
.enumerate()
.map(|(i, (n, _))| (t!(*n).into_owned(), state.plot_scale == i, FieldDialogMsg::PlotScale(i)))
.collect(),
170.0,
),
]
.spacing(8)
.into(),
FieldKind::Object => column![
row![
text(t!("Object type")).size(12).width(Length::Fixed(96.0)),
container(
text(state.object.as_ref().map(|(_, t)| t.clone()).unwrap_or_default()).size(12).style(muted_style)
)
.padding([5, 8])
.width(Fill),
button(text(t!("Select object")).size(12))
.on_press(msg(FieldDialogMsg::SelectObject))
.style(button_style(false))
.padding([5, 10]),
]
.spacing(8)
.align_y(iced::Center),
text(t!("Property")).size(12),
list(
state
.object_props
.iter()
.map(|p| (t!(*p).into_owned(), state.object_prop == Some(*p), FieldDialogMsg::ObjectProp(p)))
.collect(),
180.0,
),
]
.spacing(8)
.into(),
}
}
pub fn view<'a>(
state: &'a FieldDialogState,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let categories: Vec<Pick> = CATEGORIES.iter().enumerate().map(|(i, (c, _))| Pick(i, t!(*c).into_owned())).collect();
let left = card(
t!("Field").into_owned(),
column![
row![
text(t!("Category")).size(12).width(Length::Fixed(70.0)),
pick_list(Some(categories[state.category].clone()), categories.clone(), |p: &Pick| p.1.clone())
.on_select(|p| msg(FieldDialogMsg::Category(p.0)))
.text_size(12)
.padding([5, 8])
.width(Fill),
]
.spacing(8)
.align_y(iced::Center),
list(
fields_of(state.category)
.into_iter()
.map(|n| (n.to_string(), state.name == n, FieldDialogMsg::Name(n)))
.collect(),
330.0,
),
]
.spacing(8),
);
let preview = card(
t!("Preview").into_owned(),
container(text(if state.preview.is_empty() { "----".to_string() } else { state.preview.clone() }).size(15))
.padding([6, 8])
.width(Fill),
);
let right = match kind_of(state.name) {
FieldKind::Formula => column![
card(
t!("Formula").into_owned(),
column![
text_input("(12+3)*2", &state.formula)
.size(12)
.padding([8, 8])
.font(iced::Font::MONOSPACE)
.style(field_style)
.on_input(|v| msg(FieldDialogMsg::Formula(v))),
button(text(t!("Evaluate")).size(12))
.on_press(msg(FieldDialogMsg::Evaluate))
.style(button_style(false))
.padding([5, 14]),
]
.spacing(8),
),
card(t!("Format").into_owned(), format_panel(state)),
preview,
]
.spacing(10),
FieldKind::Hyperlink => column![card(t!("Hyperlink").into_owned(), format_panel(state)), preview].spacing(10),
_ => column![card(t!("Format").into_owned(), format_panel(state)), preview].spacing(10),
};
let (code, _) = state.code();
let expression = column![
text(t!("Field expression")).size(12),
container(text(format!("%<{code}>%")).size(12).font(iced::Font::MONOSPACE).style(muted_style))
.padding([5, 8])
.width(Fill)
.style(|theme: &iced::Theme| field_style(theme, text_input::Status::Disabled).into_container()),
]
.spacing(4);
let mut insert = button(text(t!("Insert")).size(12))
.style(button_style(true))
.padding([6, 16]);
if state.complete() {
insert = insert.on_press(msg(FieldDialogMsg::Ok));
}
let footer = row![
button(text("?").size(12)).on_press(msg(FieldDialogMsg::Help)).style(button_style(false)).padding([5, 11]),
Space::new().width(Fill),
dialog_button(t!("Cancel"), Message::CloseModal, false),
insert,
]
.spacing(6)
.align_y(iced::Center);
column![
row![container(left).width(Length::Fixed(240.0)), right].spacing(10),
expression,
footer,
]
.spacing(10)
.padding([10, 12])
.width(sizing.width)
.into()
}
+1
View File
@@ -1,5 +1,6 @@
pub mod about; pub mod about;
pub mod attdef_dialog; pub mod attdef_dialog;
pub mod field_dialog;
pub mod block_definition; pub mod block_definition;
pub mod block_palette; pub mod block_palette;
pub mod browser; pub mod browser;