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)