# -*- coding: utf-8 -*-
"""
Created the 28/07/2022
@author: Sebastien Weber
"""
from typing import List
import sys
from pint.errors import DimensionalityError
from qtpy import QtWidgets
from qtpy.QtCore import Signal, Qt
from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QWidget, QToolBar, QComboBox
from pymodaq.utils.daq_utils import ThreadCommand
from pymodaq.utils.gui_utils.custom_app import CustomApp
from pymodaq.utils.gui_utils.widgets import PushButtonIcon, LabelWithFont, SpinBox, QSpinBox_ro, QLED, QSpinBoxWithShortcut
from pymodaq.control_modules.utils import ControlModuleUI
from pymodaq.utils.gui_utils import DockArea
from pymodaq.utils.plotting.data_viewers.viewer import ViewerDispatcher
from pymodaq.utils.data import DataWithAxes, DataToExport, DataActuator
from pymodaq.utils.config import Config
config = Config()
[docs]class DAQ_Move_UI(ControlModuleUI):
"""DAQ_Move user interface.
This class manages the UI and emit dedicated signals depending on actions from the user
Attributes
----------
command_sig: Signal[Threadcommand]
This signal is emitted whenever some actions done by the user has to be
applied on the main module. Possible commands are:
* init
* quit
* get_value
* loop_get_value
* find_home
* stop
* move_abs
* move_rel
* show_log
* actuator_changed
* rel_value
* show_config
* show_plugin_config
Methods
-------
display_value(value: float)
Update the display of the actuator's value on the UI
do_init()
Programmatic init
See Also
--------
pymodaq.utils.daq_utils.ThreadCommand
"""
def __init__(self, parent, title="DAQ_Move"):
super().__init__(parent)
self.title = title
self._unit = ''
self.setup_ui()
self.enable_move_buttons(False)
[docs] def display_value(self, value: DataActuator):
try:
self.current_value_sb.setValue(value.value(self._unit))
except DimensionalityError as e:
value.force_units(self._unit)
self.current_value_sb.setValue(value.value())
@property
def actuator_init(self):
"""bool: the status of the init LED."""
return self.ini_state_led.get_state()
@actuator_init.setter
def actuator_init(self, status):
self.ini_state_led.set_as(status)
self.enable_move_buttons(status)
@property
def actuator(self):
return self.actuators_combo.currentText()
@actuator.setter
def actuator(self, act_name: str):
self.actuators_combo.setCurrentText(act_name)
@property
def actuators(self):
return [self.actuators_combo.itemText(ind) for ind in range(self.actuators_combo.count())]
@actuators.setter
def actuators(self, actuators: List[str]):
self.actuators_combo.clear()
self.actuators_combo.addItems(actuators)
@property
def move_done(self):
"""bool: the status of the move_done LED."""
return self.move_done_led.get_state()
@move_done.setter
def move_done(self, status):
self.move_done_led.set_as(status)
def set_settings_tree(self, tree):
self.settings_ui.layout().addWidget(tree)
def enable_move_buttons(self, status):
self.abs_value_sb.setEnabled(status)
self.abs_value_sb_2.setEnabled(status)
self.control_ui.setEnabled(status)
self.get_action('move_abs').setEnabled(status)
self.get_action('move_abs_2').setEnabled(status)
[docs] def set_abs_spinbox_properties(self, **properties):
""" Change the Spinbox properties
Parameters
--------
properties: dict or named parameters
possible keys are :
* decimals: to set the number of displayed decimals
* 'minimum': to set the minimum value
* 'maximum': to set the maximum value
* 'step': to set the step value
"""
if 'decimals' in properties:
self.abs_value_sb.setDecimals(properties['decimals'])
self.abs_value_sb_2.setDecimals(properties['decimals'])
self.abs_value_sb_bis.setDecimals(properties['decimals'])
if 'minimum' in properties:
self.abs_value_sb.setMinimum(properties['minimum'])
self.abs_value_sb_2.setMinimum(properties['minimum'])
self.abs_value_sb_bis.setMinimum(properties['minimum'])
if 'maximum' in properties:
self.abs_value_sb.setMaximum(properties['maximum'])
self.abs_value_sb_2.setMaximum(properties['maximum'])
self.abs_value_sb_bis.setMaximum(properties['maximum'])
if 'step' in properties:
self.abs_value_sb.setSingleStep(properties['step'])
self.abs_value_sb_2.setSingleStep(properties['step'])
self.abs_value_sb_bis.setSingleStep(properties['step'])
def show_data(self, data: DataToExport):
self.viewer.show_data(data)
[docs] def setup_docks(self):
self.parent.setLayout(QVBoxLayout())
#self.parent.layout().setSizeConstraint(QHBoxLayout.SetFixedSize)
self.parent.layout().setContentsMargins(2, 2, 2, 2)
widget = QWidget()
widget.setLayout(QHBoxLayout())
splitter_hor = QtWidgets.QSplitter(Qt.Orientation.Horizontal)
widget.layout().addWidget(splitter_hor)
self.parent.layout().addWidget(widget)
self.main_ui = QWidget()
self.control_ui = QWidget()
self.settings_ui = QWidget()
self.graph_ui = QWidget()
self.graph_ui.setLayout(QtWidgets.QHBoxLayout())
self.graph_ui.layout().setContentsMargins(0, 0, 0, 0)
dockarea = DockArea()
self.graph_ui.layout().addWidget(dockarea)
self.viewer = ViewerDispatcher(dockarea)
left_widget = QWidget()
left_widget.setLayout(QVBoxLayout())
left_widget.layout().addWidget(self.main_ui)
left_widget.layout().addWidget(self.control_ui)
left_widget.layout().setContentsMargins(0, 0, 0, 0)
left_widget.layout().addStretch()
splitter_hor.addWidget(left_widget)
splitter_hor.addWidget(self.settings_ui)
#widget.layout().addStretch()
# populate the main ui
self.move_toolbar = QToolBar()
self.main_ui.setLayout(QGridLayout())
self.main_ui.layout().setSpacing(0)
self.main_ui.layout().setContentsMargins(0, 0, 0, 0)
self.main_ui.layout().addWidget(self.toolbar, 0, 0, 1, 2)
self.main_ui.layout().addWidget(self.move_toolbar, 1, 0, 1, 2)
self.abs_value_sb = QSpinBoxWithShortcut(step=0.1, dec=True, siPrefix=config('actuator', 'siprefix'))
self.abs_value_sb.setStyleSheet("background-color : lightgreen; color: black")
self.abs_value_sb_2 = QSpinBoxWithShortcut(step=0.1, dec=True, siPrefix=config('actuator', 'siprefix'))
self.abs_value_sb_2.setStyleSheet("background-color : lightcoral; color: black")
self.move_toolbar.addWidget(self.abs_value_sb)
self.move_toolbar.addWidget(self.abs_value_sb_2)
self.main_ui.layout().addWidget(LabelWithFont('Actuator:'), 2, 0)
self.actuators_combo = QComboBox()
self.main_ui.layout().addWidget(self.actuators_combo, 2, 1)
self.ini_actuator_pb = PushButtonIcon('ini', 'Initialization', checkable=True,
tip='Start This actuator initialization')
self.main_ui.layout().addWidget(self.ini_actuator_pb, 3, 0)
self.ini_state_led = QLED(readonly=True)
self.main_ui.layout().addWidget(self.ini_state_led, 3, 1)
self.main_ui.layout().addWidget(LabelWithFont('Current value:'), 4, 0)
self.move_done_led = QLED(readonly=True)
self.main_ui.layout().addWidget(self.move_done_led, 4, 1)
self.current_value_sb = QSpinBox_ro(font_size=20, min_height=27,
siPrefix=config('actuator', 'siprefix'),
)
self.main_ui.layout().addWidget(self.current_value_sb, 5, 0, 1, 2)
# populate the control ui
self.control_ui.setLayout(QGridLayout())
self.control_ui.layout().addWidget(LabelWithFont('Abs. Value'), 0, 0)
self.find_home_pb = PushButtonIcon('home2', 'Find Home')
self.control_ui.layout().addWidget(self.find_home_pb, 0, 1)
self.abs_value_sb_bis = QSpinBoxWithShortcut(step=0.1, dec=True, siPrefix=config('actuator', 'siprefix'))
self.control_ui.layout().addWidget(self.abs_value_sb_bis, 1, 0)
self.move_abs_pb = PushButtonIcon('Move', 'Set Abs.',
tip='Set the value of the actuator to the set absolute value')
self.control_ui.layout().addWidget(self.move_abs_pb, 1, 1)
self.control_ui.layout().addWidget(LabelWithFont('Rel. Increment'), 2, 0)
self.move_rel_plus_pb = PushButtonIcon('MoveUp', 'Set Rel. (+)')
self.control_ui.layout().addWidget(self.move_rel_plus_pb, 2, 1)
self.rel_value_sb = QSpinBoxWithShortcut(step=0.1, dec=True, siPrefix=config('actuator', 'siprefix'), key_sequences=("Ctrl+Enter","Ctrl+Shift+Enter"),)
self.control_ui.layout().addWidget(self.rel_value_sb, 3, 0)
self.move_rel_minus_pb = PushButtonIcon('MoveDown', 'Set Rel. (-)')
self.control_ui.layout().addWidget(self.move_rel_minus_pb, 3, 1)
self.stop_pb = PushButtonIcon('stop', 'Stop')
self.control_ui.layout().addWidget(self.stop_pb, 4, 0)
self.get_value_pb = PushButtonIcon('Help_32', 'Update Value')
self.control_ui.layout().addWidget(self.get_value_pb, 4, 1)
self.control_ui.layout().setContentsMargins(0, 0, 0, 0)
self.settings_ui.setLayout(QHBoxLayout())
self.settings_ui.layout().setContentsMargins(0, 0, 0, 0)
self.control_ui.setVisible(False)
self.settings_ui.setVisible(False)
self.statusbar = QtWidgets.QStatusBar()
self.statusbar.setMaximumHeight(30)
self.parent.layout().addWidget(self.statusbar)
[docs] def set_unit_as_suffix(self, unit: str):
"""Will append the actuator units in the value display"""
self._unit = unit
self.current_value_sb.setOpts(suffix=unit)
self.abs_value_sb_bis.setOpts(suffix=unit)
self.abs_value_sb.setOpts(suffix=unit)
self.abs_value_sb_2.setOpts(suffix=unit)
self.rel_value_sb.setOpts(suffix=unit)
[docs] def setup_actions(self):
self.add_action('move_abs', 'Move Abs', 'go_to_1', "Move to the set absolute value",
toolbar=self.move_toolbar)
self.add_action('move_abs_2', 'Move Abs', 'go_to_2', "Move to the other set absolute value",
toolbar=self.move_toolbar)
self.add_action('show_controls', 'Show Controls', 'Add_Step', "Show more controls", checkable=True,
toolbar=self.toolbar)
self.add_action('show_settings', 'Show Settings', 'tree', "Show Settings", checkable=True,
toolbar=self.toolbar)
self.add_action('show_config', 'Show Config', 'Settings', "Show PyMoDAQ Config", checkable=False,
toolbar=self.toolbar)
self.add_action('show_graph', 'Show Graph', 'graph', "Show Graph", checkable=True,
toolbar=self.toolbar)
self.add_action('refresh_value', 'Refresh', 'Refresh2', "Refresh Value", checkable=True,
toolbar=self.toolbar)
self.add_action('stop', 'Stop', 'stop', "Stop Motion", checkable=False,
toolbar=self.toolbar)
self.add_action('quit', 'Quit the module', 'close2')
self.add_action('log', 'Show Log file', 'information2')
self.toolbar.addWidget(LabelWithFont(self.title, font_name="Tahoma", font_size=14, isbold=True, isitalic=True))
[docs] def connect_things(self):
self.connect_action('show_controls', lambda show: self.control_ui.setVisible(show))
self.connect_action('show_settings', lambda show: self.settings_ui.setVisible(show))
self.connect_action('show_graph', lambda show: self.graph_ui.setVisible(show))
self.connect_action('quit', lambda: self.command_sig.emit(ThreadCommand('quit', )))
self.connect_action('refresh_value',
lambda do_refresh: self.command_sig.emit(ThreadCommand('loop_get_value', do_refresh)))
self.connect_action('move_abs', lambda: self.emit_move_abs(self.abs_value_sb))
self.connect_action('move_abs_2', lambda: self.emit_move_abs(self.abs_value_sb_2))
self.connect_action('log', lambda: self.command_sig.emit(ThreadCommand('show_log', )))
self.connect_action('stop', lambda: self.command_sig.emit(ThreadCommand('stop', )))
self.connect_action('show_config', lambda: self.command_sig.emit(ThreadCommand('show_config', )))
self.move_abs_pb.clicked.connect(lambda: self.emit_move_abs(self.abs_value_sb_bis))
self.abs_value_sb.shortcut["Ctrl+Enter"].activated.connect(lambda: self.emit_move_abs(self.abs_value_sb))
self.abs_value_sb_2.shortcut["Ctrl+Enter"].activated.connect(lambda: self.emit_move_abs(self.abs_value_sb_2))
self.abs_value_sb_bis.shortcut["Ctrl+Enter"].activated.connect(lambda: self.emit_move_abs(self.abs_value_sb_bis))
self.rel_value_sb.valueChanged.connect(lambda: self.command_sig.emit(
ThreadCommand('rel_value', self.rel_value_sb.value())))
self.rel_value_sb.shortcut["Ctrl+Enter"].activated.connect(lambda: self.emit_move_rel('+'))
self.rel_value_sb.shortcut["Ctrl+Shift+Enter"].activated.connect(lambda: self.emit_move_rel('-'))
self.move_rel_plus_pb.clicked.connect(lambda: self.emit_move_rel('+'))
self.move_rel_minus_pb.clicked.connect(lambda: self.emit_move_rel('-'))
self.find_home_pb.clicked.connect(lambda: self.command_sig.emit(ThreadCommand('find_home', )))
self.stop_pb.clicked.connect(lambda: self.command_sig.emit(ThreadCommand('stop', )))
self.get_value_pb.clicked.connect(lambda: self.command_sig.emit(ThreadCommand('get_value', )))
self.ini_actuator_pb.clicked.connect(self.send_init)
self.actuators_combo.currentTextChanged.connect(
lambda act: self.command_sig.emit(ThreadCommand('actuator_changed', act)))
[docs] def do_init(self, do_init=True):
"""Programmatically press the Init button
API entry
Parameters
----------
do_init: bool
will fire the Init button depending on the argument value and the button check state
"""
if do_init is not self.ini_actuator_pb.isChecked():
self.ini_actuator_pb.click()
[docs] def send_init(self, checked):
self.actuators_combo.setEnabled(not checked)
self.command_sig.emit(ThreadCommand('init', [self.ini_actuator_pb.isChecked(),
self.actuators_combo.currentText()]))
def emit_move_abs(self, spinbox):
spinbox.editingFinished.emit()
self.command_sig.emit(ThreadCommand('move_abs', DataActuator(data=spinbox.value(),
units=self._unit)))
def emit_move_rel(self, sign):
self.command_sig.emit(ThreadCommand(
'move_rel',
DataActuator(data=self.rel_value_sb.value() * (1 if sign == '+' else -1),
units=self._unit)))
def close(self):
self.graph_ui.close()
self.parent.close()
def main(init_qt=True):
from pymodaq.utils.gui_utils.dock import DockArea
if init_qt: # used for the test suite
app = QtWidgets.QApplication(sys.argv)
actuators = [f'act{ind}' for ind in range(5)]
widget = QtWidgets.QWidget()
prog = DAQ_Move_UI(widget, title="test")
widget.show()
def print_command_sig(cmd_sig):
print(cmd_sig)
if cmd_sig.command == 'init':
prog.enable_move_buttons(True)
prog.command_sig.connect(print_command_sig)
prog.actuators = actuators
if init_qt:
sys.exit(app.exec_())
return prog, widget
if __name__ == '__main__':
main()