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]