Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Buttons #31

Merged
merged 4 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions schemas/pvi.device.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@
{
"$ref": "#/definitions/ComboBox"
},
{
"$ref": "#/definitions/ButtonPanel"
},
{
"$ref": "#/definitions/TextWrite"
},
Expand Down Expand Up @@ -326,6 +329,23 @@
},
"additionalProperties": false
},
"ButtonPanel": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "ButtonPanel",
"default": "ButtonPanel"
},
"actions": {
"type": "object",
"default": {
"go": 1
}
}
},
"additionalProperties": false
},
"TextWrite": {
"type": "object",
"properties": {
Expand Down
2 changes: 1 addition & 1 deletion src/pvi/_format/aps.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def format(self, device: Device, prefix: str, path: Path):
group_width_offset=0,
)
widget_formatter_factory = WidgetFormatterFactory(
heading_formatter_cls=LabelWidgetFormatter.from_template(
header_formatter_cls=LabelWidgetFormatter.from_template(
template,
search='"Heading"',
property_map=dict(textix="text"),
Expand Down
4 changes: 2 additions & 2 deletions src/pvi/_format/dls.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def format_edl(self, device: Device, prefix: str, path: Path):
group_width_offset=0,
)
widget_formatter_factory = WidgetFormatterFactory(
heading_formatter_cls=LabelWidgetFormatter.from_template(
header_formatter_cls=LabelWidgetFormatter.from_template(
template,
search='"Heading"',
property_map=dict(value="text"),
Expand Down Expand Up @@ -206,7 +206,7 @@ def format_bob(self, device: Device, prefix: str, path: Path):
)
# SW DOCS REF: Extract widget types from template file
widget_formatter_factory = WidgetFormatterFactory(
heading_formatter_cls=LabelWidgetFormatter.from_template(
header_formatter_cls=LabelWidgetFormatter.from_template(
template,
search="Heading",
property_map=dict(text="text"),
Expand Down
207 changes: 110 additions & 97 deletions src/pvi/_format/screen.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, Iterator, List, Tuple, Type, TypeVar, Union
from typing import Dict, Iterator, List, Sequence, Tuple, Type, TypeVar, Union

from typing_extensions import Annotated

from pvi._format.bob import is_table
from pvi._format.utils import Bounds, indent_widget
from pvi._format.utils import Bounds
from pvi._format.widget import (
GroupFormatter,
SubScreenWidgetFormatter,
Expand All @@ -19,10 +19,12 @@
)
from pvi._schema_utils import desc
from pvi.device import (
ButtonPanel,
Component,
Generic,
Grid,
Group,
ReadSignalType,
Row,
SignalR,
SignalRef,
Expand All @@ -32,6 +34,7 @@
SubScreen,
TableWidgetTypes,
Tree,
WriteSignalType,
)

T = TypeVar("T")
Expand Down Expand Up @@ -117,10 +120,11 @@ def create_screen_formatter(
screen_widgets.extend(
self.create_component_widget_formatters(
c,
column_bounds=last_column_bounds,
parent_bounds=screen_bounds,
column_bounds=last_column_bounds,
next_column_bounds=next_column_bounds,
group_widget_indent=self.layout.group_widget_indent,
# Indent top-level widgets to align with Group widgets
indent=True,
)
)

Expand Down Expand Up @@ -214,16 +218,15 @@ def create_group_formatters(
# embedding the components within a Group widget
return self.create_component_widget_formatters(
Group(c.name, SubScreen(), c.children),
column_bounds=column_bounds,
parent_bounds=screen_bounds,
column_bounds=column_bounds,
next_column_bounds=next_column_bounds,
group_widget_indent=self.layout.group_widget_indent,
indent=True,
add_label=True,
)

group_formatter = self.create_group_formatter(
c,
bounds=Bounds(column_bounds.x, column_bounds.y, h=screen_bounds.h),
c, bounds=Bounds(column_bounds.x, column_bounds.y, h=screen_bounds.h)
)

if group_formatter.bounds.h + group_formatter.bounds.y <= screen_bounds.h:
Expand Down Expand Up @@ -298,8 +301,8 @@ def create_group_formatter(
widget_factories.extend(
self.create_component_widget_formatters(
component,
column_bounds=column_bounds,
parent_bounds=bounds,
column_bounds=column_bounds,
next_column_bounds=next_column_bounds,
add_label=group.layout.labelled,
)
Expand All @@ -315,11 +318,11 @@ def create_group_formatter(
def create_component_widget_formatters(
self,
c: Union[Group[Component], Component],
column_bounds: Bounds,
parent_bounds: Bounds,
column_bounds: Bounds,
next_column_bounds: Bounds,
indent=False,
add_label=True,
group_widget_indent: int = 0,
) -> List[WidgetFormatter[T]]:
"""Generate widgets from component data and position them in a grid format

Expand All @@ -331,7 +334,8 @@ def create_component_widget_formatters(
height limits
add_label: Whether the widget should have an associated label.
Defaults to True.
group_widget_indent: The x offset of widgets within groups.
indent: Shift the resulting widgets to the group ident level
Used for top-level widgets that are not inside a group.

Returns:
A collection of widgets representing the component
Expand All @@ -341,29 +345,33 @@ def create_component_widget_formatters(
added widget

"""
# Take copies so we don't modify the originals until we're done
tmp_column_bounds = column_bounds.copy()
tmp_next_column_bounds = next_column_bounds.copy()

if indent:
tmp_column_bounds.indent(self.layout.group_widget_indent)
tmp_next_column_bounds.indent(self.layout.group_widget_indent)

widgets = list(
self.generate_component_formatters(
c, column_bounds, group_widget_indent, add_label
)
self.generate_component_formatters(c, tmp_column_bounds, add_label)
)
if max_y(widgets) > parent_bounds.h:
# Add to next column
if max_y(widgets) <= parent_bounds.h:
# Current column still fits on screen
column_bounds.y = next_y(widgets, self.layout.spacing)
else:
# Widget makes current column too tall. Repeat in next column.
widgets = list(
self.generate_component_formatters(
c, next_column_bounds, group_widget_indent, add_label
)
self.generate_component_formatters(c, tmp_next_column_bounds, add_label)
)
next_column_bounds.y = next_y(widgets, self.layout.spacing)
else:
column_bounds.y = next_y(widgets, self.layout.spacing)

return widgets

def generate_component_formatters(
self,
c: Union[Group[Component], Component],
bounds: Bounds,
group_widget_indent: int,
add_label=True,
) -> Iterator[WidgetFormatter[T]]:
"""Convert a component into its WidgetFormatter equivalents
Expand All @@ -385,17 +393,17 @@ def generate_component_formatters(
component_bounds = bounds.copy()

if isinstance(c, Group) and isinstance(c.layout, Row):
# This Group should be formatted as a table - check if headers are required
# This Group should be formatted as a table
if c.layout.header is not None:
# Create table header
assert len(c.layout.header) == len(
c.children
), "Header length does not match number of elements"

# Create column headers
for column_header in c.layout.header:
yield self.widget_formatter_factory.heading_formatter_cls(
indent_widget(component_bounds, group_widget_indent),
column_header,
yield self.widget_formatter_factory.header_formatter_cls(
component_bounds.copy(), column_header
)
component_bounds.x += component_bounds.w + self.layout.spacing

Expand All @@ -404,91 +412,96 @@ def generate_component_formatters(
component_bounds.y += self.layout.widget_height + self.layout.spacing

add_label = False # Don't add a row label
sub_components = c.children # Create a widget for each row of Group
row_components = c.children # Create a widget for each row of Group
# Allow given component width for each column, plus spacing
component_bounds = component_bounds.tile(
horizontal=len(c.children), spacing=self.layout.spacing
)
elif (
isinstance(c, (SignalW, SignalRW))
and hasattr(c, "widget")
and isinstance(c.widget, ButtonPanel)
):
# Convert W of Signal(R)W into SignalX for each button
row_components = [
SignalX(label, c.pv, value) for label, value in c.widget.actions.items()
]
if isinstance(c, SignalRW):
row_components += [SignalR(c.label, c.read_pv, c.read_widget)]
else:
sub_components = [c] # Create one widget for Group/Component
row_components = [c] # Create one widget for row

if hasattr(c, "widget") and isinstance(c.widget, TableWidgetTypes):
add_label = False # Do not add row labels for Tables
component_bounds.w = 100 * len(c.widget.widgets)
component_bounds.h *= 10 # TODO: How do we know the number of rows?

if add_label:
# Insert label and reduce width for widget
left, row_bounds = component_bounds.split(
left, row_bounds = component_bounds.split_left(
self.layout.label_width, self.layout.spacing
)
yield self.widget_formatter_factory.label_formatter_cls(
indent_widget(left, group_widget_indent), c.get_label()
)
yield self.widget_formatter_factory.label_formatter_cls(left, c.get_label())
else:
# Allow full width for widget
row_bounds = component_bounds

# Actual widgets
sub_components = (
c.children if isinstance(c, Group) and isinstance(c.layout, Row) else [c]
if isinstance(c, SignalRef):
yield from self.generate_component_formatters(
self.components[c.name], row_bounds, add_label
)
return

yield from self.generate_row_component_formatters(row_components, row_bounds)

def generate_row_component_formatters(
self,
row_components: Sequence[Union[Group[Component], Component]],
row_bounds: Bounds,
) -> Iterator[WidgetFormatter[T]]:

row_component_bounds = row_bounds.copy().split_into(
len(row_components), self.layout.spacing
)
for sc in sub_components:
if isinstance(sc, SignalX):
for rc_bounds, rc in zip(row_component_bounds, row_components):
if isinstance(rc, SignalRW):
left, right = rc_bounds.split_into(2, self.layout.spacing)
yield from self.generate_write_widget(rc, left)
yield from self.generate_read_widget(rc, right)
elif isinstance(rc, SignalW):
yield from self.generate_write_widget(rc, rc_bounds)
elif isinstance(rc, SignalR):
yield from self.generate_read_widget(rc, rc_bounds)
elif isinstance(rc, SignalX):
yield self.widget_formatter_factory.action_formatter_cls(
indent_widget(row_bounds, group_widget_indent),
sc.get_label(),
self.prefix + sc.pv,
sc.value,
)
elif isinstance(sc, SignalR) and sc.widget:
if (
isinstance(sc.widget, TableWidgetTypes)
and len(sc.widget.widgets) > 0
):
widget_bounds = row_bounds.copy()
widget_bounds.w = 100 * len(sc.widget.widgets)
widget_bounds.h *= 10 # TODO: How do we know the number of rows?
else:
widget_bounds = row_bounds

yield self.widget_formatter_factory.pv_widget_formatter(
sc.widget,
indent_widget(widget_bounds, group_widget_indent),
sc.pv,
self.prefix,
)
elif (
isinstance(sc, SignalRW) and sc.read_pv and sc.read_widget and sc.widget
):
left, right = row_bounds.split(
int((row_bounds.w - self.layout.spacing) / 2), self.layout.spacing
rc_bounds,
rc.get_label(),
self.prefix + rc.pv,
rc.value,
)
yield self.widget_formatter_factory.pv_widget_formatter(
sc.widget,
indent_widget(left, group_widget_indent),
sc.pv,
self.prefix,
)
yield self.widget_formatter_factory.pv_widget_formatter(
sc.read_widget,
indent_widget(right, group_widget_indent),
sc.read_pv,
self.prefix,
)
elif isinstance(sc, (SignalW, SignalRW)) and sc.widget:
yield self.widget_formatter_factory.pv_widget_formatter(
sc.widget,
indent_widget(row_bounds, group_widget_indent),
sc.pv,
self.prefix,
)
elif isinstance(sc, SignalRef):
yield from self.generate_component_formatters(
self.components[sc.name],
indent_widget(row_bounds, group_widget_indent),
add_label,
)
elif isinstance(sc, Group) and isinstance(sc.layout, SubScreen):
elif isinstance(rc, Group) and isinstance(rc.layout, SubScreen):
yield self.widget_formatter_factory.sub_screen_formatter_cls(
indent_widget(row_bounds, group_widget_indent),
f"{self.base_file_name}_{sc.name}",
sc,
rc_bounds,
f"{self.base_file_name}_{rc.name}",
rc,
)
# TODO: Need to handle DeviceRef
# TODO: Need to handle DeviceRef

# Shift bounds along row for next widget
row_bounds.x += row_bounds.w + self.layout.spacing
def generate_read_widget(self, signal: ReadSignalType, bounds: Bounds):
if isinstance(signal, SignalRW):
widget = signal.read_widget
pv = signal.read_pv
else:
widget = signal.widget
pv = signal.pv

if widget is not None:
yield self.widget_formatter_factory.pv_widget_formatter(
widget, bounds, pv, self.prefix
)

def generate_write_widget(self, signal: WriteSignalType, bounds: Bounds):
if signal.widget is not None:
yield self.widget_formatter_factory.pv_widget_formatter(
signal.widget, bounds, signal.pv, self.prefix
)
Loading