Exemple #1
0
    def __init__(self, parent: QWidget):
        super(LogWidget, self).__init__(parent)
        self.setAttribute(
            Qt.WA_DeleteOnClose)  # This is required to stop background timers!

        self._clear_button = make_button(self,
                                         'Clear',
                                         'delete-document',
                                         on_clicked=self._do_clear)
        self._status_display = QLabel(self)

        self._model = _TableModel(self)
        self._model.layoutChanged.connect(self._on_model_changed)

        self._table_view = _TableView(self, self._model)

        # View setup
        controls_layout = QHBoxLayout()
        controls_layout.addWidget(self._clear_button)
        controls_layout.addStretch(1)
        controls_layout.addWidget(self._status_display)

        layout = QVBoxLayout()
        layout.addLayout(controls_layout)
        layout.addWidget(self._table_view, 1)
        self.setLayout(layout)
    def __init__(self, parent: QWidget, commander: Commander):
        super(Widget, self).__init__(parent)

        self._commander = commander
        self._event_suppression_depth = 0

        self._sync_checkbox = QCheckBox(self)
        self._sync_checkbox.setIcon(get_icon('link'))
        self._sync_checkbox.setChecked(True)
        self._sync_checkbox.stateChanged.connect(
            self._on_sync_checkbox_changed)
        self._sync_checkbox.setToolTip('Always same value for all phases')
        self._sync_checkbox.setStatusTip(self._sync_checkbox.toolTip())

        self._send_button = \
            make_button(self,
                        text='Execute',
                        icon_name='send-up',
                        tool_tip='Sends the command to the device; also, while this button is checked (pressed), '
                                 'commands will be sent automatically every time the controls are changed by the user.',
                        checkable=True,
                        checked=False,
                        on_clicked=self._on_send_button_changed)

        self._phase_controls = [
            SpinboxLinkedWithSlider(self,
                                    minimum=0.0,
                                    maximum=100.0,
                                    step=1.0,
                                    slider_orientation=SpinboxLinkedWithSlider.
                                    SliderOrientation.HORIZONTAL)
            for _ in range(3)
        ]

        for pc in self._phase_controls:
            pc.spinbox_suffix = ' %'
            pc.value_change_event.connect(self._on_any_control_changed)

        def make_fat_label(text: str) -> QLabel:
            lbl = QLabel(text, self)
            font: QFont = lbl.font()
            font.setBold(True)
            lbl.setFont(font)
            return lbl

        top_layout_items = []

        for index, pc in enumerate(self._phase_controls):
            top_layout_items.append(
                lay_out_horizontally(
                    make_fat_label('ABC'[index]),
                    pc.spinbox,
                    (pc.slider, 1),
                ))

        self.setLayout(
            lay_out_horizontally(*(top_layout_items +
                                   [self._sync_checkbox, self._send_button])))
Exemple #3
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),
                ),
            ))
 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
Exemple #5
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),
         )
     )
Exemple #6
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),
            ))
    def __init__(self, parent: QWidget, commander: Commander):
        super(Widget, self).__init__(parent)

        self._commander = commander
        self._event_suppression_depth = 0

        self._send_button = make_button(
            self,
            text="Execute",
            icon_name="send-up",
            tool_tip=
            "Sends the command to the device; also, while this button is checked (pressed), "
            "commands will be sent automatically every time the controls are changed by the user.",
            checkable=True,
            checked=False,
            on_clicked=self._on_send_button_changed,
        )

        self._volt_per_hertz_control = SpinboxLinkedWithSlider(
            self,
            minimum=0.001,
            maximum=1.0,
            step=0.001,
            slider_orientation=SpinboxLinkedWithSlider.SliderOrientation.
            HORIZONTAL,
        )
        self._volt_per_hertz_control.spinbox_suffix = " V/Hz"
        self._volt_per_hertz_control.num_decimals = 3
        self._volt_per_hertz_control.tool_tip = "Amplitude, volt per hertz"
        self._volt_per_hertz_control.status_tip = self._volt_per_hertz_control.tool_tip
        self._volt_per_hertz_control.slider_visible = False

        self._target_frequency_control = SpinboxLinkedWithSlider(
            self,
            minimum=-9999.0,
            maximum=9999.0,
            step=10.0,
            slider_orientation=SpinboxLinkedWithSlider.SliderOrientation.
            HORIZONTAL,
        )
        self._target_frequency_control.spinbox_suffix = " Hz"
        self._target_frequency_control.tool_tip = "Target frequency, hertz"
        self._target_frequency_control.status_tip = (
            self._target_frequency_control.tool_tip)

        self._frequency_gradient_control = SpinboxLinkedWithSlider(
            self,
            minimum=0.0,
            maximum=999.0,
            step=1.0,
            slider_orientation=SpinboxLinkedWithSlider.SliderOrientation.
            HORIZONTAL,
        )
        self._frequency_gradient_control.spinbox_suffix = " Hz/s"
        self._frequency_gradient_control.tool_tip = (
            "Frequency gradient, hertz per second")
        self._frequency_gradient_control.status_tip = (
            self._frequency_gradient_control.tool_tip)
        self._frequency_gradient_control.slider_visible = False

        # Initial values
        self._volt_per_hertz_control.value = 0.01
        self._target_frequency_control.value = 0.0
        self._frequency_gradient_control.value = 20.0

        # Having configured the values, connecting the events
        self._volt_per_hertz_control.value_change_event.connect(
            self._on_any_control_changed)
        self._target_frequency_control.value_change_event.connect(
            self._on_any_control_changed)
        self._frequency_gradient_control.value_change_event.connect(
            self._on_any_control_changed)

        self.setLayout(
            lay_out_horizontally(
                self._volt_per_hertz_control.spinbox,
                self._frequency_gradient_control.spinbox,
                self._target_frequency_control.spinbox,
                make_button(
                    self,
                    icon_name="clear-symbol",
                    tool_tip="Reset the target frequency to zero",
                    on_clicked=self._on_target_frequency_clear_button_clicked,
                ),
                self._target_frequency_control.slider,
                self._send_button,
            ))
Exemple #8
0
    def __init__(
        self,
        parent: typing.Optional[QWidget],
        on_connection_request: ConnectionRequestCallback,
        on_disconnection_request: DisconnectionRequestCallback,
    ):
        super(DeviceManagementWidget, self).__init__(parent)
        self.setAttribute(
            Qt.WA_DeleteOnClose)  # This is required to stop background timers!

        self._port_discoverer = PortDiscoverer()
        self._port_mapping: typing.Dict[str:str] = {}

        self._port_combo = QComboBox(self)
        self._port_combo.setEditable(True)
        self._port_combo.setInsertPolicy(QComboBox.NoInsert)
        self._port_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self._port_combo.setFont(get_monospace_font())
        self._port_combo.lineEdit().returnPressed.connect(
            self._on_confirmation)

        self._connect_button = make_button(self,
                                           "Connect",
                                           "disconnected",
                                           on_clicked=self._on_confirmation)
        self._connect_button.setEnabled(
            False)  # Empty by default, therefore disabled

        self._port_combo.currentTextChanged.connect(
            lambda: self._connect_button.setEnabled(
                bool(self._port_combo.currentText().strip())))

        self._status_text = QLabel(self)
        self._status_text.setText(_STATUS_WHEN_NOT_CONNECTED)
        self._status_text.setWordWrap(True)

        self._device_info_widget = LittleBobbyTablesWidget(self)

        combo_completer = QCompleter()
        combo_completer.setCaseSensitivity(Qt.CaseInsensitive)
        combo_completer.setModel(self._port_combo.model())
        self._port_combo.setCompleter(combo_completer)

        self._update_timer = QTimer(self)
        self._update_timer.timeout.connect(self._update_ports)
        self._update_timer.start(2000)

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

        self._connection_established = False
        self._last_task: typing.Optional[asyncio.Task] = None

        self._connection_request_callback: ConnectionRequestCallback = (
            on_connection_request)
        self._disconnection_request_callback: DisconnectionRequestCallback = (
            on_disconnection_request)

        # Layout
        self._overlay = QStackedLayout(self)
        self._init_overlay_widgets()
        self.setLayout(self._overlay)

        # Initialization
        self._update_ports()
Exemple #9
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)
Exemple #10
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())