Source code for qudi.gui.taskrunner.taskwidget

# -*- coding: utf-8 -*-
"""
This file contains a widget to control a ModuleTask and display its state.

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/>.
"""

__all__ = ['TaskWidget']

import os
from typing import Type, Optional, Dict, Tuple, Any, Iterable
from PySide2 import QtCore, QtWidgets, QtGui

from qudi.util.helpers import is_integer
from qudi.util.paths import get_artwork_dir
from qudi.util.parameters import ParameterWidgetMapper
from qudi.util.widgets.loading_indicator import CircleLoadingIndicator
from qudi.util.widgets.separator_lines import VerticalLine
from qudi.core.scripting.moduletask import ModuleTask


[docs] class TestToolButton(QtWidgets.QToolButton):
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
def resizeEvent(self, event: QtGui.QResizeEvent) -> None: super().resizeEvent(event) self.setIconSize(event.size())
[docs] class TaskWidget(QtWidgets.QWidget): """QWidget to control a ModuleTask and display its state. """ sigStartTask = QtCore.Signal(dict) # parameters sigInterruptTask = QtCore.Signal() _ParamWidgetsIterable = Iterable[Tuple[QtWidgets.QLabel, QtWidgets.QWidget]] _ParamWidgetsDict = Dict[str, Tuple[QtWidgets.QLabel, QtWidgets.QWidget]]
[docs] def __init__(self, *args, task_type: Type[ModuleTask], max_columns: Optional[int] = None, max_rows: Optional[int] = None, **kwargs): super().__init__(*args, **kwargs) if max_rows is not None and max_columns is not None: raise ValueError('Can either set "max_columns" OR "max_rows" but not both.') if max_columns is not None and not is_integer(max_columns): raise ValueError('"max_columns" must be None or integer value') if max_rows is not None and not is_integer(max_rows): raise ValueError('"max_rows" must be None or integer value') if max_rows is None and max_columns is None: max_rows = 8 elif max_rows is None: number_of_widgets = len(task_type.call_parameters()) max_rows = number_of_widgets // max_columns + number_of_widgets % max_columns # Create control button and state label. Arrange them in a sub-layout and connect button. # Also add animated busy-indicator icon_dir = os.path.join(get_artwork_dir(), 'icons') self._play_icon = QtGui.QIcon(os.path.join(icon_dir, 'media-playback-start')) self._stop_icon = QtGui.QIcon(os.path.join(icon_dir, 'media-playback-stop')) self.state_label = QtWidgets.QLabel('stopped') self.state_label.setAlignment(QtCore.Qt.AlignCenter) font = self.state_label.font() font.setBold(True) self.state_label.setFont(font) control_width = self.state_label.sizeHint().width() * 2 self.run_interrupt_button = QtWidgets.QToolButton() self.run_interrupt_button.setIcon(self._play_icon) self.run_interrupt_button.setToolButtonStyle(QtGui.Qt.ToolButtonIconOnly) self.run_interrupt_button.setFixedWidth(control_width) self.run_interrupt_button.setFixedHeight(control_width) self.run_interrupt_button.setIconSize(self.run_interrupt_button.size()) self.running_indicator = CircleLoadingIndicator() self.running_indicator.setFixedWidth(control_width // 1.5) self.running_indicator.setFixedHeight(control_width // 1.5) tmp = self.running_indicator.sizePolicy() tmp.setRetainSizeWhenHidden(True) self.running_indicator.setSizePolicy(tmp) ctrl_layout = QtWidgets.QVBoxLayout() ctrl_layout.addStretch(1) ctrl_layout.addWidget(self.running_indicator, 0, QtCore.Qt.AlignCenter) ctrl_layout.addWidget(self.state_label) ctrl_layout.addWidget(self.run_interrupt_button, 0, QtCore.Qt.AlignCenter) self.running_indicator.hide() self.run_interrupt_button.clicked.connect(self._run_interrupt_clicked) # Create task parameter editors and put them in a sub-layout self.parameter_widgets = self.__create_parameter_editor_widgets(task_type) param_layout = self.__layout_parameter_widgets(self.parameter_widgets.values(), max_rows) # Add sub-layouts to main layout main_layout = QtWidgets.QHBoxLayout() main_layout.addLayout(param_layout) main_layout.addWidget(VerticalLine()) main_layout.addLayout(ctrl_layout) self.setLayout(main_layout) # State flag to indicate current button functionality (start or interrupt) self._interrupt_enabled = False
@staticmethod def __create_parameter_editor_widgets(task_type: Type[ModuleTask]) -> _ParamWidgetsDict: """Helper function to create editor widgets and labels for each ModuleTask call parameter. """ task_parameters = task_type.call_parameters() param_widgets = dict() for param_name, param in task_parameters.items(): editor = ParameterWidgetMapper.widget_for_parameter(param) if editor is None: editor = QtWidgets.QLabel('Unknown parameter type') editor.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) else: editor = editor() # ToDo: Set default values here label = QtWidgets.QLabel(f'{param_name}:') label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight) param_widgets[param_name] = (label, editor) return param_widgets @staticmethod def __layout_parameter_widgets(param_widgets: _ParamWidgetsIterable, max_rows: int) -> QtWidgets.QGridLayout: """Helper function to layout parameter widgets in a QGridLayout.""" row = 0 column = 0 layout = QtWidgets.QGridLayout() layout.setColumnStretch(1, 1) max_height = 0 for label, editor in param_widgets: layout.addWidget(label, row, column) layout.addWidget(editor, row, column + 1) max_height = max(max_height, editor.sizeHint().height()) if row + 1 >= max_rows: row = 0 column += 2 layout.setColumnStretch(column + 1, 1) else: row += 1 for ii in range(row): layout.setRowMinimumHeight(ii, max_height) layout.setRowStretch(row, 1) return layout @QtCore.Slot() def _run_interrupt_clicked(self) -> None: """Callback method for button clicks.""" if self._interrupt_enabled: self.sigInterruptTask.emit() else: self.run_interrupt_button.setEnabled(False) self.sigStartTask.emit(self.get_parameters()) @QtCore.Slot() def task_started(self) -> None: self._interrupt_enabled = True self.run_interrupt_button.setIcon(self._stop_icon) self.run_interrupt_button.setEnabled(True) self.running_indicator.show() @QtCore.Slot(str) def task_state_changed(self, new_state: str) -> None: """Callback method for ModuleTask state changes.""" self.state_label.setText(new_state) @QtCore.Slot(object, bool) def task_finished(self, result: Any, success: bool) -> None: """Callback for task finished event.""" self._interrupt_enabled = False self.run_interrupt_button.setIcon(self._play_icon) self.run_interrupt_button.setEnabled(True) self.running_indicator.hide() self.set_task_result(result, success) @QtCore.Slot(object, bool) def set_task_result(self, result: Any, success: bool) -> None: """Updates the task result display.""" print(result, success) def get_parameters(self) -> Dict[str, Any]: """Reads parameters from parameter editors and returns them in a dict.""" parameters = dict() for param_name, (_, editor) in self.parameter_widgets.items(): if isinstance(editor, QtWidgets.QLabel): continue try: parameters[param_name] = editor.value() except AttributeError: try: parameters[param_name] = editor.isChecked() except AttributeError: parameters[param_name] = editor.text() return parameters