diff --git a/docs/developer/explanations/original-design.rst b/docs/explanations/original-design.md similarity index 56% rename from docs/developer/explanations/original-design.rst rename to docs/explanations/original-design.md index 9f94b4fd..d76bddf9 100644 --- a/docs/developer/explanations/original-design.rst +++ b/docs/explanations/original-design.md @@ -1,12 +1,13 @@ -Original Design -=============== +# Original Design -.. note:: - This page was the initial plan for what PVI would do. The produce features have now - been removed and only direct conversion to a Device representation and then - formatting of UIs is supported now. This page is kept now to record the design in - case full integration into areaDetector is revisited in the future. +:::{note} +This page was the initial plan for what PVI would do. The produce features have now +been removed and only direct conversion to a Device representation and then +formatting of UIs is supported now. This page is kept now to record the design in +case full integration into areaDetector is revisited in the future. +::: +```{eval-rst} .. digraph:: pvi_flowchart bgcolor=transparent @@ -37,7 +38,9 @@ Original Design "pilatus_parameters.adl" -> "pilatus.adl" [label="linked from"] "pilatus_parameters.edl" -> "pilatus.edl" [label="linked from"] "pilatus_parameters.opi" -> "pilatus.opi" [label="linked from"] +``` +```{eval-rst} .. list-table:: Aims of PVI :widths: 20, 80 :header-rows: 1 @@ -61,9 +64,9 @@ Original Design type, pv and widget), and lets the site specific template generate the screen according to local styles +``` -How it works ------------- +## How it works The YAML file contains information about each asyn parameter that will be exposed by the driver, it's name, type, description, initial value, which record @@ -73,11 +76,11 @@ Channel and AsynParam objects. These are passed to a site specific Formatter whi takes the tree of intermediate objects and writes a parameter CPP file, database template, and site specific screens to disk. -YAML file -~~~~~~~~~ +### YAML file The YAML file is formed of a number of sections: +```{eval-rst} .. list-table:: :widths: 20, 80 :header-rows: 1 @@ -95,12 +98,14 @@ The YAML file is formed of a number of sections: * - components - Tree of Components for each logical asyn parameter arranged in logical GUI groups +``` The Components are created from the YAML file with local overrides (also incorporating the base classes for screens). These are passed to the Producer which produces AsynParameters, Records and Channels. These are then passed to the Formatter which outputs them to file: +```{eval-rst} .. digraph:: pvi_products bgcolor=transparent @@ -111,16 +116,16 @@ outputs them to file: Products [label="Template\nScreens\nDriver Params\nDocumentation"] {rank=same; Components -> Producer -> Intermediate -> Formatter -> Products} +``` Here's a cut down pilatus.yaml file that might describe a parameter in a detector: -.. literalinclude:: ../../snippets/pilatusDetector.pvi.producer.yaml - :language: yaml +```{literalinclude} ../../snippets/pilatusDetector.pvi.producer.yaml +:language: yaml +``` - -Screen files -~~~~~~~~~~~~ +### Screen files The intermediate objects are a number of Channel instances. These contain basic types (like Combo, TextInput, TextUpdate, LED, Group) and some creation hints @@ -136,26 +141,25 @@ little screens with one group per screen. Styling is also covered, so the blue/grey MEDM screens and green/grey EDM screens can be customized to fit the site style guide. -HTML Documentation -~~~~~~~~~~~~~~~~~~ +### HTML Documentation The Parameter and record sections of the existing documentation could be reproduced, in tabular form as a csv file that can be included in rst docs: +```{eval-rst} .. csv-table:: Pilatus Parameters :file: ../../snippets/pilatusParameters.csv :widths: 15, 10, 8, 25, 25, 10, 60 :header-rows: 1 +``` -Questions ---------- +## Questions I am fairly happy with the scheme set out above, but there are a lot of implementation questions. Here are the most pressing: -One-time generation and checked into source control or generated by Makefile? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### One-time generation and checked into source control or generated by Makefile? The process would probably be: @@ -166,63 +170,49 @@ The process would probably be: ADGenICam would be supported by building a GenICamProducer which took no components, just a path to a GenICam XML file - -Which screen tools to support? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Which screen tools to support? I suggest creating adl and edl files initially, following the example of makeAdl.py in ADGenICam, then expanding to support opi, bob and ui files natively. This would avoid needing screen converters installed - -.. _YAML: - https://en.wikipedia.org/wiki/YAML - - -Drivers -======== +# Drivers The generated header file contains the string parameters, and defines the parameters to make the interface. In this example we have a header file pilatusDetectorParamSet.h: -.. literalinclude:: ../../snippets/pilatusDetectorParamSet.h - :language: cpp - +```{literalinclude} ../../snippets/pilatusDetectorParamSet.h +:language: cpp +``` The existing pilatus.cpp is then modified to remove these parameters definitions and use the param set API. - -Database Template File ----------------------- +## Database Template File According to the demand and readback properties of the component, the following records are created: -.. literalinclude:: ../../snippets/pilatusDetectorParameters.template - +```{literalinclude} ../../snippets/pilatusDetectorParameters.template +``` The top level pilatus.template includes this file, as well as records that provide logic (for things like the arrayRate and EPICSShutter in areaDetector). - -UI --- +## UI Finally, UI elements can be generated for each component for multiple graphical applications. For example, the following EDM screen is generated: -.. image:: ../../images/pilatus_edl.png - :width: 50% - :align: center - +```{image} ../../images/pilatus_edl.png +:align: center +:width: 50% +``` This can serve as a low level overview of the entire system, as well as a convenient pallette for constructing higher level, more structured screens. - -Ongoing Development -------------------- +## Ongoing Development Once a module is working with PVI (either an existing module after the YAML is created from the templates using the one time generation script, or a newly written module) it @@ -230,218 +220,202 @@ will then be necessary to update the YAML file in future development. Here is an example of necessary changes to add a new parameter, with and without PVI: -With PVI -~~~~~~~~ +### With PVI Update YAML file: -.. code-block:: YAML - - - type: AsynFloat64 - name: DelayTime - description: Delay in seconds between the external trigger and the start of image acquisition - role: Setting - initial: 0 - record_fields: - PREC: 3 - EGU: s +```YAML +- type: AsynFloat64 + name: DelayTime + description: Delay in seconds between the external trigger and the start of image acquisition + role: Setting + initial: 0 + record_fields: + PREC: 3 + EGU: s +``` And then run pvi (or possibly just make, if it is integrated into a Makefile). It can then be shared with other sites who can generate their own required files. - -Without PVI -~~~~~~~~~~~ +### Without PVI Update template: -.. code-block:: cpp - - # Delay time in External Trigger mode. - record(ao, "$(P)$(R)DelayTime") - { - field(PINI, "YES") - field(DTYP, "asynFloat64") - field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))DELAY_TIME") - field(EGU, "s") - field(VAL, "0") - field(PREC, "6") - } - - record(ai, "$(P)$(R)DelayTime_RBV") - { - field(DTYP, "asynFloat64") - field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))DELAY_TIME") - field(EGU, "s") - field(PREC, "6") - field(SCAN, "I/O Intr") - } - +```cpp +# Delay time in External Trigger mode. +record(ao, "$(P)$(R)DelayTime") +{ + field(PINI, "YES") + field(DTYP, "asynFloat64") + field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))DELAY_TIME") + field(EGU, "s") + field(VAL, "0") + field(PREC, "6") +} + +record(ai, "$(P)$(R)DelayTime_RBV") +{ + field(DTYP, "asynFloat64") + field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))DELAY_TIME") + field(EGU, "s") + field(PREC, "6") + field(SCAN, "I/O Intr") +} +``` Update header file: -.. code-block:: cpp - - ... - #define PilatusDelayTimeString "DELAY_TIME" - ... - createParam(PilatusDelayTimeString, asynParamFloat64, &PilatusDelayTime); - ... - int PilatusDelayTime; - ... - +```cpp +... +#define PilatusDelayTimeString "DELAY_TIME" +... +createParam(PilatusDelayTimeString, asynParamFloat64, &PilatusDelayTime); +... +int PilatusDelayTime; +... +``` Update docs: -.. code-block:: rst - - * - Delay in seconds between the external trigger and the start of image acquisition - - DELAY_TIME - - $(P)$(R)DelayTime - - ao - +```rst +* - Delay in seconds between the external trigger and the start of image acquisition + - DELAY_TIME + - $(P)$(R)DelayTime + - ao +``` Update screens (of course, this will actually involve editing with a graphical interface): -.. code-block:: javascript - - "text update" { - object { - x=604 - y=146 - width=80 - height=18 - } - monitor { - chan="$(P)$(R)DelayTime_RBV" - clr=54 - bclr=4 - } - align="horiz. centered" - limits { - } +```javascript +"text update" { + object { + x=604 + y=146 + width=80 + height=18 } - "text entry" { - object { - x=540 - y=145 - width=59 - height=20 - } - control { - chan="$(P)$(R)DelayTime" - clr=14 - bclr=51 - } - limits { - } + monitor { + chan="$(P)$(R)DelayTime_RBV" + clr=54 + bclr=4 } - text { - object { - x=435 - y=145 - width=100 - height=20 - } - "basic attribute" { - clr=14 - } - textix="Delay time" - align="horiz. right" + align="horiz. centered" + limits { } +} +"text entry" { + object { + x=540 + y=145 + width=59 + height=20 + } + control { + chan="$(P)$(R)DelayTime" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=435 + y=145 + width=100 + height=20 + } + "basic attribute" { + clr=14 + } + textix="Delay time" + align="horiz. right" +} +``` Then either add equivalent changes to other screen types or use autoconvert, if available, and add any site specific details to any of these files (such as autosave and archiver tags). - -Class Hierarchy ---------------- +## Class Hierarchy Drivers will access their parameters via a param set, either using inheritance or composition. The class hierarchy for param sets mirrors the drivers. Each of the 'base' classes (classes not instantiated directly) has-a paramSet containing its parameters in addition to its parent class(es). The most derived classes inherit their param sets so that they have direct access to all parameter indexes (and so that the source code -does not have to change to insert ``paramSet->`` to access them). +does not have to change to insert `paramSet->` to access them). -There is a new method ``asynPortDriver::createParams`` that iterates the member vector -of ``asynParamSet`` storing parameter definitions and calls -``asynPortDriver::createParam`` (no 's') on each of them. If the vector is empty -(i.e. if it only has the default ``asynParamSet`` and not a specific implementation) -it has no effect. This means ``asynPortDriver`` can be inherited from as before with +There is a new method `asynPortDriver::createParams` that iterates the member vector +of `asynParamSet` storing parameter definitions and calls +`asynPortDriver::createParam` (no 's') on each of them. If the vector is empty +(i.e. if it only has the default `asynParamSet` and not a specific implementation) +it has no effect. This means `asynPortDriver` can be inherited from as before with no change. Virtual inheritance is required for two reasons. Primarily, it ensures only a single -instance of ``asynParamSet`` is created and it is shared throughout the class +instance of `asynParamSet` is created and it is shared throughout the class hierarchy to ensure asynPortDriver can find the child parameters. It also means that the most derived class must call the constructors for all virtual base classes before the non-virtual base classes. This means the constructors are called in the correct -order such that when the asynPortDriver constructor the ``asynParamSet`` -``parameterDefinitions`` is fully populated when ``createParams`` is called. - -Change Summary -~~~~~~~~~~~~~~ - - * asyn - https://github.com/dls-controls/asyn/tree/pvi - * Created ``asynParamSet`` - * New overloaded asynPortDriver constructor that takes an ``asynParamSet*`` - and calls createParams() - - * ADCore - https://github.com/dls-controls/ADCore/tree/pvi - * ``asynNDArrayDriver`` parameters split into ``asynNDArrayDriverParamSet`` - Constructor updated to take an ``asynNDArrayDriverParamSet*``. - Updated to access parameters via ``paramSet->`` - * ``ADDriver`` parameters split into ``ADDriverParamSet`` - Constructor updated to take an ``ADDriverParamSet*``. - Updated to access parameters via ``paramSet->`` - * ``NDPluginDriver`` inherits ``asynNDArrayDriverParamSet`` in addition to ``asynNDArrayDriver`` - Updated to access parameters via ``paramSet->``. - Child classes work with no changes - * Some trivial updates to the tests - - * ADSimDetector - https://github.com/dls-controls/ADSimDetector/tree/pvi - * ``simDetector`` parameters split into ``simDetectorParamSet`` - * ``simDetector`` inherits from ``simDetectorParamSet`` in addition to - ``ADDriver`` - * Can access parameters as before - - * ADPilatus - https://github.com/dls-controls/ADPilatus/tree/pvi - * Equivalent to ADSimDetector changes - - * motor - https://github.com/dls-controls/motor/tree/pvi - * ``asynMotorController`` parameters split into ``asynMotorControllerParamSet`` - * Updated to access parameters via ``paramSet->`` - - * pmac - https://github.com/dls-controls/pmac/tree/pvi - * ``pmacController`` parameters split into ``pmacControllerParamSet`` - * ``pmacCSController`` same - * Each inherit from their own param set (which inherits - ``asynMotorControllerParamSet``) in addition to ``asynMotorController`` - * Can access parameters as before - -Caveats -~~~~~~~ +order such that when the asynPortDriver constructor the `asynParamSet` +`parameterDefinitions` is fully populated when `createParams` is called. + +### Change Summary + +> - asyn - +> : - Created `asynParamSet` +> - New overloaded asynPortDriver constructor that takes an `asynParamSet*` +> and calls createParams() +> - ADCore - +> : - `asynNDArrayDriver` parameters split into `asynNDArrayDriverParamSet` +> : Constructor updated to take an `asynNDArrayDriverParamSet*`. +> Updated to access parameters via `paramSet->` +> - `ADDriver` parameters split into `ADDriverParamSet` +> : Constructor updated to take an `ADDriverParamSet*`. +> Updated to access parameters via `paramSet->` +> - `NDPluginDriver` inherits `asynNDArrayDriverParamSet` in addition to `asynNDArrayDriver` +> : Updated to access parameters via `paramSet->`. +> Child classes work with no changes +> - Some trivial updates to the tests +> - ADSimDetector - +> : - `simDetector` parameters split into `simDetectorParamSet` +> - `simDetector` inherits from `simDetectorParamSet` in addition to +> `ADDriver` +> - Can access parameters as before +> - ADPilatus - +> : - Equivalent to ADSimDetector changes +> - motor - +> : - `asynMotorController` parameters split into `asynMotorControllerParamSet` +> - Updated to access parameters via `paramSet->` +> - pmac - +> : - `pmacController` parameters split into `pmacControllerParamSet` +> - `pmacCSController` same +> - Each inherit from their own param set (which inherits +> `asynMotorControllerParamSet`) in addition to `asynMotorController` +> - Can access parameters as before + +### Caveats There are some changes that are unavoidable without inserting edge cases into the generation logic and making the YAML schema more complicated. Some examples are: - 1. The first param index used for calling base class methods is inconsistently - named, so we will have to agree a consistent way to generate them and make them - the same. - 2. Any readback parameters will have an _RBV suffix added. Some existing readbacks - do not have this, e.g. Armed in pilatusDetector. - 3. FIRST_DRIVER_PARAM needs to be defined in the main header file based on the - FIRST_DRIVER_PARAM_INDEX defined in the param set header file, appending - ``paramSet->`` or not depending on whether it inherits the param set or not. (This - could possibly be handled in a better way by adding more logic to the - ``asynParamSet`` - see `Possible Further Work`_) - 4. Asyn parameter names will be the same as the name of the index variable. The - value can be overridden to define drvInfo for drvUserCreate dynamic parameters. - -Next Steps -~~~~~~~~~~ +> 1. The first param index used for calling base class methods is inconsistently +> named, so we will have to agree a consistent way to generate them and make them +> the same. +> 2. Any readback parameters will have an \_RBV suffix added. Some existing readbacks +> do not have this, e.g. Armed in pilatusDetector. +> 3. FIRST_DRIVER_PARAM needs to be defined in the main header file based on the +> FIRST_DRIVER_PARAM_INDEX defined in the param set header file, appending +> `paramSet->` or not depending on whether it inherits the param set or not. (This +> could possibly be handled in a better way by adding more logic to the +> `asynParamSet` - see [Possible Further Work]) +> 4. Asyn parameter names will be the same as the name of the index variable. The +> value can be overridden to define drvInfo for drvUserCreate dynamic parameters. + +### Next Steps A script is in development to perform the generation of an initial YAML file from a template. The idea being that after this point, everything is generated from the YAML @@ -451,7 +425,7 @@ a YAML file from the start. The current hierarchy (base classes have-a param set while the most derived class is-a param set) is inconsistent and confusing, but it has the benefit that people are not forced to change drivers inheriting from these base classes to access parameters -via ``paramSet->``, they just need to inherit the parent param set. It also means that +via `paramSet->`, they just need to inherit the parent param set. It also means that the most derived classes cannot be inherited from alongside an extended param set (this is not necessarily a problem, but it seems like an unnecessary restriction). It would be clearer to remove this and require all child classes to use the param set @@ -460,20 +434,21 @@ to the extern C calls to create the param set and pass it into the constructor o driver. Another option is to create a class that inherits from the driver and the param set, with no additional logic. This could then instantiate the param set and pass it to the driver constructor. Either solution would resolve caveat 3, because all classes -will access FIRST_DRIVER_PARAM via ``paramSet->``. +will access FIRST_DRIVER_PARAM via `paramSet->`. -Possible Further Work -~~~~~~~~~~~~~~~~~~~~~ +### Possible Further Work Adopting this framework could make it easier to make other improvements to the C++ code, such as: - * Move some asynPortDriver functionality into ``asynParamSet`` / ``asynParam`` (See ADEiger eigerParam) - * Reduce the size of ``asynPortDriver`` - * Possibility of typed subclasses of ``asynParam`` to reduce if statements for - handling the many ``asynParamType`` values in slightly different ways - * Simplify the sharing of file writing functionality between some drivers - * ADPilatus and NDPluginFile-derived classes - Parameters could be split out - of ``asynNDArrayDriver`` into a ``FileWriterParamSet``, which could then be - included via composition only where required, rather than every driver and - plugin under ``asynNDArrayDriver`` having these parameters. +> - Move some asynPortDriver functionality into `asynParamSet` / `asynParam` (See ADEiger eigerParam) +> : - Reduce the size of `asynPortDriver` +> - Possibility of typed subclasses of `asynParam` to reduce if statements for +> handling the many `asynParamType` values in slightly different ways +> - Simplify the sharing of file writing functionality between some drivers +> : - ADPilatus and NDPluginFile-derived classes - Parameters could be split out +> of `asynNDArrayDriver` into a `FileWriterParamSet`, which could then be +> included via composition only where required, rather than every driver and +> plugin under `asynNDArrayDriver` having these parameters. + +[yaml]: https://en.wikipedia.org/wiki/YAML diff --git a/docs/developer/how-to/write-a-formatter.rst b/docs/how-to/write-a-formatter.md similarity index 72% rename from docs/developer/how-to/write-a-formatter.rst rename to docs/how-to/write-a-formatter.md index af40a49f..3b58e749 100644 --- a/docs/developer/how-to/write-a-formatter.rst +++ b/docs/how-to/write-a-formatter.md @@ -1,40 +1,44 @@ -How to Write a Site Specific Formatter -====================================== +# How to Write a Site Specific Formatter + This guide explains how you can create a pvi formatter to generate screens for your own use cases. -Overview --------- +## Overview + The formatters role is to take a device.yaml file and turn this into a screen file that can be used by the display software. Inside of the device.yaml file is a list of components that specify its name, a widget type and any additional properties that can be assigned to that widget (such as a pv name). During formatting, the device.yaml file is deserialised into component objects, which are later translated into widgets: -.. literalinclude:: ../../../src/pvi/device.py - :pyobject: Component +```{literalinclude} ../../../src/pvi/device.py +:pyobject: Component +``` -.. literalinclude:: ../../../src/pvi/device.py - :pyobject: SignalR +```{literalinclude} ../../../src/pvi/device.py +:pyobject: SignalR +``` To make a screen from this, we need a template file. This contains a blank representation of each supported widget for each of the supported file formats (bob, edl etc...). Below is an example of a 'text entry' widget for a .bob file: -.. literalinclude:: ../../../src/pvi/_format/dls.bob - :lines: 57-73 +```{literalinclude} ../../../src/pvi/_format/dls.bob +:lines: 57-73 +``` By extracting and altering the template widgets with the information provided by the components, we can create a screen file. -Create a formatter subclass ---------------------------- +## Create a formatter subclass + To start, we will need to create our own formatter class. These inherit from an abstract 'Formatter' class that is defined in base.py. Inside, we need to define one mandatory 'format' function, which will be used to create our screen file: -.. literalinclude:: ../../../src/pvi/_format/base.py - :pyobject: Formatter +```{literalinclude} ../../../src/pvi/_format/base.py +:pyobject: Formatter +``` The format function takes in a device: a list of components obtained from our deserialised device.yaml file, A prefix: the pv prefix of the device, and a path: the @@ -43,14 +47,15 @@ output destination for the generated screen file. With a formatter defined, we now can start to populate this by defining the screen dependencies. -Define the Screen Layout Properties ------------------------------------ +## Define the Screen Layout Properties + Each screen requires a number of layout properties that allow you to customise the size and placement of widgets. These are stored within a 'ScrenLayout' dataclass that can be imported from utils.py. Within the dataclass are the following configurable parameters: -.. literalinclude:: ../../../src/pvi/_format/screen.py - :pyobject: ScreenLayout +```{literalinclude} ../../../src/pvi/_format/screen.py +:pyobject: ScreenLayout +``` When defining these in our formatter, we have the option of deciding which properties should be configurable inside of the formatter.yaml. Properties defined as member @@ -58,9 +63,10 @@ variables of the formatter class (and then referenced by the layout properties i screen format function) will be available to adjust inside of the formatter.yaml. Anything else, should be considered as defaults for the formatter: -.. literalinclude:: ../../../src/pvi/_format/dls.py - :start-after: LP DOCS REF - :end-before: SW DOCS REF +```{literalinclude} ../../../src/pvi/_format/dls.py +:end-before: SW DOCS REF +:start-after: LP DOCS REF +``` In the example above, everything has been made adjustable from the formatter.yaml except the properties relating to groups. This is becuase they are more dependant on the file @@ -70,43 +76,44 @@ For clarity, the example below shows how the formatter.yaml can be used to set t layout properties. Note that these are optional as each property is defined with a default value: -.. literalinclude:: ../../../formatters/dls.bob.pvi.formatter.yaml +```{literalinclude} ../../../formatters/dls.bob.pvi.formatter.yaml +``` + +## Assign a Template File -Assign a Template File ----------------------- As previously stated, a template file provides the formatter with a base model of all of the supported widgets that it can then overwrite with component data. Currently, pvi supports templates for edl, adl and bob files, which can be referenced from the -_format directory with the filename 'dls' + the file formats suffix (eg. dls.bob). +\_format directory with the filename 'dls' + the file formats suffix (eg. dls.bob). Inside of the format function, we need to provide a reference to the template file that can then be used to identify what each widget should look like: -.. code-block:: python3 +```python3 +template = BobTemplate(str(Path(__file__).parent / "dls.bob")) +``` - template = BobTemplate(str(Path(__file__).parent / "dls.bob")) +% Documentation does not explain what the WidgetTemplate function does, +% nor its subclasses BobTemplate, EdlTemplate & AdlTemplate. -.. - Documentation does not explain what the WidgetTemplate function does, - nor its subclasses BobTemplate, EdlTemplate & AdlTemplate. +## Divide the Template into Widgets -Divide the Template into Widgets --------------------------------- With a template defined, we now need to assign each part of it to a supported widget. This is achieved using the ScreenWidgets dataclass (from utils.py). With this, we can assign each of the widget classes to a snippet of the template using the WidgetFactory.from_template method: -.. literalinclude:: ../../../src/pvi/_format/dls.py - :start-after: SW DOCS REF - :end-before: MAKE_WIDGETS DOCS REF +```{literalinclude} ../../../src/pvi/_format/dls.py +:end-before: MAKE_WIDGETS DOCS REF +:start-after: SW DOCS REF +``` This function uses a unique search term to locate and extract a widget from the template. As such, the search term MUST be unique to avoid extracing multiple or irrelevant widgets from the template. -Define screen and group widget functions ----------------------------------------- +## Define screen and group widget functions + Two widgets that are not handled by ScreenWidgets are the screen title and group object. This is because the style of these widgets differ greatly for each file type. For instance, with edl and adl files, groups are represented by a rectangle and title placed @@ -119,18 +126,20 @@ We then need to define two functions that can be used to create multiple instanc these widgets. In this example, we provide two arguments: The 'bounds', to set the widgets size and position, and the 'title' to populate the label with. -.. literalinclude:: ../../../src/pvi/_format/dls.py - :start-after: MAKE_WIDGETS DOCS REF - :end-before: SCREEN_INI DOCS REF +```{literalinclude} ../../../src/pvi/_format/dls.py +:end-before: SCREEN_INI DOCS REF +:start-after: MAKE_WIDGETS DOCS REF +``` + +## Construct a Screen Object -Construct a Screen Object -------------------------- Provided that you have defined the LayoutProperties, template, ScreenWidgets and the screen title and group object functions, we are now ready to define a screen object. -.. literalinclude:: ../../../src/pvi/_format/dls.py - :start-after: SCREEN_INI DOCS REF - :end-before: SCREEN_FORMAT DOCS REF +```{literalinclude} ../../../src/pvi/_format/dls.py +:end-before: SCREEN_FORMAT DOCS REF +:start-after: SCREEN_INI DOCS REF +``` Note that screen_cls and group_cls are defined separately here as GroupFactories. This is because they take in the make_widgets function, which has the possibility of returning @@ -144,21 +153,24 @@ On the output of this, we can call a (screen.)format function that populates the with the extracted properties from the device.yaml, and converts them into the chosen file format: -.. literalinclude:: ../../../src/pvi/_format/dls.py - :start-after: SCREEN_FORMAT DOCS REF - :end-before: SCREEN_WRITE DOCS REF +```{literalinclude} ../../../src/pvi/_format/dls.py +:end-before: SCREEN_WRITE DOCS REF +:start-after: SCREEN_FORMAT DOCS REF +``` + +## Generate the Screen file -Generate the Screen file ------------------------- After calling format on the screen object, you will be left with a list of strings that represent each widget in your chosen file format. The final step is to create a screen file by unpacking the list and writing each widget to the file: -.. literalinclude:: ../../../src/pvi/_format/dls.py - :start-after: SCREEN_WRITE DOCS REF +```{literalinclude} ../../../src/pvi/_format/dls.py +:start-after: SCREEN_WRITE DOCS REF +``` And thats it. With this you can now create your own custom formatters. Below you can find a complete example formatter, supporting both edl and bob file formats for DLS: -.. literalinclude:: ../../../src/pvi/_format/dls.py - :pyobject: DLSFormatter +```{literalinclude} ../../../src/pvi/_format/dls.py +:pyobject: DLSFormatter +```