Source code for qudi.core.gui.main_gui.modulewidget

# -*- coding: utf-8 -*-
"""
This file contains a custom module widget for the Qudi manager GUI.

Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this
distribution and on <https://github.com/Ulm-IQO/qudi-core/>

This file is part of qudi.

Qudi is free software: you can redistribute it and/or modify it under the terms of
the GNU Lesser General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.

Qudi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with qudi.
If not, see <https://www.gnu.org/licenses/>.
"""

import os
from PySide2 import QtCore, QtGui, QtWidgets
from qudi.util.paths import get_artwork_dir
from qudi.util.mutex import Mutex


[docs] class ModuleFrameWidget(QtWidgets.QWidget): """ Custom module QWidget for the Qudi main GUI. """ sigActivateClicked = QtCore.Signal(str) sigDeactivateClicked = QtCore.Signal(str) sigReloadClicked = QtCore.Signal(str) sigCleanupClicked = QtCore.Signal(str)
[docs] def __init__(self, *args, module_name=None, **kwargs): super().__init__(*args, **kwargs) # Create QToolButtons self.cleanup_button = QtWidgets.QToolButton() self.cleanup_button.setObjectName('cleanupButton') self.deactivate_button = QtWidgets.QToolButton() self.deactivate_button.setObjectName('deactivateButton') self.reload_button = QtWidgets.QToolButton() self.reload_button.setObjectName('reloadButton') # Set icons for QToolButtons icon_path = os.path.join(get_artwork_dir(), 'icons') self.cleanup_button.setIcon(QtGui.QIcon(os.path.join(icon_path, 'edit-clear'))) self.deactivate_button.setIcon(QtGui.QIcon(os.path.join(icon_path, 'edit-delete'))) self.reload_button.setIcon(QtGui.QIcon(os.path.join(icon_path, 'view-refresh'))) # Create activation pushbutton self.activate_button = QtWidgets.QPushButton('load/activate <module_name>') self.activate_button.setObjectName('loadButton') self.activate_button.setCheckable(True) self.activate_button.setMinimumWidth(200) self.activate_button.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) # Create status label self.status_label = QtWidgets.QLabel('Module status goes here...') self.status_label.setObjectName('statusLabel') # Set tooltips self.cleanup_button.setToolTip('Clean up module status file') self.deactivate_button.setToolTip('Deactivate module') self.reload_button.setToolTip('Reload module') self.activate_button.setToolTip('Load this module and all its dependencies') self.status_label.setToolTip('Displays module status information') # Combine all widgets in a layout and set as main layout layout = QtWidgets.QGridLayout() layout.addWidget(self.activate_button, 0, 0) layout.addWidget(self.reload_button, 0, 1) layout.addWidget(self.deactivate_button, 0, 2) layout.addWidget(self.cleanup_button, 0, 3) layout.addWidget(self.status_label, 1, 0, 1, 4) self.setLayout(layout) self._module_name = '' if module_name: self.set_module_name(module_name) self.activate_button.clicked.connect(self.activate_clicked) self.deactivate_button.clicked.connect(self.deactivate_clicked) self.reload_button.clicked.connect(self.reload_clicked) self.cleanup_button.clicked.connect(self.cleanup_clicked) return
def set_module_name(self, name): if name: self.activate_button.setText('Load {0}'.format(name)) self._module_name = name def set_module_state(self, state): if state == 'not loaded': self.activate_button.setText('Load {0}'.format(self._module_name)) self.cleanup_button.setEnabled(True) self.deactivate_button.setEnabled(False) self.reload_button.setEnabled(False) if self.activate_button.isChecked(): self.activate_button.setChecked(False) elif state == 'deactivated': self.activate_button.setText('Activate {0}'.format(self._module_name)) self.cleanup_button.setEnabled(True) self.deactivate_button.setEnabled(False) self.reload_button.setEnabled(True) if self.activate_button.isChecked(): self.activate_button.setChecked(False) else: self.activate_button.setText(self._module_name) self.cleanup_button.setEnabled(False) self.deactivate_button.setEnabled(True) self.reload_button.setEnabled(True) if not self.activate_button.isChecked(): self.activate_button.setChecked(True) self.status_label.setText('Module is {0}'.format(state)) def set_module_app_data(self, exists): self.cleanup_button.setEnabled(exists) @QtCore.Slot() def activate_clicked(self): self.sigActivateClicked.emit(self._module_name) @QtCore.Slot() def deactivate_clicked(self): self.sigDeactivateClicked.emit(self._module_name) @QtCore.Slot() def cleanup_clicked(self): self.sigCleanupClicked.emit(self._module_name) @QtCore.Slot() def reload_clicked(self): self.sigReloadClicked.emit(self._module_name)
[docs] class ModuleListModel(QtCore.QAbstractListModel): """ """
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._lock = Mutex() self._module_states = dict() self._module_app_data = dict() self._module_names = list()
def rowCount(self, parent): return len(self._module_names) def data(self, index, role): if not index.isValid(): return row = index.row() if row >= len(self._module_names): return name = self._module_names[row] state = self._module_states[name] app_data = self._module_app_data[name] if role == QtCore.Qt.DisplayRole: return name, state, app_data def flags(self, index): return QtCore.Qt.ItemNeverHasChildren | QtCore.Qt.ItemIsEnabled def append_module(self, name, state, app_data): with self._lock: if name in self._module_states: raise RuntimeError(f'Module with name "{name}" already present in ModuleListModel.') self.beginInsertRows(len(self._module_names)) self._module_names.append(name) self._module_states[name] = state self._module_app_data[name] = app_data self.endInsertRows() def remove_module(self, name): with self._lock: if name not in self._module_states: return row = self._module_names.index(name) self.beginRemoveRows(row, row + 1) del self._module_names[row] del self._module_states[name] del self._module_app_data[name] self.endRemoveRows() def reset_modules(self, state_dict, app_data_dict): if set(state_dict) != set(app_data_dict): raise RuntimeError('state_dict and app_data_dict must contain exactly the same keys.') with self._lock: self.beginResetModel() self._module_states = state_dict.copy() self._module_app_data = app_data_dict.copy() self._module_names = list(state_dict) self.endResetModel() def change_module_state(self, name, state): with self._lock: if name not in self._module_states: raise RuntimeError( f'Can not change module state in ModuleListModel. No module by the name ' f'"{name}" found.' ) self._module_states[name] = state row = self._module_names.index(name) self.dataChanged.emit(self.createIndex(row, 0), self.createIndex(row + 1, 0), (QtCore.Qt.DisplayRole,)) def change_app_data(self, name, exists): with self._lock: if name not in self._module_app_data: raise RuntimeError( f'Can not change module app status in ModuleListModel. No module by the name ' f'"{name}" found.' ) self._module_app_data[name] = exists row = self._module_names.index(name) self.dataChanged.emit(self.createIndex(row, 0), self.createIndex(row, 0))
[docs] class ModuleListItemDelegate(QtWidgets.QStyledItemDelegate): """ """ sigActivateClicked = QtCore.Signal(str) sigDeactivateClicked = QtCore.Signal(str) sigReloadClicked = QtCore.Signal(str) sigCleanupClicked = QtCore.Signal(str)
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.render_widget = ModuleFrameWidget() self.__origin = QtCore.QPoint()
def createEditor(self, parent, option, index): widget = ModuleFrameWidget(parent=parent) # Found no other way to pefectly match editor and rendered item view (using paint()) widget.setContentsMargins(2, 2, 2, 2) widget.sigActivateClicked.connect(self.sigActivateClicked) widget.sigDeactivateClicked.connect(self.sigDeactivateClicked) widget.sigReloadClicked.connect(self.sigReloadClicked) widget.sigCleanupClicked.connect(self.sigCleanupClicked) return widget def setEditorData(self, editor, index): data = index.data() if data: editor.set_module_name(data[0]) editor.set_module_state(data[1]) editor.set_module_app_data(data[2]) def setModelData(self, editor, model, index): pass def sizeHint(self, option=None, index=None): return self.render_widget.sizeHint() def paint(self, painter, option, index): """ """ name, state, app_data = index.data() self.render_widget.set_module_name(name) self.render_widget.set_module_state(state) self.render_widget.set_module_app_data(app_data) self.render_widget.setGeometry(option.rect) painter.save() painter.translate(option.rect.topLeft()) self.render_widget.render(painter, self.__origin) painter.restore()
[docs] class ModuleListView(QtWidgets.QListView): """ """
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setMouseTracking(True) delegate = ModuleListItemDelegate() self.setItemDelegate(delegate) self.setMinimumWidth(delegate.sizeHint().width()) self.setUniformItemSizes(True) self.setContentsMargins(0, 0, 0, 0) self.setSpacing(1) self.previous_index = QtCore.QModelIndex()
def mouseMoveEvent(self, event): index = self.indexAt(event.pos()) if index != self.previous_index: if self.previous_index.isValid(): self.closePersistentEditor(self.previous_index) if index.isValid(): self.openPersistentEditor(index) self.previous_index = index def leaveEvent(self, event): if self.previous_index.isValid(): self.closePersistentEditor(self.previous_index) self.previous_index = QtCore.QModelIndex()
[docs] class ModuleWidget(QtWidgets.QTabWidget): """ """ sigActivateModule = QtCore.Signal(str) sigDeactivateModule = QtCore.Signal(str) sigCleanupModule = QtCore.Signal(str) sigReloadModule = QtCore.Signal(str)
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) self.list_models = {'gui' : ModuleListModel(), 'logic' : ModuleListModel(), 'hardware': ModuleListModel()} self.list_views = {'gui' : ModuleListView(), 'logic' : ModuleListView(), 'hardware': ModuleListView()} self.addTab(self.list_views['gui'], 'GUI') self.addTab(self.list_views['logic'], 'Logic') self.addTab(self.list_views['hardware'], 'Hardware') for base, view in self.list_views.items(): view.setModel(self.list_models[base]) delegate = view.itemDelegate() delegate.sigActivateClicked.connect(self.sigActivateModule) delegate.sigDeactivateClicked.connect(self.sigDeactivateModule) delegate.sigReloadClicked.connect(self.sigReloadModule) delegate.sigCleanupClicked.connect(self.sigCleanupModule)
@QtCore.Slot(dict) def update_modules(self, modules_dict): for base, model in self.list_models.items(): model.reset_modules( {name: mod.state for name, mod in modules_dict.items() if mod.module_base == base}, {name: mod.has_app_data for name, mod in modules_dict.items() if mod.module_base == base} ) return @QtCore.Slot(str, str, str) def update_module_state(self, base, name, state): self.list_models[base].change_module_state(name, state) @QtCore.Slot(str, str, bool) def update_module_app_data(self, base, name, exists): self.list_models[base].change_app_data(name, exists)