Source code for devctrl.gui.devices.device_manager.device_manager

import logging

from devctrl.devices import device, device_info
log = logging.getLogger(__name__)

from PyQt6.QtWidgets import QComboBox, QMessageBox, QPushButton, QWidget, QVBoxLayout, QLabel
from PyQt6.QtWidgets import QFormLayout, QDialog, QDialogButtonBox, QLineEdit, QListWidget, QListWidgetItem

from ....devices.device_manager import DeviceManager, Device, DeviceInfo
from ..device import DeviceWidget
from ...widgets.settings_form import SettingsForm
from .device_dialogs import DeviceInfoWidget
from ..device_selection import select_device_dialog, SelectDeviceWidget


class DeviceSettingsWidget(QWidget):
    """
    Widget that shows the device info and allows for selecting a device to connect to.
    """
    def __init__(self, device_manager: DeviceManager, device_key: str, parent=None):
        super().__init__(parent)
        self.w_info = DeviceInfoWidget()             
        self._devmg = device_manager
        self._key = device_key
        if device_key in device_manager:
            dev_info = device_manager.get_info(device_key)
            self.w_info.set_device_info(dev_info)
        self.setLayout(QVBoxLayout())
        self.layout().addWidget(self.w_info)

        self.b_update_list = QPushButton()
        self.b_update_list.setText("Update list of available devices")
        self.b_update_list.clicked.connect(self.update_device_list)
        self.layout().addWidget(self.b_update_list)

        self.w_device_list = QListWidget()
        self.layout().addWidget(self.w_device_list)
        # initially, only show the entry of the currently connected device
        # since any new communication with devices should only be initiated by user interaction
        self._connection_info: tuple[str,str]|None = None
        if self._devmg.is_connected(self._key):
            try:
                dev_typename, dev_name = self._devmg.get_connection_info(self._key)
                w = QListWidgetItem(f"{dev_typename}: {dev_name}", parent=self.w_device_list)
                w.dev_type = dev_typename
                w.dev_name = dev_name
                self._connection_info = (dev_typename, dev_name)
                self.w_device_list.setCurrentItem(w)
            except KeyError:
                pass


    def update_device_list(self):
        dev_info = self.w_info.get_device_info()
        type_devices = dev_info.device_type.list_devices()

        self.w_device_list.clear()
        for dev_type, dev_name in type_devices:
            w = QListWidgetItem(f"{dev_type}: {dev_name}", parent=self.w_device_list)
            w.dev_type = dev_type
            w.dev_name = dev_name
            # select the item if it is currently connected
            if self._connection_info and self._connection_info[0] == dev_type and self._connection_info[1] == dev_name:
                self.w_device_list.setCurrentItem(w)


    def get_selected_device(self) -> tuple[str,str]|None:
        """
        :return: device typename, device name of the currently selected item, or None if no item is selected
        """
        selected = self.w_device_list.currentItem()
        if selected is None:
            return None
        else:
            return selected.dev_type, selected.dev_name


    def get_device_info(self) -> DeviceInfo:
        """
        :return: the current `DeviceInfo` object
        """
        return self.w_info.get_device_info()

    def apply(self):
        info = self.w_info.get_device_info()
        # if key was changed, remove old reference
        if self._key != info.key:
            self._devmg.remove_device(self._key)
            self._key = info.key
        self._devmg.add_device_info(info)

    def connect(self):
        """
        Prompt the user to connect a device. Call `apply` first.
        """
        info = self.get_selected_device()
        if info is None:
            raise ValueError(f"No device selected")
        dev_typename, dev_name = info
        self._devmg.connect_manually(self._key, dev_typename, dev_name)
        self._connection_info = info

    def apply_and_connect(self):
        self.apply()
        self.connect()


class DeviceSettingsDialog(QDialog):
    def __init__(self, device_manager: DeviceManager, device_key: str, parent=None):
        super().__init__(parent=parent)
        self.setLayout(QVBoxLayout())
        self.setWindowTitle(f"Settings for '{device_key}'")
        self._w_settings = DeviceSettingsWidget(device_manager, device_key)
        self.layout().addWidget(self._w_settings)

        b_apply = QPushButton("Apply")
        b_apply.pressed.connect(self.apply)
        b_ok = QPushButton("Ok")
        b_ok.pressed.connect(self.apply_exit)
        b_cancel = QPushButton("Cancel")
        b_cancel.pressed.connect(self.reject)

        # ok and cancel
        buttons = QDialogButtonBox()
        # manual button creation since buttons must be changed to "Ok and connect" and "Apply and connect" when the user selects a different device
        buttons.addButton(b_ok,     QDialogButtonBox.ButtonRole.AcceptRole)
        buttons.addButton(b_apply,  QDialogButtonBox.ButtonRole.ApplyRole)
        buttons.addButton(b_cancel, QDialogButtonBox.ButtonRole.RejectRole)
        self.layout().addWidget(buttons)

    def apply(self):
        log.debug(f"Applying settings")
        self._w_settings.apply_and_connect()

    def apply_exit(self):
        self.apply()
        self.accept()

    def connect(self):
        self._w_settings.apply_and_connect()



[docs] class DeviceManagerSettingsWidget(QWidget): """ Widget that lets the user add/remove devices from a device manager """ def __init__(self, device_manager: DeviceManager, parent=None): super().__init__(parent=parent) self.l = QVBoxLayout() self.device_manager = device_manager self.b_add = QPushButton() self.b_add.setText(f"Add device") self.b_add.pressed.connect(self.add_device) self.b_remove = QPushButton() self.b_remove.setText(f"Remove device") self.b_remove.pressed.connect(self.add_device) self.l.addWidget(self.b_add) self.setLayout(self.l)
[docs] def add_device(self): """ Prompt for adding a new device """ dialog = QDialog(self) dialog.setWindowTitle("Add new field") dialog.setLayout(QVBoxLayout()) w_add = DeviceInfoWidget() dialog.layout().addWidget(w_add) # ok and cancel buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) dialog.layout().addWidget(buttons) while True: ret = dialog.exec() if ret == QDialog.DialogCode.Accepted: try: dev_info = w_add.get_device_info() except ValueError as e: QMessageBox.critical(self, "Error", f"Invalid input:\n{e}") continue self.device_manager.add_device_info(dev_info) break else: break
[docs] class DeviceManagerWidget(QWidget): """ Widget that contains device widgets for all devices that are handled by a device manager instance. """ def __init__(self, device_manager: DeviceManager, parent=None): super().__init__(parent=parent) self.l = QVBoxLayout() self.setLayout(self.l) self.l.addStretch() self.device_manager = device_manager self._device_widgets = {} # not required, the signals will handle this even for existing devices # for dev_key in self.device_manager: # self._add_device(dev_key) self.device_manager.e_device_updated.connect(lambda key: self._update_device(key)) self.device_manager.e_device_added.connect(lambda key: self._update_device(key)) # update may also add one if it does not exist yet self.device_manager.e_device_removed.connect(lambda key: self._remove_device(key)) # self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) def _get_new_device_widget(self, dev_key): """ Create a widget for the device with `dev_key` """ kw = dict(key=dev_key, icon_button_f=lambda: self.edit_device_dialog(dev_key)) if self.device_manager.is_connected(dev_key): w = DeviceWidget.get_widget(self.device_manager[dev_key], name=self.device_manager.get_info(dev_key).name, **kw) else: w = DeviceWidget(device=None, **kw) return w def _add_device(self, dev_key): """ Create and add a widget for device with `device_key` to the list. """ log.debug(f"Adding widget for device '{dev_key}'") w = self._get_new_device_widget(dev_key) self._device_widgets[dev_key] = w self._add_device_widget(w) def _remove_device(self, dev_key): """ Remove the widget of device with `dev_key` from the list """ log.debug(f"Removed widget for device '{dev_key}'") w = self._device_widgets.pop(dev_key) self.l.removeWidget(w) w.deleteLater() def _add_device_widget(self, dev: DeviceWidget|QLabel): """ Append a widget into the list """ self.l.insertWidget(self.l.count() - 1, dev, stretch=0) def _update_device_widget(self, dev_key: str): """ Update the widget for device `dev_key`, by deleting the old widget and creating a new one. """ # w = self._device_widgets[key].update() w_old = self._device_widgets[dev_key] w_new = self._get_new_device_widget(dev_key) layout_item = self.l.replaceWidget(w_old, w_new) self._device_widgets[dev_key] = w_new # layout_item.deleteLater() # layout_item.widget().deleteLater() w_old.deleteLater() log.debug(f"Updated widget for device '{dev_key}'") def _update_device(self, dev_key): """ Update or add a widget for the device with `dev_key`. """ log.debug(f"Updating or adding device '{dev_key}'") # either updated or added if dev_key in self.device_manager: # added if dev_key not in self._device_widgets: self._add_device(dev_key) # updated else: self._update_device_widget(dev_key) # removed elif dev_key in self._device_widgets: self._remove_device(dev_key) else: raise KeyError(f"Invalid device key: '{dev_key}'") def _update_devices(self): log.info(f"Updating devices list widget") print("UPDATE!!!") def edit_device_dialog(self, dev_key: str): dialog = DeviceSettingsDialog(self.device_manager, dev_key) dialog.exec() def __len__(self): return len(self._device_widgets) def __getitem__(self, idx): return self._device_widgets[idx]