diff --git a/schemas/pvi.device.schema.json b/schemas/pvi.device.schema.json
index 1e4537a7..38eb5ff9 100644
--- a/schemas/pvi.device.schema.json
+++ b/schemas/pvi.device.schema.json
@@ -117,7 +117,7 @@
},
"CheckBox": {
"additionalProperties": false,
- "description": "Checkable control of a boolean PV",
+ "description": "Checkable control of a boolean PV.\n\nThis is compact replacement for a `ToggleButton` to be used in rows and tables.",
"properties": {
"type": {
"const": "CheckBox",
@@ -504,6 +504,9 @@
},
{
"$ref": "#/$defs/TextWrite"
+ },
+ {
+ "$ref": "#/$defs/ToggleButton"
}
],
"title": "Write Widget"
@@ -636,6 +639,9 @@
},
{
"$ref": "#/$defs/TextWrite"
+ },
+ {
+ "$ref": "#/$defs/ToggleButton"
}
],
"title": "Write Widget"
@@ -883,6 +889,19 @@
},
"title": "TextWrite",
"type": "object"
+ },
+ "ToggleButton": {
+ "additionalProperties": false,
+ "description": "A pair of buttons to select between two mutually exclusive states.",
+ "properties": {
+ "type": {
+ "const": "ToggleButton",
+ "default": "ToggleButton",
+ "title": "Type"
+ }
+ },
+ "title": "ToggleButton",
+ "type": "object"
}
},
"additionalProperties": false,
diff --git a/src/pvi/_convert/_asyn_convert.py b/src/pvi/_convert/_asyn_convert.py
index e4535eaf..51335c7c 100644
--- a/src/pvi/_convert/_asyn_convert.py
+++ b/src/pvi/_convert/_asyn_convert.py
@@ -1,17 +1,23 @@
import re
-from typing import Any, Optional, Type
-
-from pvi.device import SignalR, SignalRW, SignalW, enforce_pascal_case
-
-from ._parameters import (
- AsynParameter,
- InRecordTypes,
- OutRecordTypes,
- Parameter,
- Record,
- get_waveform_parameter,
+from typing import Any, ClassVar, List, Optional, Type, cast
+
+from pydantic import Field
+
+from pvi._schema_utils import rec_subclasses
+from pvi.device import (
+ LED,
+ ComboBox,
+ Named,
+ ReadWidgetUnion,
+ TextFormat,
+ TextRead,
+ TextWrite,
+ ToggleButton,
+ WriteWidgetUnion,
)
+from ._parameters import Record, TypeStrings
+
class RecordError(Exception):
pass
@@ -47,7 +53,7 @@ def get_parameter_name(self) -> Optional[str]:
parameter_name = match.group(1)
return parameter_name
- def asyn_component_type(self) -> Type[AsynParameter]:
+ def asyn_component_type(self) -> Type["AsynParameter"]:
# For waveform records the data type is defined by DTYP
if self.type == "waveform":
return get_waveform_parameter(self.fields["DTYP"])
@@ -70,61 +76,176 @@ def asyn_component_type(self) -> Type[AsynParameter]:
)
-class SettingPair(Parameter):
- read_record: AsynRecord
- write_record: AsynRecord
+class AsynParameter(Named):
+ """Base class for all Asyn Parameters to inherit from"""
- def generate_component(self) -> SignalRW:
- asyn_cls = self.write_record.asyn_component_type()
- component = asyn_cls(
- name=enforce_pascal_case(self.write_record.name),
- write_record=self.write_record.pv,
- read_record=self.read_record.pv,
- )
+ type_strings: ClassVar[TypeStrings]
+ read_record: AsynRecord | None = Field(
+ default=None,
+ description="A read AsynRecord, if not given then use $(name)_RBV as read PV",
+ )
+ write_record: AsynRecord | None = Field(
+ default=None,
+ description="A write AsynRecord, if not given then use $(name) as write PV",
+ )
+ read_widget: ReadWidgetUnion = Field(default=TextRead())
+ write_widget: WriteWidgetUnion = Field(default=TextWrite())
- return SignalRW(
- name=component.name,
- write_pv=component.get_write_record(),
- write_widget=component.write_widget,
- read_pv=component.get_read_record(),
- read_widget=component.read_widget,
- )
+ def get_read_pv(self) -> str:
+ if self.read_record:
+ return self.read_record.pv
+ else:
+ return self.name + "_RBV"
+
+ def get_write_pv(self) -> str:
+ if self.write_record:
+ return self.write_record.pv
+ else:
+ return self.name
-class Readback(Parameter):
- read_record: AsynRecord
+class AsynBinary(AsynParameter):
+ """Asyn Binary Parameter and records"""
+
+ type_strings = TypeStrings(
+ asyn_read="asynInt32",
+ asyn_write="asynInt32",
+ asyn_param="asynParamInt32",
+ )
+ read_widget: ReadWidgetUnion = Field(LED())
+ write_widget: WriteWidgetUnion = Field(ToggleButton())
- def generate_component(self) -> SignalR:
- asyn_cls = self.read_record.asyn_component_type()
+ def model_post_init(self, __context: Any) -> None:
+ if self.write_record is not None:
+ if not all(f in self.write_record.fields for f in ("ZNAM", "ONAM")):
+ print(
+ f"WARNING: ZNAM/ONAM not set for {self.write_record.pv}. "
+ "Button labels will be blank."
+ )
- name = self.read_record.name
- if name.endswith("_RBV"):
- name = name[: -len("_RBV")]
- component = asyn_cls(
- name=enforce_pascal_case(name), read_record=self.read_record.pv
- )
+class AsynBusy(AsynBinary):
+ """Asyn Busy Parameter and records"""
- return SignalR(
- name=component.name,
- read_pv=component.get_read_record(),
- read_widget=component.read_widget,
- )
+class AsynFloat64(AsynParameter):
+ """Asyn Float64 Parameter and records"""
+
+ type_strings: ClassVar[TypeStrings] = TypeStrings(
+ asyn_read="asynFloat64",
+ asyn_write="asynFloat64",
+ asyn_param="asynParamFloat64",
+ )
+ read_widget: ReadWidgetUnion = Field(default=TextRead())
+ write_widget: WriteWidgetUnion = Field(default=TextWrite())
-class Action(Parameter):
- write_record: AsynRecord
- def generate_component(self) -> SignalW:
- asyn_cls = self.write_record.asyn_component_type()
+class AsynInt32(AsynParameter):
+ """Asyn Int32 Parameter and records"""
- component = asyn_cls(
- name=enforce_pascal_case(self.write_record.name),
- write_record=self.write_record.pv,
- )
+ type_strings: ClassVar[TypeStrings] = TypeStrings(
+ asyn_read="asynInt32",
+ asyn_write="asynInt32",
+ asyn_param="asynParamInt32",
+ )
+ read_widget: ReadWidgetUnion = Field(default=TextRead())
+ write_widget: WriteWidgetUnion = Field(default=TextWrite())
- return SignalW(
- name=component.name,
- write_pv=component.get_write_record(),
- write_widget=component.write_widget,
- )
+
+class AsynLong(AsynInt32):
+ """Asyn Long Parameter and records"""
+
+
+class AsynMultiBitBinary(AsynParameter):
+ """Asyn MultiBitBinary Parameter and records"""
+
+ type_strings: ClassVar[TypeStrings] = TypeStrings(
+ asyn_read="asynInt32",
+ asyn_write="asynInt32",
+ asyn_param="asynParamInt32",
+ )
+ read_widget: ReadWidgetUnion = Field(TextRead())
+ write_widget: WriteWidgetUnion = Field(ComboBox())
+
+
+class AsynString(AsynParameter):
+ """Asyn String Parameter and records"""
+
+ type_strings: ClassVar[TypeStrings] = TypeStrings(
+ asyn_read="asynOctetRead",
+ asyn_write="asynOctetWrite",
+ asyn_param="asynParamOctet",
+ )
+ read_widget: ReadWidgetUnion = Field(TextRead())
+ write_widget: WriteWidgetUnion = Field(TextWrite())
+
+
+InRecordTypes = {
+ "ai": AsynFloat64,
+ "bi": AsynBinary,
+ "longin": AsynLong,
+ "mbbi": AsynMultiBitBinary,
+ "stringin": AsynString,
+}
+
+
+OutRecordTypes = {
+ "ao": AsynFloat64,
+ "bo": AsynBinary,
+ "busy": AsynBusy,
+ "longout": AsynLong,
+ "mbbo": AsynMultiBitBinary,
+ "stringout": AsynString,
+}
+
+
+class AsynWaveform(AsynParameter):
+ """Asyn Waveform Parameter and records"""
+
+ type_strings: ClassVar[TypeStrings] = TypeStrings(
+ asyn_read="asynOctetRead",
+ asyn_write="asynOctetWrite",
+ asyn_param="asynParamOctet",
+ )
+ read_widget: ReadWidgetUnion = Field(TextRead(format=TextFormat.string))
+ write_widget: WriteWidgetUnion = Field(TextWrite(format=TextFormat.string))
+
+
+class AsynInt32Waveform(AsynWaveform):
+ """Asyn Waveform Parameter and records with int32 array elements"""
+
+ type_strings: ClassVar[TypeStrings] = TypeStrings(
+ asyn_read="asynInt32ArrayIn",
+ asyn_write="asynInt32ArrayOut",
+ asyn_param="asynParamInt32",
+ )
+ read_widget: ReadWidgetUnion = Field(TextRead())
+ write_widget: WriteWidgetUnion = Field(TextWrite())
+
+
+class AsynFloat64Waveform(AsynWaveform):
+ """Asyn Waveform Parameter and records with float64 array elements"""
+
+ type_strings: ClassVar[TypeStrings] = TypeStrings(
+ asyn_read="asynFloat64ArrayIn",
+ asyn_write="asynFloat64ArrayOut",
+ asyn_param="asynParamFloat64",
+ )
+ read_widget: ReadWidgetUnion = Field(TextRead())
+ write_widget: WriteWidgetUnion = Field(TextWrite())
+
+
+WaveformRecordTypes = [AsynWaveform] + cast(
+ List[Type[AsynWaveform]], rec_subclasses(AsynWaveform)
+)
+
+
+def get_waveform_parameter(dtyp: str):
+ for waveform_cls in WaveformRecordTypes:
+ if dtyp in (
+ waveform_cls.type_strings.asyn_read,
+ waveform_cls.type_strings.asyn_write,
+ ):
+ return waveform_cls
+
+ assert False, f"Waveform type for DTYP {dtyp} not found in {WaveformRecordTypes}"
diff --git a/src/pvi/_convert/_parameters.py b/src/pvi/_convert/_parameters.py
index 11db04b0..d7b90517 100644
--- a/src/pvi/_convert/_parameters.py
+++ b/src/pvi/_convert/_parameters.py
@@ -1,23 +1,11 @@
import re
from enum import Enum
from functools import cached_property
-from typing import ClassVar, Dict, List, Optional, Type, cast
+from typing import Dict, Optional
from pydantic import BaseModel, Field
-from pvi._schema_utils import rec_subclasses
-from pvi.device import (
- LED,
- CheckBox,
- ComboBox,
- ComponentUnion,
- Named,
- ReadWidgetUnion,
- TextFormat,
- TextRead,
- TextWrite,
- WriteWidgetUnion,
-)
+from pvi.device import ComponentUnion
class TypeStrings(BaseModel):
@@ -70,175 +58,6 @@ class DisplayForm(Enum):
ENGINEERING = "Engineering"
-class AsynParameter(Named):
- """Base class for all Asyn Parameters to inherit from"""
-
- type_strings: ClassVar[TypeStrings]
- read_record: Optional[str] = Field(
- default=None,
- description="The full read record, if not given then use $(name)_RBV",
- )
- write_record: Optional[str] = Field(
- default=None, description="The full write record, if not given then use $(name)"
- )
- display_form: Optional[DisplayForm] = Field(
- default=None, description="Display form for numeric/array fields"
- )
- read_widget: ReadWidgetUnion = Field(default=TextRead())
- write_widget: WriteWidgetUnion = Field(default=TextWrite())
-
- def get_read_record(self) -> str:
- if self.read_record:
- return self.read_record
- else:
- return self.name + "_RBV"
-
- def get_write_record(self) -> str:
- if self.write_record:
- return self.write_record
- else:
- return self.name
-
-
-class AsynBinary(AsynParameter):
- """Asyn Binary Parameter and records"""
-
- type_strings = TypeStrings(
- asyn_read="asynInt32",
- asyn_write="asynInt32",
- asyn_param="asynParamInt32",
- )
- read_widget: ReadWidgetUnion = Field(LED())
- write_widget: WriteWidgetUnion = Field(CheckBox())
-
-
-class AsynBusy(AsynBinary):
- """Asyn Busy Parameter and records"""
-
-
-class AsynFloat64(AsynParameter):
- """Asyn Float64 Parameter and records"""
-
- type_strings: ClassVar[TypeStrings] = TypeStrings(
- asyn_read="asynFloat64",
- asyn_write="asynFloat64",
- asyn_param="asynParamFloat64",
- )
- read_widget: ReadWidgetUnion = Field(default=TextRead())
- write_widget: WriteWidgetUnion = Field(default=TextWrite())
-
-
-class AsynInt32(AsynParameter):
- """Asyn Int32 Parameter and records"""
-
- type_strings: ClassVar[TypeStrings] = TypeStrings(
- asyn_read="asynInt32",
- asyn_write="asynInt32",
- asyn_param="asynParamInt32",
- )
- read_widget: ReadWidgetUnion = Field(default=TextRead())
- write_widget: WriteWidgetUnion = Field(default=TextWrite())
-
-
-class AsynLong(AsynInt32):
- """Asyn Long Parameter and records"""
-
-
-class AsynMultiBitBinary(AsynParameter):
- """Asyn MultiBitBinary Parameter and records"""
-
- type_strings: ClassVar[TypeStrings] = TypeStrings(
- asyn_read="asynInt32",
- asyn_write="asynInt32",
- asyn_param="asynParamInt32",
- )
- read_widget: ReadWidgetUnion = Field(TextRead())
- write_widget: WriteWidgetUnion = Field(ComboBox())
-
-
-class AsynString(AsynParameter):
- """Asyn String Parameter and records"""
-
- type_strings: ClassVar[TypeStrings] = TypeStrings(
- asyn_read="asynOctetRead",
- asyn_write="asynOctetWrite",
- asyn_param="asynParamOctet",
- )
- read_widget: ReadWidgetUnion = Field(TextRead())
- write_widget: WriteWidgetUnion = Field(TextWrite())
-
-
-InRecordTypes = {
- "ai": AsynFloat64,
- "bi": AsynBinary,
- "longin": AsynLong,
- "mbbi": AsynMultiBitBinary,
- "stringin": AsynString,
-}
-
-
-OutRecordTypes = {
- "ao": AsynFloat64,
- "bo": AsynBinary,
- "busy": AsynBusy,
- "longout": AsynLong,
- "mbbo": AsynMultiBitBinary,
- "stringout": AsynString,
-}
-
-
-class AsynWaveform(AsynParameter):
- """Asyn Waveform Parameter and records"""
-
- type_strings: ClassVar[TypeStrings] = TypeStrings(
- asyn_read="asynOctetRead",
- asyn_write="asynOctetWrite",
- asyn_param="asynParamOctet",
- )
- read_widget: ReadWidgetUnion = Field(TextRead(format=TextFormat.string))
- write_widget: WriteWidgetUnion = Field(TextWrite(format=TextFormat.string))
-
-
-class AsynInt32Waveform(AsynWaveform):
- """Asyn Waveform Parameter and records with int32 array elements"""
-
- type_strings: ClassVar[TypeStrings] = TypeStrings(
- asyn_read="asynInt32ArrayIn",
- asyn_write="asynInt32ArrayOut",
- asyn_param="asynParamInt32",
- )
- read_widget: ReadWidgetUnion = Field(TextRead())
- write_widget: WriteWidgetUnion = Field(TextWrite())
-
-
-class AsynFloat64Waveform(AsynWaveform):
- """Asyn Waveform Parameter and records with float64 array elements"""
-
- type_strings: ClassVar[TypeStrings] = TypeStrings(
- asyn_read="asynFloat64ArrayIn",
- asyn_write="asynFloat64ArrayOut",
- asyn_param="asynParamFloat64",
- )
- read_widget: ReadWidgetUnion = Field(TextRead())
- write_widget: WriteWidgetUnion = Field(TextWrite())
-
-
-WaveformRecordTypes = [AsynWaveform] + cast(
- List[Type[AsynWaveform]], rec_subclasses(AsynWaveform)
-)
-
-
-def get_waveform_parameter(dtyp: str):
- for waveform_cls in WaveformRecordTypes:
- if dtyp in (
- waveform_cls.type_strings.asyn_read,
- waveform_cls.type_strings.asyn_write,
- ):
- return waveform_cls
-
- assert False, f"Waveform type for DTYP {dtyp} not found in {WaveformRecordTypes}"
-
-
MACRO_RE = re.compile(r"\$\(.*\)")
diff --git a/src/pvi/_convert/_template_convert.py b/src/pvi/_convert/_template_convert.py
index 2f581b9d..5ca14c15 100644
--- a/src/pvi/_convert/_template_convert.py
+++ b/src/pvi/_convert/_template_convert.py
@@ -2,16 +2,22 @@
from pathlib import Path
from typing import List, Tuple
-from pvi.device import ComponentUnion, Grid, Group, Tree, enforce_pascal_case
+from pvi.device import (
+ ComponentUnion,
+ Grid,
+ Group,
+ SignalR,
+ SignalRW,
+ SignalW,
+ Tree,
+ enforce_pascal_case,
+)
from ._asyn_convert import (
- Action,
AsynRecord,
- Parameter,
- Readback,
RecordError,
- SettingPair,
)
+from ._parameters import Parameter
OVERRIDE_DESC = "# Overriding value in auto-generated template"
@@ -169,6 +175,66 @@ def _sort_inputs_outputs(
return parameters
+class SettingPair(Parameter):
+ read_record: AsynRecord
+ write_record: AsynRecord
+
+ def generate_component(self) -> SignalRW:
+ asyn_cls = self.write_record.asyn_component_type()
+ component = asyn_cls(
+ name=enforce_pascal_case(self.write_record.name),
+ write_record=self.write_record,
+ read_record=self.read_record,
+ )
+
+ return SignalRW(
+ name=component.name,
+ write_pv=component.get_write_pv(),
+ write_widget=component.write_widget,
+ read_pv=component.get_read_pv(),
+ read_widget=component.read_widget,
+ )
+
+
+class Readback(Parameter):
+ read_record: AsynRecord
+
+ def generate_component(self) -> SignalR:
+ asyn_cls = self.read_record.asyn_component_type()
+
+ name = self.read_record.name
+ if name.endswith("_RBV"):
+ name = name[: -len("_RBV")]
+
+ component = asyn_cls(
+ name=enforce_pascal_case(name), read_record=self.read_record
+ )
+
+ return SignalR(
+ name=component.name,
+ read_pv=component.get_read_pv(),
+ read_widget=component.read_widget,
+ )
+
+
+class Action(Parameter):
+ write_record: AsynRecord
+
+ def generate_component(self) -> SignalW:
+ asyn_cls = self.write_record.asyn_component_type()
+
+ component = asyn_cls(
+ name=enforce_pascal_case(self.write_record.name),
+ write_record=self.write_record,
+ )
+
+ return SignalW(
+ name=component.name,
+ write_pv=component.get_write_pv(),
+ write_widget=component.write_widget,
+ )
+
+
class ParameterRoleMatcher:
@staticmethod
def get_actions(
diff --git a/src/pvi/_format/aps.adl b/src/pvi/_format/aps.adl
index fc0fcd68..2257acd7 100644
--- a/src/pvi/_format/aps.adl
+++ b/src/pvi/_format/aps.adl
@@ -216,7 +216,7 @@ byte {
height=19
}
control {
- chan="CheckBox"
+ chan="ToggleButton"
clr=14
bclr=51
}
@@ -276,3 +276,17 @@ text {
clr=14
bclr=4
}
+"choice button" {
+ object {
+ x=165
+ y=340
+ width=140
+ height=19
+ }
+ control {
+ chan="CheckBox"
+ clr=14
+ bclr=51
+ }
+ stacking="column"
+}
diff --git a/src/pvi/_format/aps.py b/src/pvi/_format/aps.py
index cc579fa9..3393368b 100644
--- a/src/pvi/_format/aps.py
+++ b/src/pvi/_format/aps.py
@@ -77,6 +77,11 @@ def format(self, device: Device, path: Path):
search='"CheckBox"',
property_map={"chan": "pv"},
),
+ toggle_formatter_cls=PVWidgetFormatter.from_template(
+ template,
+ search='"ToggleButton"',
+ property_map={"chan": "pv"},
+ ),
combo_box_formatter_cls=PVWidgetFormatter.from_template(
template,
search='"ComboBox"',
diff --git a/src/pvi/_format/dls.bob b/src/pvi/_format/dls.bob
index 08b3cf75..5f014f92 100644
--- a/src/pvi/_format/dls.bob
+++ b/src/pvi/_format/dls.bob
@@ -90,17 +90,12 @@
30
- ChoiceButton
- CheckBox
+ ToggleButton
+ ToggleButton
18
207
120
31
-
- - 0
- - 1
- - 2
-
ComboBox
@@ -120,11 +115,12 @@
CheckBox
-
- 24
- 296
- 110
- 30
+
+ 64
+ 300
+ 18
+ 18
+ true
GroupLabel
diff --git a/src/pvi/_format/dls.edl b/src/pvi/_format/dls.edl
index a60e3a55..0b998ea0 100644
--- a/src/pvi/_format/dls.edl
+++ b/src/pvi/_format/dls.edl
@@ -34,7 +34,7 @@ release 0
x 5
y 50
w 130
-h 220
+h 245
lineColor index 14
fill
fillColor index 5
@@ -96,7 +96,7 @@ selectColor index 3
inconsistentColor index 14
topShadowColor index 1
botShadowColor index 11
-controlPv "CheckBox"
+controlPv "ToggleButton"
indicatorPv "None"
font "arial-bold-r-14.0"
orientation "horizontal"
@@ -317,3 +317,25 @@ displayFileName {
icon
endObjectProperties
+# (Choice Button)
+object activeChoiceButtonClass
+beginObjectProperties
+major 4
+minor 0
+release 0
+x 10
+y 270
+w 120
+h 17
+fgColor index 14
+bgColor index 3
+selectColor index 3
+inconsistentColor index 14
+topShadowColor index 1
+botShadowColor index 11
+controlPv "CheckBox"
+indicatorPv "None"
+font "arial-bold-r-14.0"
+orientation "horizontal"
+endObjectProperties
+
diff --git a/src/pvi/_format/dls.py b/src/pvi/_format/dls.py
index 5ac2719b..b6640da5 100644
--- a/src/pvi/_format/dls.py
+++ b/src/pvi/_format/dls.py
@@ -87,6 +87,11 @@ def format_edl(self, device: Device, path: Path):
search='"ComboBox"',
property_map={"controlPv": "pv"},
),
+ toggle_formatter_cls=PVWidgetFormatter.from_template(
+ template,
+ search='"ToggleButton"',
+ property_map={"controlPv": "pv"},
+ ),
combo_box_formatter_cls=PVWidgetFormatter.from_template(
template,
search='"ComboBox"',
@@ -278,6 +283,11 @@ def format_bob(self, device: Device, path: Path):
search="ChoiceButton",
property_map={"pv_name": "pv"},
),
+ toggle_formatter_cls=PVWidgetFormatter.from_template(
+ template,
+ search="ToggleButton",
+ property_map={"pv_name": "pv"},
+ ),
combo_box_formatter_cls=PVWidgetFormatter.from_template(
template,
search="ComboBox",
diff --git a/src/pvi/_format/widget.py b/src/pvi/_format/widget.py
index 8ce5c4f8..3a507a79 100644
--- a/src/pvi/_format/widget.py
+++ b/src/pvi/_format/widget.py
@@ -19,6 +19,7 @@
TableWrite,
TextRead,
TextWrite,
+ ToggleButton,
WidgetUnion,
)
@@ -266,6 +267,7 @@ class WidgetFormatterFactory(BaseModel, Generic[T]):
progress_bar_formatter_cls: Type[PVWidgetFormatter[T]]
text_read_formatter_cls: Type[PVWidgetFormatter[T]]
check_box_formatter_cls: Type[PVWidgetFormatter[T]]
+ toggle_formatter_cls: Type[PVWidgetFormatter[T]]
combo_box_formatter_cls: Type[PVWidgetFormatter[T]]
text_write_formatter_cls: Type[PVWidgetFormatter[T]]
table_formatter_cls: Type[PVWidgetFormatter[T]]
@@ -300,6 +302,7 @@ def pv_widget_formatter(
TextRead: self.text_read_formatter_cls,
TableRead: self.table_formatter_cls,
CheckBox: self.check_box_formatter_cls,
+ ToggleButton: self.toggle_formatter_cls,
ComboBox: self.combo_box_formatter_cls,
TextWrite: self.text_write_formatter_cls,
TableWrite: self.table_formatter_cls,
diff --git a/src/pvi/device.py b/src/pvi/device.py
index 2b69dddb..120d934b 100644
--- a/src/pvi/device.py
+++ b/src/pvi/device.py
@@ -139,7 +139,15 @@ class WriteWidget(TypedModel, AccessModeMixin):
class CheckBox(WriteWidget):
- """Checkable control of a boolean PV"""
+ """Checkable control of a boolean PV.
+
+ This is compact replacement for a `ToggleButton` to be used in rows and tables.
+
+ """
+
+
+class ToggleButton(WriteWidget):
+ """A pair of buttons to select between two mutually exclusive states."""
class ComboBox(WriteWidget):
@@ -243,7 +251,13 @@ class SubScreen(Layout):
)
WriteWidgetUnion = (
- ArrayWrite | ButtonPanel | CheckBox | ComboBox | TableWrite | TextWrite
+ ArrayWrite
+ | ButtonPanel
+ | CheckBox
+ | ComboBox
+ | TableWrite
+ | TextWrite
+ | ToggleButton
)
if not TYPE_CHECKING:
diff --git a/tests/convert/input/simDetector.template b/tests/convert/input/simDetector.template
index 573979ee..36e6f3ab 100644
--- a/tests/convert/input/simDetector.template
+++ b/tests/convert/input/simDetector.template
@@ -637,3 +637,10 @@ record(waveform, "$(P)$(R)Dimensions_RBV")
field(FLNK, "$(P)$(R)Dim0SA_RBV")
field(SCAN, "I/O Intr")
}
+record(bo, "$(P)$(R)ResetPower")
+{
+ field(DTYP, "asynInt32")
+ field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))RESET_POWER")
+ field(ZNAM, "Done")
+ field(ONAM, "Reset")
+}
diff --git a/tests/convert/output/simDetector.pvi.device.yaml b/tests/convert/output/simDetector.pvi.device.yaml
index d41e237f..7b4b775c 100644
--- a/tests/convert/output/simDetector.pvi.device.yaml
+++ b/tests/convert/output/simDetector.pvi.device.yaml
@@ -9,6 +9,12 @@ children:
labelled: true
children:
+ - type: SignalW
+ name: ResetPower
+ write_pv: $(P)$(R)ResetPower
+ write_widget:
+ type: ToggleButton
+
- type: SignalR
name: Read
read_pv: $(P)$(R)Read
diff --git a/tests/format/input/asynNDArrayDriver.pvi.device.yaml b/tests/format/input/asynNDArrayDriver.pvi.device.yaml
index a81253ac..d2b38cd4 100644
--- a/tests/format/input/asynNDArrayDriver.pvi.device.yaml
+++ b/tests/format/input/asynNDArrayDriver.pvi.device.yaml
@@ -14,13 +14,13 @@ children:
name: WaitForPlugins
pv: $(P)$(R)WaitForPlugins
widget:
- type: CheckBox
+ type: ToggleButton
- type: SignalW
name: EmptyFreeList
pv: $(P)$(R)EmptyFreeList
widget:
- type: CheckBox
+ type: ToggleButton
- type: SignalW
name: NDAttributesMacros
@@ -196,7 +196,7 @@ children:
name: Acquire
pv: $(P)$(R)Acquire
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: Acquire_RBV
read_widget:
type: LED
@@ -250,7 +250,7 @@ children:
name: ArrayCallbacks
pv: $(P)$(R)ArrayCallbacks
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: ArrayCallbacks_RBV
read_widget:
type: LED
@@ -344,7 +344,7 @@ children:
name: AutoIncrement
pv: $(P)$(R)AutoIncrement
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: AutoIncrement_RBV
read_widget:
type: LED
@@ -364,7 +364,7 @@ children:
name: AutoSave
pv: $(P)$(R)AutoSave
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: AutoSave_RBV
read_widget:
type: LED
@@ -373,7 +373,7 @@ children:
name: WriteFile
pv: $(P)$(R)WriteFile
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: WriteFile_RBV
read_widget:
type: LED
@@ -382,7 +382,7 @@ children:
name: ReadFile
pv: $(P)$(R)ReadFile
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: ReadFile_RBV
read_widget:
type: LED
@@ -409,7 +409,7 @@ children:
name: Capture
pv: $(P)$(R)Capture
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: Capture_RBV
read_widget:
type: LED
@@ -427,7 +427,7 @@ children:
name: DeleteDriverFile
pv: $(P)$(R)DeleteDriverFile
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: DeleteDriverFile_RBV
read_widget:
type: LED
@@ -436,7 +436,7 @@ children:
name: LazyOpen
pv: $(P)$(R)LazyOpen
widget:
- type: CheckBox
+ type: ToggleButton
read_pv: LazyOpen_RBV
read_widget:
type: LED
diff --git a/tests/format/input/mixedWidgets.pvi.device.yaml b/tests/format/input/mixedWidgets.pvi.device.yaml
index 47aae162..a13e77a9 100644
--- a/tests/format/input/mixedWidgets.pvi.device.yaml
+++ b/tests/format/input/mixedWidgets.pvi.device.yaml
@@ -13,13 +13,13 @@ children:
name: ResetPower
write_pv: $(P)$(R)ResetPower
write_widget:
- type: CheckBox
+ type: ToggleButton
- type: SignalW
name: ThresholdApply
write_pv: $(P)$(R)ThresholdApply
write_widget:
- type: CheckBox
+ type: ToggleButton
- type: SignalW
name: ImageFileTmot
@@ -298,7 +298,7 @@ children:
name: ThresholdAutoApply
write_pv: $(P)$(R)ThresholdAutoApply
write_widget:
- type: CheckBox
+ type: ToggleButton
read_pv: $(P)$(R)ThresholdAutoApply_RBV
read_widget:
type: LED
diff --git a/tests/format/output/mixedWidgets.bob b/tests/format/output/mixedWidgets.bob
index 2c892ef3..94f994c6 100644
--- a/tests/format/output/mixedWidgets.bob
+++ b/tests/format/output/mixedWidgets.bob
@@ -41,17 +41,12 @@
20
- ChoiceButton
+ ToggleButton
$(P)$(R)ResetPower
124
0
124
20
-
- - 0
- - 1
- - 2
-
Label
@@ -62,17 +57,12 @@
20
- ChoiceButton
+ ToggleButton
$(P)$(R)ThresholdApply
124
24
124
20
-
- - 0
- - 1
- - 2
-
Label
@@ -886,17 +876,12 @@
20
- ChoiceButton
+ ToggleButton
$(P)$(R)ThresholdAutoApply
124
24
60
20
-
- - 0
- - 1
- - 2
-
LED
diff --git a/tests/format/output/mixedWidgets.edl b/tests/format/output/mixedWidgets.edl
index 0840c3a4..8f1f0f40 100644
--- a/tests/format/output/mixedWidgets.edl
+++ b/tests/format/output/mixedWidgets.edl
@@ -99,8 +99,8 @@ value {
}
endObjectProperties
-# (Menu Button)
-object activeMenuButtonClass
+# (Choice Button)
+object activeChoiceButtonClass
beginObjectProperties
major 4
minor 0
@@ -109,13 +109,16 @@ x 165
y 45
w 205
h 20
-fgColor index 25
+fgColor index 14
bgColor index 3
-inconsistentColor index 0
+selectColor index 3
+inconsistentColor index 14
topShadowColor index 1
botShadowColor index 11
controlPv "$(P)$(R)ResetPower"
-font "arial-bold-r-12.0"
+indicatorPv "None"
+font "arial-bold-r-14.0"
+orientation "horizontal"
endObjectProperties
# (Static Text)
@@ -137,8 +140,8 @@ value {
}
endObjectProperties
-# (Menu Button)
-object activeMenuButtonClass
+# (Choice Button)
+object activeChoiceButtonClass
beginObjectProperties
major 4
minor 0
@@ -147,13 +150,16 @@ x 165
y 70
w 205
h 20
-fgColor index 25
+fgColor index 14
bgColor index 3
-inconsistentColor index 0
+selectColor index 3
+inconsistentColor index 14
topShadowColor index 1
botShadowColor index 11
controlPv "$(P)$(R)ThresholdApply"
-font "arial-bold-r-12.0"
+indicatorPv "None"
+font "arial-bold-r-14.0"
+orientation "horizontal"
endObjectProperties
# (Static Text)
@@ -1898,8 +1904,8 @@ value {
}
endObjectProperties
-# (Menu Button)
-object activeMenuButtonClass
+# (Choice Button)
+object activeChoiceButtonClass
beginObjectProperties
major 4
minor 0
@@ -1908,13 +1914,16 @@ x 540
y 515
w 100
h 20
-fgColor index 25
+fgColor index 14
bgColor index 3
-inconsistentColor index 0
+selectColor index 3
+inconsistentColor index 14
topShadowColor index 1
botShadowColor index 11
controlPv "$(P)$(R)ThresholdAutoApply"
-font "arial-bold-r-12.0"
+indicatorPv "None"
+font "arial-bold-r-14.0"
+orientation "horizontal"
endObjectProperties
# (Byte)