3. BaseMalWidget

The BaseMalWidget class is a reusable (QWidget, Logger) base class that centralizes the MAL connection boilerplate shared by all widgets in the IFW Widget Library. Instead of each widget manually creating facades, wiring connection signals, and clearing Taurus bgRole, BaseMalWidget handles all of this automatically.

Every CCF widget, SysSup widget, and custom widget in the library inherits from BaseMalWidget.

3.1. What It Provides

BaseMalWidget handles six areas of boilerplate:

ConnectionManager

Creates the singleton ConnectionManager on construction and stores it as self._conn_mgr, so subclasses can obtain facades without instantiating their own manager.

Facade Registration

Subclasses override _setup_facades() to obtain facades from self._conn_mgr and call self.register_facade(facade) for each one. This automatically wires the facade’s connectionChanged signal.

Connection Monitoring

All registered facades are monitored. When any facade disconnects, the widget is automatically disabled (visually grayed out), providing immediate feedback to the user.

Taurus Status Listener Factory

make_status_listener() creates a QObjectTaurusListener for an OLDB data point, with automatic existence checking. Returns (Attribute, listener) or (None, None) if the path does not exist.

TaurusLabel bgRole Clearing

remove_bgrole() uses findChildren to discover all TaurusBaseWidget descendants (TaurusLabel, TaurusState, TimeHMSWidget, etc.) and clears their bgRole attribute so that QSS themes control appearance uniformly.

Logger Mixin

Inherits Logger from taurus, providing self.debug(), self.info(), self.warning(), and self.error() methods.

3.2. Using BaseMalWidget

To create a widget that inherits from BaseMalWidget, follow this pattern:

  1. Call super().__init__(parent, base_uri)

  2. Store constructor parameters (dcs_db_prefix, etc.)

  3. Call self._setup_facades() to register facades

  4. Instantiate the OLDB paths dataclass

  5. Set up the UI

  6. Call self.remove_bgrole()

  7. Call _update_db_models() to bind Taurus labels

  8. Call _init_gui() to wire up Qt signals

Example:

from dataclasses import dataclass

from taurus.external.qt.QtGui import QWidget
from taurus.external.qt.QtCore import Slot

from ifw.wdglib.comm import StdCmdsFacade
from ifw.wdglib.widgets.common import BaseMalWidget
from myapp.ui_mystatewidget import Ui_MyStateWidget


@dataclass(frozen=True)
class MyStateOldbPaths:
    dcs_db_prefix: str

    @property
    def state_path(self) -> str:
        return f"{self.dcs_db_prefix}/sm/state"


class MyStateWidget(BaseMalWidget):
    """Widget displaying a subsystem state value with state commands."""

    def __init__(
        self,
        parent: QWidget,
        base_uri: str,
        dcs_db_prefix: str,
    ) -> None:
        super().__init__(parent, base_uri)

        self._dcs_db_prefix = dcs_db_prefix
        self._setup_facades()

        self.oldb_paths = MyStateOldbPaths(dcs_db_prefix)

        self.ui = Ui_MyStateWidget()
        self.ui.setupUi(self)
        self.remove_bgrole()

        self._update_db_models()
        self._init_gui()

    def _setup_facades(self) -> None:
        self._stdcmds = self._conn_mgr.get_stdcmds(
            f"{self._base_uri}/StdCmds"
        )
        self.register_facade(self._stdcmds)

    def _update_db_models(self) -> None:
        self.ui.stateLabel.setModel(self.oldb_paths.state_path)

    def _init_gui(self) -> None:
        self.ui.stateComboBox.addItems(["Init", "Enable", "Disable"])
        self.ui.stateComboBox.currentIndexChanged.connect(
            self._on_stateComboBox_currentIndexChanged
        )
        self._update_connection()

    @Slot(str)
    def _on_stateComboBox_currentIndexChanged(self, new_state) -> None:
        selection = self.ui.stateComboBox.currentText()
        if selection == "Init":
            self._stdcmds.init()
        elif selection == "Enable":
            self._stdcmds.enable()
        elif selection == "Disable":
            self._stdcmds.disable()

3.3. Method Reference

__init__(parent, base_uri)

Initializes the widget, creates the ConnectionManager singleton, stores base_uri, and initializes an empty facade list.

Args:

parent: Parent QWidget. base_uri: MAL base URI, e.g. “zpb.rr://127.0.0.1:12091”.

_setup_facades()

Override in subclass to register the MAL facades this widget needs. Raise NotImplementedError if not overridden.

After obtaining each facade from self._conn_mgr, call self.register_facade(facade) to wire its connectionChanged signal. Available factory methods:

  • get_stdcmds(uri) – Standard lifecycle commands

  • get_dcscmds(uri) – Device control/monitoring

  • get_reccmds(uri) – Recording commands

  • get_fcfcmds(uri) – FCF I/O device commands

  • get_syssupcmds(uri) – System supervisor

register_facade(facade)

Connects the facade’s connectionChanged signal to on_cmds_connection_changed and adds it to the internal list of facades checked by _update_connection().

on_cmds_connection_changed(state)

Slot connected to all registered facade connectionChanged signals. Delegates to _update_connection(). Override if you need per-facade behavior (e.g., individual connection LEDs).

_update_connection()

Enables or disables the widget based on whether ALL registered facades are connected. Override for custom behavior (e.g., per-facade status indicators, or enabling the widget if only a subset of facades is required).

make_status_listener(full_attr_path, event, name)

Creates a QObjectTaurusListener for an OLDB data point. Checks that the data point exists before creating the listener. Returns (Attribute, QObjectTaurusListener) or (None, None) on error.

Args:

full_attr_path: Full OLDB path to listen to. event: Callable connected to the listener’s taurusEvent signal. name: Name for the QObjectTaurusListener instance.

remove_bgrole()

Clears the bgRole attribute on all TaurusBaseWidget descendants (TaurusLabel, TaurusState, TimeHMSWidget, etc.) using findChildren. Must be called after ui.setupUi(self) so that the widget hierarchy exists.

3.4. See Also

For a complete step-by-step walkthrough of creating a custom widget using BaseMalWidget, see the Creating a Custom Widget chapter.