diff --git a/schemas/pvi.device.schema.json b/schemas/pvi.device.schema.json index 46deff7d..78bb1a33 100644 --- a/schemas/pvi.device.schema.json +++ b/schemas/pvi.device.schema.json @@ -67,6 +67,13 @@ "title": "Labels", "type": "array" }, + "number_of_bits": { + "default": 8, + "description": "Number of bits to display", + "exclusiveMinimum": 0, + "title": "Number Of Bits", + "type": "integer" + }, "type": { "const": "BitField", "default": "BitField", @@ -339,6 +346,26 @@ "additionalProperties": false, "description": "Progress bar from lower to upper limit of a float PV", "properties": { + "use_pv_limits": { + "default": false, + "description": "Use PV limits, if True then maximum and minumum are ignored", + "title": "Use Pv Limits", + "type": "boolean" + }, + "minimum": { + "default": 0.0, + "description": "Lower limit of progress bar", + "exclusiveMinimum": 0.0, + "title": "Minimum", + "type": "number" + }, + "maximum": { + "default": 1.0, + "description": "Upper limit of progress bar", + "exclusiveMinimum": 0.0, + "title": "Maximum", + "type": "number" + }, "type": { "const": "ProgressBar", "default": "ProgressBar", diff --git a/src/pvi/_format/adl.py b/src/pvi/_format/adl.py index 1fbbb26f..66c853de 100644 --- a/src/pvi/_format/adl.py +++ b/src/pvi/_format/adl.py @@ -54,7 +54,9 @@ def set( case TextWrite(format=format) | TextRead(format=format) if ( is_text_widget(template) and format is not None ): - template = add_property(template, "format", ADL_TEXT_FORMATS[format]) + template = add_property( + template, "format", ADL_TEXT_FORMATS[TextFormat(format)] + ) return template diff --git a/src/pvi/_format/bob.py b/src/pvi/_format/bob.py index 5ea1b82b..e834360c 100644 --- a/src/pvi/_format/bob.py +++ b/src/pvi/_format/bob.py @@ -7,7 +7,10 @@ from pvi._format.widget import UITemplate, WidgetFormatter from pvi.device import ( LED, + BitField, ComboBox, + ImageRead, + ProgressBar, TableRead, TableWrite, TextFormat, @@ -48,6 +51,9 @@ def set( properties["y"] = bounds.y properties["width"] = bounds.w properties["height"] = bounds.h + if isinstance(widget, BitField): + properties["width"] = widget.number_of_bits * 20 + bounds.w = properties["width"] widget_type = template.attrib.get("type", "") @@ -81,7 +87,18 @@ def set( "textupdate", TextRead(format=format), ) if format is not None: - add_format(t_copy, BOB_TEXT_FORMATS[format]) + add_format(t_copy, BOB_TEXT_FORMATS[TextFormat(format)]) # type: ignore + case ("byte_monitor", BitField()): + add_byte_number_of_bits(t_copy, widget.number_of_bits) # type: ignore + case ("image", ImageRead()): + pass + case ("progressbar", ProgressBar()): + add_minimum_and_maximum_progressbar( + t_copy, + widget.use_pv_limits, # type: ignore + widget.minimum, # type: ignore + widget.maximum, # type: ignore + ) return t_copy @@ -206,6 +223,18 @@ def add_format(element: ElementBase, format: str): SubElement(element, "format").text = format +def add_byte_number_of_bits(element: ElementBase, number_of_bits: int): + SubElement(element, "numBits").text = str(number_of_bits) + + +def add_minimum_and_maximum_progressbar( + element: ElementBase, use_pv_limits: bool, minimum: float, maximum: float +): + SubElement(element, "limits_from_pv").text = "true" if use_pv_limits else "false" + SubElement(element, "minimum").text = str(minimum) + SubElement(element, "maximum").text = str(maximum) + + def find_element(root_element: ElementBase, tag: str, index: int = 0) -> ElementBase: """Iterate tree to find tag and replace text. @@ -213,7 +242,6 @@ def find_element(root_element: ElementBase, tag: str, index: int = 0) -> Element root_element: Root of tree to search tag: Tag to search for in tree index: Match to return if multiple matches are found - """ match list(root_element.iter(tag=tag)): case []: diff --git a/src/pvi/_format/dls.bob b/src/pvi/_format/dls.bob index 42bb7f3f..8f43ad9c 100644 --- a/src/pvi/_format/dls.bob +++ b/src/pvi/_format/dls.bob @@ -2,7 +2,7 @@ Display 12 - 12 + 0 1000 800 4 @@ -192,9 +192,57 @@ Table pva://Table - 300 - 300 + 671 + 670 200 100 + + + Column 1 + 100 + true + + + + + BitField + ByteMonitor + 172 + 412 + 165 + + + ArrayTrace + 348 + 412 + 230 + 230 + + + $(traces[0].y_pv) + + ArrayTrace + + 0 + 1 + + + + + 1 + 0 + 0 + 10 + true + + + + + ImageRead + ImageRead + 566 + 48 + 300 + 301 diff --git a/src/pvi/_format/dls.py b/src/pvi/_format/dls.py index c283bbbc..97bcb502 100644 --- a/src/pvi/_format/dls.py +++ b/src/pvi/_format/dls.py @@ -228,6 +228,26 @@ def format_bob(self, device: Device, path: Path): search="ProgressBar", property_map={"pv_name": "pv"}, ), + bitfield_formatter_cls=PVWidgetFormatter.from_template( + template, + search="BitField", + property_map={"pv_name": "pv"}, + ), + button_panel_formatter_cls=PVWidgetFormatter.from_template( + template, + search="ButtonPanel", + property_map={"pv_name": "pv"}, + ), + array_trace_formatter_cls=PVWidgetFormatter.from_template( + template, + search="ArrayTrace", + property_map={"y_pv": "pv"}, + ), + image_read_formatter_cls=PVWidgetFormatter.from_template( + template, + search="ImageRead", + property_map={"pv_name": "pv"}, + ), text_read_formatter_cls=PVWidgetFormatter.from_template( template, search="TextUpdate", diff --git a/src/pvi/_format/edl.py b/src/pvi/_format/edl.py index dd2800df..5ba73af4 100644 --- a/src/pvi/_format/edl.py +++ b/src/pvi/_format/edl.py @@ -56,7 +56,7 @@ def set( is_text_widget(template) and format is not None ): template = add_property( - template, "displayMode", EDL_TEXT_FORMATS[format] + template, "displayMode", EDL_TEXT_FORMATS[TextFormat(format)] ) return template diff --git a/src/pvi/_format/screen.py b/src/pvi/_format/screen.py index ddba4196..70ed06ef 100644 --- a/src/pvi/_format/screen.py +++ b/src/pvi/_format/screen.py @@ -454,7 +454,7 @@ def generate_component_formatters( for action, value in c.write_widget.actions.items() ] # If this is a SignalRW, recreate the readback with a SignalR - if isinstance(c, SignalRW): + if isinstance(c, SignalRW) and c.read_widget is not None: row_components += [ SignalR(name=c.name, read_pv=c.read_pv, read_widget=c.read_widget) ] diff --git a/src/pvi/_format/widget.py b/src/pvi/_format/widget.py index 6e477402..7d7ce24e 100644 --- a/src/pvi/_format/widget.py +++ b/src/pvi/_format/widget.py @@ -7,9 +7,13 @@ from pvi._format.utils import Bounds, GroupType from pvi.device import ( LED, + ArrayTrace, + BitField, + ButtonPanel, CheckBox, ComboBox, Group, + ImageRead, ProgressBar, TableRead, TableWrite, @@ -260,14 +264,17 @@ class WidgetFormatterFactory(BaseModel, Generic[T]): label_formatter_cls: Type[LabelWidgetFormatter[T]] led_formatter_cls: Type[PVWidgetFormatter[T]] progress_bar_formatter_cls: Type[PVWidgetFormatter[T]] - # TODO: add bitfield, progress_bar, plot, image text_read_formatter_cls: Type[PVWidgetFormatter[T]] check_box_formatter_cls: Type[PVWidgetFormatter[T]] combo_box_formatter_cls: Type[PVWidgetFormatter[T]] text_write_formatter_cls: Type[PVWidgetFormatter[T]] - table_formatter_cls: Type[PVWidgetFormatter] + table_formatter_cls: Type[PVWidgetFormatter[T]] action_formatter_cls: Type[ActionWidgetFormatter[T]] sub_screen_formatter_cls: Type[SubScreenWidgetFormatter[T]] + bitfield_formatter_cls: Optional[Type[PVWidgetFormatter[T]]] = None + array_trace_formatter_cls: Optional[Type[PVWidgetFormatter[T]]] = None + button_panel_formatter_cls: Optional[Type[PVWidgetFormatter[T]]] = None + image_read_formatter_cls: Optional[Type[PVWidgetFormatter[T]]] = None def pv_widget_formatter( self, @@ -286,7 +293,7 @@ def pv_widget_formatter( A WidgetFormatter representing the component """ - widget_formatter_classes: Dict[type, Type[PVWidgetFormatter[T]]] = { + widget_formatter_classes: Dict[type, Optional[Type[PVWidgetFormatter[T]]]] = { # Currently supported formatters of ReadWidget/WriteWidget Components LED: self.led_formatter_cls, ProgressBar: self.progress_bar_formatter_cls, @@ -296,11 +303,20 @@ def pv_widget_formatter( ComboBox: self.combo_box_formatter_cls, TextWrite: self.text_write_formatter_cls, TableWrite: self.table_formatter_cls, + BitField: self.bitfield_formatter_cls, + ButtonPanel: self.button_panel_formatter_cls, + ArrayTrace: self.array_trace_formatter_cls, + ImageRead: self.image_read_formatter_cls, } if isinstance(widget, (TextRead, TextWrite)): bounds.h *= widget.get_lines() widget_formatter_cls = widget_formatter_classes[type(widget)] + if widget_formatter_cls is None: + raise RuntimeError( + f"Unsupported widget type {type(widget)} for this widget formatter" + ) + return widget_formatter_cls(bounds=bounds, pv=pv, widget=widget) diff --git a/src/pvi/device.py b/src/pvi/device.py index 7ea6cc19..9228a5d2 100644 --- a/src/pvi/device.py +++ b/src/pvi/device.py @@ -2,7 +2,7 @@ import json import re -from enum import IntEnum +from enum import Enum from pathlib import Path from typing import ( TYPE_CHECKING, @@ -65,7 +65,7 @@ def enforce_pascal_case(s: str) -> str: return s[0].upper() + s[1:] -class TextFormat(IntEnum): +class TextFormat(Enum): """Format to use for display of Text{Read,Write} widgets on a UI""" decimal = 0 @@ -95,11 +95,21 @@ class BitField(ReadWidget): """LED and label for each bit of an int PV""" labels: Sequence[str] = Field(description="Label for each bit") + number_of_bits: int = Field( + default=8, description="Number of bits to display", gt=0 + ) class ProgressBar(ReadWidget): """Progress bar from lower to upper limit of a float PV""" + use_pv_limits: bool = Field( + default=False, + description="Use PV limits, if True then maximum and minumum are ignored", + ) + minimum: float = Field(default=0.0, description="Lower limit of progress bar", gt=0) + maximum: float = Field(default=1.0, description="Upper limit of progress bar", gt=0) + class TextRead(ReadWidget): """Text view of any PV""" diff --git a/tests/format/output/button.bob b/tests/format/output/button.bob index a224fbbd..96fb1193 100644 --- a/tests/format/output/button.bob +++ b/tests/format/output/button.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 274 102 4 diff --git a/tests/format/output/combo_box.bob b/tests/format/output/combo_box.bob index 11475a00..707f5188 100644 --- a/tests/format/output/combo_box.bob +++ b/tests/format/output/combo_box.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 274 54 4 diff --git a/tests/format/output/device_ref.bob b/tests/format/output/device_ref.bob index cca5f681..e05cfdab 100644 --- a/tests/format/output/device_ref.bob +++ b/tests/format/output/device_ref.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 274 54 4 diff --git a/tests/format/output/index.bob b/tests/format/output/index.bob index 566a94a5..c5daf3c6 100644 --- a/tests/format/output/index.bob +++ b/tests/format/output/index.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 388 105 4 diff --git a/tests/format/output/mixedWidgets.bob b/tests/format/output/mixedWidgets.bob index bfe1bc39..ee45057c 100644 --- a/tests/format/output/mixedWidgets.bob +++ b/tests/format/output/mixedWidgets.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 576 688 4 @@ -1026,6 +1026,9 @@ 144 124 20 + false + 0.0 + 1.0 diff --git a/tests/format/output/pva_table.bob b/tests/format/output/pva_table.bob index d29e523e..1dfe7317 100644 --- a/tests/format/output/pva_table.bob +++ b/tests/format/output/pva_table.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 226 234 4 @@ -32,6 +32,13 @@ 30 200 200 + + + Column 1 + 100 + true + + Column 1 diff --git a/tests/format/output/pva_table_panda.bob b/tests/format/output/pva_table_panda.bob index 28b297e6..380b1132 100644 --- a/tests/format/output/pva_table_panda.bob +++ b/tests/format/output/pva_table_panda.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 1726 234 4 @@ -32,6 +32,13 @@ 30 1700 200 + + + Column 1 + 100 + true + + Column 1 diff --git a/tests/format/output/static_table.bob b/tests/format/output/static_table.bob index 49713e8a..0b66042f 100644 --- a/tests/format/output/static_table.bob +++ b/tests/format/output/static_table.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 274 78 4 diff --git a/tests/format/output/static_table_BigTable.bob b/tests/format/output/static_table_BigTable.bob index d2edb690..792f1cea 100644 --- a/tests/format/output/static_table_BigTable.bob +++ b/tests/format/output/static_table_BigTable.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 1030 150 4 diff --git a/tests/format/output/sub_screen.bob b/tests/format/output/sub_screen.bob index c5f1ebfa..080d3c20 100644 --- a/tests/format/output/sub_screen.bob +++ b/tests/format/output/sub_screen.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 290 160 4 diff --git a/tests/format/output/sub_screen_Group1.bob b/tests/format/output/sub_screen_Group1.bob index 4bbad837..78af2914 100644 --- a/tests/format/output/sub_screen_Group1.bob +++ b/tests/format/output/sub_screen_Group1.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 290 136 4 diff --git a/tests/format/output/sub_screen_Group4.bob b/tests/format/output/sub_screen_Group4.bob index e8736cf2..c13d2f21 100644 --- a/tests/format/output/sub_screen_Group4.bob +++ b/tests/format/output/sub_screen_Group4.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 290 136 4 diff --git a/tests/format/output/text_format.bob b/tests/format/output/text_format.bob index 84dca35d..b8cbc6c7 100644 --- a/tests/format/output/text_format.bob +++ b/tests/format/output/text_format.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 274 150 4