Create custom Taurus widgets

Use taurus.qt.qtgui.base.TaurusBaseComponent (or one of its derived mixin classes) when a custom Qt widget needs to interact directly with Taurus models. For background on the widget model API and the available customization levels, see Extending Taurus widgets.

Create a single-model widget

The following example creates a simple power-meter widget by combining a QProgressBar with taurus.qt.qtgui.base.TaurusBaseComponent:

 1from taurus.external.qt import Qt
 2from taurus.qt.qtgui.application import TaurusApplication
 3from taurus.qt.qtgui.base import TaurusBaseComponent
 4
 5
 6class PowerMeter(Qt.QProgressBar, TaurusBaseComponent):
 7    """A Taurus-ified QProgressBar"""
 8
 9    # setFormat() defined by both TaurusBaseComponent and QProgressBar. Rename.
10    setFormat = TaurusBaseComponent.setFormat
11    setBarFormat = Qt.QProgressBar.setFormat
12
13    def __init__(self, parent=None, value_range=(0, 100)):
14        super(PowerMeter, self).__init__(parent=parent)
15        self.setOrientation(Qt.Qt.Vertical)
16        self.setRange(*value_range)
17        self.setTextVisible(False)
18
19    def handleEvent(self, evt_src, evt_type, evt_value):
20        """reimplemented from TaurusBaseComponent"""
21        try:
22            self.setValue(int(evt_value.rvalue.m))
23        except Exception as e:
24            self.info("Skipping event. Reason: %s", e)
25
26
27if __name__ == "__main__":
28    import sys
29
30    app = TaurusApplication()
31    w = PowerMeter()
32    w.setModel("eval:Q(60+20*rand())")
33    w.show()
34    sys.exit(app.exec_())

The mixin provides the Taurus functionality for setting and subscribing to a model. The widget implements handleEvent, which is called whenever the attached model is updated.

If the widget is generic enough to be useful to others, consider contributing it to Taurus or distributing it as a Taurus plugin.

Add support for multiple models

To attach several models to one widget, define the supported keys in modelKeys and handle events according to their source. This version of the power-meter adds a second model that supplies the background color:

 1from taurus.external.qt import Qt
 2from taurus.qt.qtgui.application import TaurusApplication
 3from taurus.qt.qtgui.base import TaurusBaseComponent
 4
 5
 6class PowerMeter2(Qt.QProgressBar, TaurusBaseComponent):
 7    """A Taurus-ified QProgressBar with separate models for value and color"""
 8
 9    # setFormat() defined by both TaurusBaseComponent and QProgressBar. Rename.
10    setFormat = TaurusBaseComponent.setFormat
11    setBarFormat = Qt.QProgressBar.setFormat
12
13    modelKeys = ["power", "color"]  # support 2 models (default key is "power")
14    _template = "QProgressBar::chunk {background: %s}"  # stylesheet template
15
16    def __init__(self, parent=None, value_range=(0, 100)):
17        super(PowerMeter2, self).__init__(parent=parent)
18        self.setOrientation(Qt.Qt.Vertical)
19        self.setRange(*value_range)
20        self.setTextVisible(False)
21
22    def handleEvent(self, evt_src, evt_type, evt_value):
23        """reimplemented from TaurusBaseComponent"""
24        try:
25            if evt_src is self.getModelObj(key="power"):
26                self.setValue(int(evt_value.rvalue.m))
27            elif evt_src is self.getModelObj(key="color"):
28                self.setStyleSheet(self._template % evt_value.rvalue)
29        except Exception as e:
30            self.info("Skipping event. Reason: %s", e)
31
32
33if __name__ == "__main__":
34    import sys
35
36    app = TaurusApplication()
37    w = PowerMeter2()
38    w.setModel("eval:Q(60+20*rand())")  # implicit use of  key="power"
39    w.setModel("eval:['green','red','blue'][randint(3)]", key="color")
40    w.show()
41    sys.exit(app.exec_())

The first key returned by modelKeys is used as the default when a model API method is called without an explicit key.

Handle a list of models

Use the special MLIST key when the widget must operate on an arbitrary list of models. The following widget logs events received from a list of attributes:

 1from datetime import datetime
 2
 3from taurus.core import TaurusEventType
 4from taurus.external.qt import Qt
 5from taurus.qt.qtgui.application import TaurusApplication
 6from taurus.qt.qtgui.base import TaurusBaseComponent
 7
 8
 9class EventLogger(Qt.QTextEdit, TaurusBaseComponent):
10    """A taurus-ified QTextEdit widget that logs events received
11    from an arbitrary list of taurus attributes
12    """
13
14    modelKeys = [TaurusBaseComponent.MLIST]
15
16    def __init__(self, parent=None):
17        super(EventLogger, self).__init__(parent=parent)
18        self.setMinimumWidth(800)
19
20    def handleEvent(self, evt_src, evt_type, evt_value):
21        """reimplemented from TaurusBaseComponent"""
22        line = "{}\t[{}]\t{}".format(
23            datetime.now(),
24            TaurusEventType.whatis(evt_type),
25            evt_src.getFullName(),
26        )
27        self.append(line)
28
29
30if __name__ == "__main__":
31    import sys
32
33    app = TaurusApplication()
34    w = EventLogger()
35    w.setModel(["eval:123", "tango:sys/tg_test/1/short_scalar", "eval:rand()"])
36    w.show()
37    sys.exit(app.exec_())

Calling setModel with key=MLIST expects a sequence of model names. Taurus adds generated keys of the form (MLIST, i) to the widget’s model list, and the corresponding models can then be accessed using the normal multi-model API.

Delegate models to child widgets

A container widget does not need to process all model events itself. It can create Taurus child widgets and assign the models to them instead. The following simplified form creates a label and a value widget for every model:

 1from taurus.external.qt import Qt
 2from taurus.qt.qtgui.application import TaurusApplication
 3from taurus.qt.qtgui.base import MLIST, TaurusBaseComponent
 4from taurus.qt.qtgui.display import TaurusLabel
 5
 6
 7class SimpleForm(Qt.QWidget, TaurusBaseComponent):
 8    """A simple taurus form using the model list support from
 9    TaurusBaseComponent.
10    """
11
12    modelKeys = [MLIST]
13
14    def __init__(self, parent=None):
15        super(SimpleForm, self).__init__(parent=parent)
16        self.setLayout(Qt.QFormLayout(self))
17
18    def setModel(self, model, *, key=MLIST):
19        """reimplemented from TaurusBaseComponent"""
20        TaurusBaseComponent.setModel(self, model, key=key)
21        _ly = self.layout()
22
23        if key is MLIST:  # (re)create all rows
24            # remove existing rows
25            while _ly.rowCount():
26                _ly.removeRow(0)
27            # create new rows
28            for i, name in enumerate(model):
29                simple_name = self.getModelObj(key=(MLIST, i)).getSimpleName()
30                value_label = TaurusLabel()
31                value_label.setModel(name)
32                _ly.addRow(simple_name, value_label)
33        else:  # update a single existing row
34            _, row = key  # key must be of the form (MLIST, <i>)
35            name_label = _ly.itemAt(row, _ly.ItemRole.LabelRole).widget()
36            value_label = _ly.itemAt(row, _ly.ItemRole.FieldRole).widget()
37            name_label.setText(self.getModelObj(key=key).getSimpleName())
38            value_label.setModel(self.getModelName(key=key))
39
40
41if __name__ == "__main__":
42    import sys
43
44    app = TaurusApplication()
45    w = SimpleForm()
46    w.setModel(
47        [
48            "eval:foo=123;foo",
49            "eval:randint(99)",
50            "sys/tg_test/1/short_scalar",
51            "eval:randint(99)",
52        ]
53    )
54    w.show()
55    sys.exit(app.exec_())

In this example the container does not reimplement handleEvent. Instead, it calls setModel on its child widgets and lets each child handle the events from its own model.