Esempio n. 1
0
    def __init__(self, parent: QWidget, commander: Commander):
        from . import STOP_SHORTCUT

        super(RunControlWidget, self).__init__(parent)

        self._commander = commander

        self._last_status: typing.Optional[TaskSpecificStatusReport.Run] = None

        # noinspection PyTypeChecker
        self._named_control_policies: typing.Dict[
            str, _ControlPolicy
        ] = _make_named_control_policies()

        self._guru_mode_checkbox = QCheckBox("Guru", self)
        self._guru_mode_checkbox.setToolTip(
            "The Guru Mode is dangerous! "
            "Use it only if you know what you're doing, and be ready for problems."
        )
        self._guru_mode_checkbox.setStatusTip(self._guru_mode_checkbox.toolTip())
        self._guru_mode_checkbox.setIcon(get_icon("guru"))
        self._guru_mode_checkbox.toggled.connect(self._on_guru_mode_toggled)

        self._setpoint_control = SpinboxLinkedWithSlider(
            self,
            slider_orientation=SpinboxLinkedWithSlider.SliderOrientation.HORIZONTAL,
        )
        self._setpoint_control.tool_tip = (
            f"To stop the motor, press {STOP_SHORTCUT} or click the Stop button"
        )
        self._setpoint_control.status_tip = self._setpoint_control.tool_tip
        self._setpoint_control.value_change_event.connect(self._on_setpoint_changed)
        self._setpoint_control.spinbox.setKeyboardTracking(False)

        self._mode_selector = QComboBox(self)
        self._mode_selector.setEditable(False)
        self._mode_selector.currentIndexChanged.connect(
            lambda *_: self._on_control_mode_changed()
        )
        for name, cp in self._named_control_policies.items():
            if not cp.only_for_guru:
                self._mode_selector.addItem(get_icon(cp.icon_name), name)

        self._on_control_mode_changed()

        self.setLayout(
            lay_out_vertically(
                lay_out_horizontally(
                    QLabel("Control mode", self),
                    self._mode_selector,
                    (None, 1),
                    QLabel("Setpoint", self),
                    (self._setpoint_control.spinbox, 1),
                    (None, 1),
                    self._guru_mode_checkbox,
                ),
                self._setpoint_control.slider,
                (None, 1),
            )
        )
Esempio n. 2
0
    def __init__(self, parent: QWidget, commander: Commander):
        super(LowLevelManipulationControlWidget, self).__init__(parent)

        self._last_seen_timestamped_general_status: typing.Optional[
            typing.Tuple[float, GeneralStatusView]] = None

        self._tabs = QTabWidget(self)

        for widget_type in _LLM_MODE_TO_WIDGET_TYPE_MAPPING.values():
            widget = widget_type(self, commander)
            tab_name, icon_name = widget.get_widget_name_and_icon_name()
            self._tabs.addTab(widget, get_icon(icon_name), tab_name)

        self._current_widget: LowLevelManipulationControlSubWidgetBase = self._tabs.currentWidget(
        )
        self._tabs.currentChanged.connect(self._on_current_widget_changed)

        # Presentation configuration.
        # We have only one widget here, so we reduce the margins to the bare minimum in order to
        # conserve the valuable screen space.
        # We also change the default appearance of the tab widget to make it look okay with the small margins.
        self._tabs.setTabPosition(QTabWidget.South)
        self._tabs.setDocumentMode(True)
        self.setLayout(lay_out_vertically((self._tabs, 1)))
        self.layout().setContentsMargins(0, 0, 0, 0)
Esempio n. 3
0
    def __init__(self, parent: QWidget, commander: Commander):
        super(TelegaControlWidget, self).__init__(parent)

        self._dc_quantities_widget = DCQuantitiesWidget(self)
        self._temperature_widget = TemperatureWidget(self)
        self._hardware_flag_counters_widget = HardwareFlagCountersWidget(self)
        self._device_status_widget = DeviceStatusWidget(self)
        self._vsi_status_widget = VSIStatusWidget(self)
        self._active_alerts_widget = ActiveAlertsWidget(self)

        self._task_specific_status_widget = TaskSpecificStatusWidget(self)

        self._control_widget = ControlWidget(self, commander)

        self.setLayout(
            lay_out_vertically(
                lay_out_horizontally(
                    (self._dc_quantities_widget, 1),
                    (self._temperature_widget, 1),
                    (self._hardware_flag_counters_widget, 1),
                    (self._vsi_status_widget, 1),
                    (self._active_alerts_widget, 2),
                ),
                lay_out_horizontally(
                    (self._device_status_widget, 1),
                    (self._task_specific_status_widget, 5),
                ),
                self._control_widget,
            ), )
Esempio n. 4
0
    def __init__(self, parent: QWidget):
        super(Widget, self).__init__(parent)

        self._last_displayed: TaskSpecificStatusReport.Fault = None

        self._line_height = QFontMetrics(QFont()).height()

        self._task_icon_display = QLabel(self)
        self._task_name_display = self._make_line_display()

        self._error_code_dec = self._make_line_display('Exit code in decimal')
        self._error_code_hex = self._make_line_display(
            'Same exit code in hexadecimal')
        self._error_code_bin = self._make_line_display(
            'Same exit code in binary, for extra convenience')

        self._error_description_display = self._make_line_display(
            'Error description', False)
        self._error_comment_display = self._make_text_display()

        self.setLayout(
            lay_out_vertically(
                lay_out_horizontally(QLabel('The task',
                                            self), self._task_icon_display,
                                     (self._task_name_display, 3)),
                lay_out_horizontally(QLabel('has failed with exit code',
                                            self), (self._error_code_dec, 1),
                                     (self._error_code_hex, 1),
                                     (self._error_code_bin, 2),
                                     QLabel('which means:', self)),
                lay_out_horizontally((self._error_description_display, 1)),
                lay_out_horizontally((self._error_comment_display, 1)),
                (None, 1),
            ))
    def __init__(self, parent: QWidget):
        super(Widget, self).__init__(parent)

        self._progress_bar = QProgressBar(self)
        self._progress_bar.setMinimum(0)
        self._progress_bar.setMaximum(100)

        self.setLayout(lay_out_vertically(self._progress_bar))
Esempio n. 6
0
    def __init__(self, parent: QWidget, commander: Commander):
        super(MiscControlWidget, self).__init__(parent)

        self._commander = commander
        self._performer_should_stop = True

        self._frequency_input = QDoubleSpinBox(self)
        self._frequency_input.setRange(100, 15000)
        self._frequency_input.setValue(3000)
        self._frequency_input.setSuffix(" Hz")
        self._frequency_input.setToolTip("Beep frequency, in hertz")
        self._frequency_input.setStatusTip(self._frequency_input.toolTip())

        self._duration_input = QDoubleSpinBox(self)
        self._duration_input.setRange(0.01, 3)
        self._duration_input.setValue(0.5)
        self._duration_input.setSuffix(" s")
        self._duration_input.setToolTip("Beep duration, in seconds")
        self._duration_input.setStatusTip(self._duration_input.toolTip())

        self._go_button = make_button(
            self,
            text="Beep",
            icon_name="speaker",
            tool_tip="Sends a beep command to the device once",
            on_clicked=self._beep_once,
        )

        def stealthy(
            icon_name: str,
            music_factory: typing.Callable[[], typing.Iterable["_Note"]]
        ) -> QWidget:
            b = make_button(
                self,
                icon_name=icon_name,
                on_clicked=lambda: self._begin_performance(music_factory()),
            )
            b.setFlat(True)
            b.setFixedSize(4, 4)
            return b

        self.setLayout(
            lay_out_vertically(
                lay_out_horizontally(
                    QLabel("Frequency", self),
                    self._frequency_input,
                    QLabel("Duration", self),
                    self._duration_input,
                    self._go_button,
                    (None, 1),
                ),
                (None, 1),
                lay_out_horizontally(
                    (None, 1),
                    stealthy("darth-vader", _get_imperial_march),
                ),
            ))
Esempio n. 7
0
def display_warning_message(title: str, text: str, parent: WidgetBase,
                            unwritten_registers: list):
    _warning = QDialog(parent)
    _warning.setWindowTitle(title)

    _tableWidget = QTableWidget(_warning)
    _tableWidget.setFont(get_monospace_font())
    _tableWidget.setRowCount(len(unwritten_registers))
    _tableWidget.setColumnCount(3)

    _tableWidget.setHorizontalHeaderLabels(
        ["Full name", "Current value", "Requested value"])
    _tableWidget.horizontalHeader().setStretchLastSection(True)

    _header = _tableWidget.horizontalHeader()
    _header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
    _header.setSectionResizeMode(1, QHeaderView.Stretch)
    _header.setSectionResizeMode(2, QHeaderView.Stretch)

    _tableWidget.verticalHeader().hide()
    _tableWidget.verticalHeader().setSectionResizeMode(
        _tableWidget.verticalHeader().ResizeToContents)

    for i in range(len(unwritten_registers)):
        _name = unwritten_registers[i][0]
        _current_value = unwritten_registers[i][1]
        _requested_value = unwritten_registers[i][2]
        _tableWidget.setItem(i, 0, QTableWidgetItem(_name + " "))
        _tableWidget.setItem(
            i, 1, QTableWidgetItem(", ".join(str(e) for e in _current_value)))
        _tableWidget.setItem(
            i, 2,
            QTableWidgetItem(", ".join(str(e) for e in _requested_value)))

    _btn_ok = QPushButton(_warning)
    _btn_ok.setText("Ok")
    _btn_ok.clicked.connect(_warning.close)

    _warning.setLayout(
        lay_out_vertically(
            lay_out_horizontally(QLabel(text, _warning)),
            lay_out_horizontally(
                QLabel("Some configuration parameters could not be written:",
                       _warning)),
            lay_out_horizontally(_tableWidget),
            lay_out_horizontally(_btn_ok),
        ))

    _warning.show()
Esempio n. 8
0
 def __init__(self, parent: QWidget, commander: Commander):
     super(Widget, self).__init__(parent)
     self._commander = commander
     self.setLayout(
         lay_out_vertically(
             lay_out_horizontally(
                 QLabel("Calibrate the VSI hardware", self),
                 make_button(
                     self,
                     text="Calibrate",
                     icon_name="scales",
                     on_clicked=self._execute,
                 ),
                 (None, 1),
             ),
             (None, 1),
         )
     )
Esempio n. 9
0
    def __init__(self, parent: QWidget, commander: Commander):
        super(MotorIdentificationControlWidget, self).__init__(parent)

        self._commander = commander

        self._mode_map = {
            _humanize(mid): mid
            for mid in MotorIdentificationMode
        }

        self._mode_selector = QComboBox(self)
        self._mode_selector.setEditable(False)
        # noinspection PyTypeChecker
        self._mode_selector.addItems(
            map(
                _humanize,
                sorted(
                    MotorIdentificationMode,
                    key=lambda x: x != MotorIdentificationMode.R_L_PHI,
                ),
            ))

        go_button = QPushButton(get_icon("play"), "Launch", self)
        go_button.clicked.connect(self._execute)

        self.setLayout(
            lay_out_vertically(
                lay_out_horizontally(
                    QLabel("Select parameters to estimate:", self),
                    self._mode_selector,
                    (None, 1),
                ),
                lay_out_horizontally(
                    QLabel("Then click", self),
                    go_button,
                    QLabel(
                        "and wait. The process will take a few minutes to complete.",
                        self,
                    ),
                    (None, 1),
                ),
                (None, 1),
            ))
Esempio n. 10
0
def _unittest_spinbox_linked_with_slider():
    import time
    from PyQt5.QtWidgets import QApplication, QMainWindow, QLayout
    from kucher.view.utils import lay_out_horizontally, lay_out_vertically

    app = QApplication([])

    instances: typing.List[SpinboxLinkedWithSlider] = []

    def make(minimum: float, maximum: float, step: float) -> QLayout:
        o = SpinboxLinkedWithSlider(
            widget,
            minimum=minimum,
            maximum=maximum,
            step=step,
            slider_orientation=SpinboxLinkedWithSlider.SliderOrientation.
            HORIZONTAL,
        )
        instances.append(o)
        return lay_out_horizontally((o.slider, 1), o.spinbox)

    win = QMainWindow()
    widget = QWidget(win)
    widget.setLayout(
        lay_out_vertically(make(0, 100, 1), make(-10, 10, 0.01),
                           make(-99999, 100, 100)))
    win.setCentralWidget(widget)
    win.show()

    def run_a_bit():
        for _ in range(1000):
            time.sleep(0.005)
            app.processEvents()

    run_a_bit()
    instances[0].minimum = -1000
    instances[2].step = 10
    run_a_bit()

    win.close()
Esempio n. 11
0
    def __init__(self, parent: QWidget, commander: Commander):
        super(HardwareTestControlWidget, self).__init__(parent)

        self._commander = commander

        self.setLayout(
            lay_out_vertically(
                QLabel(
                    "The motor must be connected in order for the self test to succeed.",
                    self,
                ),
                lay_out_horizontally(
                    (None, 1),
                    make_button(
                        self,
                        text="Run self-test",
                        icon_name="play",
                        on_clicked=self._execute,
                    ),
                    (None, 1),
                ),
                (None, 1),
            ))
Esempio n. 12
0
    def __init__(self, parent: QWidget):
        super(Widget, self).__init__(parent)

        self._energy_conversion_efficiency_estimate = (
            None  # Used for filtering before displaying
        )

        self._stall_count_display = self._make_display(
            "Stalls", "Number of times the rotor stalled since task activation"
        )

        self._estimated_active_power_display = self._make_display(
            "P<sub>active</sub>",
            "For well-balanced systems, the estimated active power equals the DC power",
        )

        self._demand_factor_display = self._make_display(
            "Demand", "Total powertrain demand factor"
        )

        self._mechanical_rpm_display = self._make_display(
            "\u03C9<sub>mechanical</sub>", "Mechanical revolutions per minute"
        )

        self._current_frequency_display = self._make_display(
            "f<sub>electrical</sub>", "Frequency of three-phase currents and voltages"
        )

        self._dq_display = _DQDisplayWidget(self)

        self._torque_display = self._make_display(
            "\u03C4", "Estimated torque at the shaft"
        )

        self._mechanical_power_display = self._make_display(
            "P<sub>mechanical</sub>",
            "Estimated mechanical power delivered to the shaft",
        )

        self._loss_power_display = self._make_display(
            "P<sub>loss</sub>", "Estimated power loss, DC power input to motor shaft"
        )

        self._energy_conversion_efficiency_display = self._make_display(
            "\u03B7<sub>DC-S</sub>",
            "Estimated energy conversion efficiency, DC power input to motor shaft",
        )

        self._control_mode_display = self._make_display(
            "Ctrl. mode", "Control mode used by the controller", True
        )

        self._reverse_flag_display = self._make_display(
            "Direction", "Direction of rotation", True
        )

        self._spinup_flag_display = self._make_display(
            "Started?", "Whether the motor has started or still starting", True
        )

        self._saturation_flag_display = self._make_display(
            "CSSW", "Control System Saturation Warning", True
        )

        self.setLayout(
            lay_out_horizontally(
                (
                    lay_out_vertically(
                        self._mechanical_rpm_display,
                        self._current_frequency_display,
                        self._stall_count_display,
                        self._demand_factor_display,
                    ),
                    4,
                ),
                _make_vertical_separator(self),
                (
                    lay_out_vertically(
                        self._dq_display,
                        self._estimated_active_power_display,
                        (None, 1),
                    ),
                    4,
                ),
                _make_vertical_separator(self),
                (
                    lay_out_vertically(
                        self._torque_display,
                        self._mechanical_power_display,
                        self._loss_power_display,
                        self._energy_conversion_efficiency_display,
                    ),
                    3,
                ),
                _make_vertical_separator(self),
                (
                    lay_out_vertically(
                        self._control_mode_display,
                        self._reverse_flag_display,
                        self._spinup_flag_display,
                        self._saturation_flag_display,
                    ),
                    4,
                ),
            )
        )
Esempio n. 13
0
    def __init__(self, parent: QWidget):
        super(RegisterViewWidget, self).__init__(parent)

        self._registers = []
        self._running_task: asyncio.Task = None

        self._visibility_selector = QComboBox(self)
        self._visibility_selector.addItem("Show all registers", lambda _: True)
        self._visibility_selector.addItem("Only configuration parameters",
                                          lambda r: r.mutable and r.persistent)

        # noinspection PyUnresolvedReferences
        self._visibility_selector.currentIndexChanged.connect(
            lambda _: self._on_visibility_changed())

        self._reset_selected_button = make_button(
            self,
            "Reset selected",
            icon_name="clear-symbol",
            tool_tip=f"Reset the currently selected registers to their default "
            f"values. The restored values will be committed "
            f"immediately. This function is available only if a "
            f"default value is defined. [{RESET_SELECTED_SHORTCUT}]",
            on_clicked=self._do_reset_selected,
        )

        self._reset_all_button = make_button(
            self,
            "Reset all",
            icon_name="skull-crossbones",
            tool_tip=f"Reset the all registers to their default "
            f"values. The restored values will be committed "
            f"immediately.",
            on_clicked=self._do_reset_all,
        )

        self._read_selected_button = make_button(
            self,
            "Read selected",
            icon_name="process",
            tool_tip=f"Read the currently selected registers only "
            f"[{READ_SELECTED_SHORTCUT}]",
            on_clicked=self._do_read_selected,
        )

        self._read_all_button = make_button(
            self,
            "Read all",
            icon_name="process-plus",
            tool_tip="Read all registers from the device",
            on_clicked=self._do_read_all,
        )

        self._export_button = make_button(
            self,
            "Export",
            icon_name="export",
            tool_tip="Export configuration parameters",
            on_clicked=self._do_export,
        )

        self._import_button = make_button(
            self,
            "Import",
            icon_name="import",
            tool_tip="Import configuration parameters",
            on_clicked=self._do_import,
        )

        self._expand_all_button = make_button(
            self,
            "",
            icon_name="expand-arrow",
            tool_tip="Expand all namespaces",
            on_clicked=lambda: self._tree.expandAll(),
        )

        self._collapse_all_button = make_button(
            self,
            "",
            icon_name="collapse-arrow",
            tool_tip="Collapse all namespaces",
            on_clicked=lambda: self._tree.collapseAll(),
        )

        self._status_display = QLabel(self)
        self._status_display.setWordWrap(True)

        self._reset_selected_button.setEnabled(False)
        self._reset_all_button.setEnabled(False)
        self._read_selected_button.setEnabled(False)
        self._read_all_button.setEnabled(False)
        self._export_button.setEnabled(False)
        self._import_button.setEnabled(False)

        self._tree = QTreeView(self)
        self._tree.setVerticalScrollMode(QTreeView.ScrollPerPixel)
        self._tree.setHorizontalScrollMode(QTreeView.ScrollPerPixel)
        self._tree.setAnimated(True)
        self._tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self._tree.setAlternatingRowColors(True)
        self._tree.setContextMenuPolicy(Qt.ActionsContextMenu)

        # Not sure about this one. This hardcoded value may look bad on some platforms.
        self._tree.setIndentation(20)

        def add_action(
            callback: typing.Callable[[], None],
            icon_name: str,
            name: str,
            shortcut: typing.Optional[str] = None,
        ):
            action = QAction(get_icon(icon_name), name, self)
            # noinspection PyUnresolvedReferences
            action.triggered.connect(callback)
            if shortcut:
                action.setShortcut(shortcut)
                action.setAutoRepeat(False)
                try:
                    action.setShortcutVisibleInContextMenu(True)
                except AttributeError:
                    pass  # This feature is not available in PyQt before 5.10

            self._tree.addAction(action)

        add_action(self._do_read_all, "process-plus", "Read all registers")
        add_action(
            self._do_read_selected,
            "process",
            "Read selected registers",
            READ_SELECTED_SHORTCUT,
        )
        add_action(
            self._do_reset_selected,
            "clear-symbol",
            "Reset selected to default",
            RESET_SELECTED_SHORTCUT,
        )

        self._tree.setItemDelegateForColumn(
            int(Model.ColumnIndices.VALUE),
            EditorDelegate(self._tree, self._display_status),
        )

        # It doesn't seem to be explicitly documented, but it seems to be necessary to select either top or bottom
        # decoration position in order to be able to use center alignment. Left or right positions do not work here.
        self._tree.setItemDelegateForColumn(
            int(Model.ColumnIndices.FLAGS),
            StyleOptionModifyingDelegate(
                self._tree,
                decoration_position=QStyleOptionViewItem.Top,  # Important
                decoration_alignment=Qt.AlignCenter,
            ),
        )

        header: QHeaderView = self._tree.header()
        header.setSectionResizeMode(QHeaderView.ResizeToContents)
        header.setStretchLastSection(
            False)  # Horizontal scroll bar doesn't work if this is enabled

        buttons_layout = QGridLayout()
        buttons_layout.addWidget(self._read_selected_button, 0, 0)
        buttons_layout.addWidget(self._reset_selected_button, 0, 2)
        buttons_layout.addWidget(self._read_all_button, 1, 0)
        buttons_layout.addWidget(self._reset_all_button, 1, 2)
        buttons_layout.addWidget(self._import_button, 2, 0)
        buttons_layout.addWidget(self._export_button, 2, 2)

        for col in range(3):
            buttons_layout.setColumnStretch(col, 1)

        layout = lay_out_vertically(
            (self._tree, 1),
            buttons_layout,
            lay_out_horizontally(
                self._visibility_selector,
                (None, 1),
                self._expand_all_button,
                self._collapse_all_button,
            ),
            self._status_display,
        )

        self.setLayout(layout)
Esempio n. 14
0
    def __init__(self, parent: QWidget, commander: Commander):
        super(ControlWidget, self).__init__(parent, 'Controls', 'adjust')

        self._commander: Commander = commander

        self._last_seen_timestamped_general_status: typing.Optional[
            typing.Tuple[float, GeneralStatusView]] = None

        self._run_widget = RunControlWidget(self, commander)
        self._motor_identification_widget = MotorIdentificationControlWidget(
            self, commander)
        self._hardware_test_widget = HardwareTestControlWidget(self, commander)
        self._misc_widget = MiscControlWidget(self, commander)
        self._low_level_manipulation_widget = LowLevelManipulationControlWidget(
            self, commander)

        self._panel = QTabWidget(self)

        self._panel.addTab(self._run_widget, get_icon('running'), 'Run')
        self._panel.addTab(self._motor_identification_widget,
                           get_icon('caliper'), 'Motor identification')
        self._panel.addTab(self._hardware_test_widget, get_icon('pass-fail'),
                           'Self-test')
        self._panel.addTab(self._misc_widget, get_icon('ellipsis'),
                           'Miscellaneous')
        self._panel.addTab(self._low_level_manipulation_widget,
                           get_icon('hand-button'), 'Low-level manipulation')

        self._current_widget: SpecializedControlWidgetBase = self._hardware_test_widget

        # Selecting which widget to show by default - let it be Run widget, it's easy to understand and it's first
        self._panel.setCurrentWidget(self._run_widget)

        # Shared buttons
        self._stop_button =\
            make_button(self,
                        text='Stop',
                        icon_name='stop',
                        tool_tip=f'Sends a regular stop command which instructs the controller to abandon the current'
                                 f'task and activate the Idle task [{STOP_SHORTCUT}]',
                        on_clicked=self._do_regular_stop)
        self._stop_button.setSizePolicy(QSizePolicy().MinimumExpanding,
                                        QSizePolicy().MinimumExpanding)

        self._emergency_button =\
            make_button(self,
                        text='EMERGENCY\nSHUTDOWN',
                        tool_tip=f'Unconditionally  disables and locks down the VSI until restarted '
                                 f'[{EMERGENCY_SHORTCUT}]',
                        on_clicked=self._do_emergency_stop)
        self._emergency_button.setSizePolicy(QSizePolicy().MinimumExpanding,
                                             QSizePolicy().MinimumExpanding)
        small_font = QFont()
        small_font.setPointSize(round(small_font.pointSize() * 0.8))
        self._emergency_button.setFont(small_font)

        # Observe that the shortcuts are children of the window! This is needed to make them global.
        self._stop_shortcut = QShortcut(QKeySequence(STOP_SHORTCUT),
                                        self.window())
        self._stop_shortcut.setAutoRepeat(False)

        self._emergency_shortcut = QShortcut(QKeySequence(EMERGENCY_SHORTCUT),
                                             self.window())
        self._emergency_shortcut.setAutoRepeat(False)

        self.setEnabled(False)

        # Layout
        def make_tiny_label(text: str, alignment: int) -> QLabel:
            lbl = QLabel(text, self)
            lbl.setAlignment(alignment | Qt.AlignHCenter)
            font: QFont = lbl.font()
            font.setPointSize(round(font.pointSize() * 0.7))
            lbl.setFont(font)
            return lbl

        self.setLayout(
            lay_out_horizontally(
                (self._panel, 1),
                lay_out_vertically(
                    (self._stop_button, 1),
                    make_tiny_label(f'\u2191 {STOP_SHORTCUT} \u2191',
                                    Qt.AlignTop),
                    make_tiny_label(f'\u2193 {EMERGENCY_SHORTCUT} \u2193',
                                    Qt.AlignBottom),
                    (self._emergency_button, 1),
                )))

        # Configuring the event handler in the last order, because it might fire while we're configuring the widgets!
        self._panel.currentChanged.connect(self._on_current_widget_changed)
        # Invoking the handler to complete initialization
        self._on_current_widget_changed(self._panel.currentIndex())