From a93f51b1b882818afa05c96947bb54955cd412a3 Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 23 Oct 2014 11:08:47 +0200 Subject: Implement feat-155: better API and docs for MacroButton - substitute MacroButton.toggleProgress by MacroButton.showProgress - add MacroButton.connectArgEditors - prefix some effectively-private slot names with "_" - document all public API - reimplement MacroButtonAbortDoor - simplify example code --- .../qt/qtgui/extra_macroexecutor/macrobutton.py | 221 +++++++++++---------- 1 file changed, 116 insertions(+), 105 deletions(-) (limited to 'src') diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macrobutton.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macrobutton.py index 4cf8940b..821a47ce 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macrobutton.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macrobutton.py @@ -24,7 +24,7 @@ ############################################################################## """ -macrobutton.py: +This module provides a button for executing macros """ __all__ = ['MacroButton'] @@ -35,15 +35,19 @@ import uuid import PyTango import taurus -from taurus.core import TaurusEventType +from taurus.core import TaurusEventType, TaurusDevice from taurus.external.qt import Qt from taurus.qt.qtgui.container import TaurusWidget +from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.dialog import ProtectTaurusMessageBox from taurus.core.util.colors import DEVICE_STATE_PALETTE from taurus.qt.qtgui.util.ui import UILoadable class DoorStateListener(Qt.QObject): + '''A listener of Change and periodic events from a Door State attribute. + It converts the received Tango events and emits a Qt signal + ''' __pyqtSignals__ = ["doorStateChanged"] @@ -56,22 +60,12 @@ class DoorStateListener(Qt.QObject): @UILoadable(with_ui='ui') class MacroButton(TaurusWidget): - ''' This class is intended to be used as a button to execute macros. - The model must be a valid door. - - ..note:: Not implemented but will be needed: set an icon - - ..note:: + ''' A button to execute/pause/stop macros. The model must be a valid door. - It may be useful to have a slot update_macro_param that any qwidget signal may be - connected to. The only condition might be that the sender widget should have an - attribute 'widget.macro_param_index' in order to determine where the value has - to be added. + .. todo:: Not implemented but will be needed: set an icon - ..note:: - - It may be useful to have all the streams from qdoor available somehow - (right-click?) + .. todo:: It may be useful to have all the streams from qdoor available + somehow (right-click?) ''' __pyqtSignals__ = ['statusUpdated', 'resultUpdated'] @@ -89,30 +83,45 @@ class MacroButton(TaurusWidget): self.ui.progress.setValue(0) self.ui.button.setCheckable(True) - self.connect(self.ui.button, Qt.SIGNAL('clicked()'), self.button_clicked) + self.connect(self.ui.button, Qt.SIGNAL('clicked()'), + self._onButtonClicked) def toggleProgress(self, visible): + '''deprecated''' + self.warning('toggleProgress is deprecated. Use showProgress') + self.showProgress(visible) + + def showProgress(self, visible): + '''Set wether the progress bar is shown + + :param visible: (bool) If True, the progress bar is shown. Otherwise it + is hidden''' self.ui.progress.setVisible(visible) def setModel(self, model): + ''' + reimplemented from :class:`TaurusWidget`. A door device name is + expected as the model + ''' TaurusWidget.setModel(self, model) if self.door is not None: - self.disconnect(self.door, Qt.SIGNAL('macroStatusUpdated'), self.statusUpdated) - self.disconnect(self.door, Qt.SIGNAL('resultUpdated'), self.resultUpdated) + self.disconnect(self.door, Qt.SIGNAL('macroStatusUpdated'), self._statusUpdated) + self.disconnect(self.door, Qt.SIGNAL('resultUpdated'), self._resultUpdated) self.door.getAttribute('State').removeListener(self.door_state_listener) try: self.door = taurus.Device(model) except: return - self.connect(self.door, Qt.SIGNAL('macroStatusUpdated'), self.statusUpdated) - self.connect(self.door, Qt.SIGNAL('resultUpdated'), self.resultUpdated) + self.connect(self.door, Qt.SIGNAL('macroStatusUpdated'), self._statusUpdated) + self.connect(self.door, Qt.SIGNAL('resultUpdated'), self._resultUpdated) # Manage Door Tango States self.door_state_listener = DoorStateListener() - self.connect(self.door_state_listener, Qt.SIGNAL('doorStateChanged'), self.doorStateChanged) + self.connect(self.door_state_listener, Qt.SIGNAL('doorStateChanged'), self._doorStateChanged) self.door.getAttribute('State').addListener(self.door_state_listener) - def doorStateChanged(self, state): + def _doorStateChanged(self, state): + '''slot called on door state changes''' color = '#' + DEVICE_STATE_PALETTE.hex(state) stylesheet = 'QFrame{border: 4px solid %s;}' % color self.ui.frame.setStyleSheet(stylesheet) @@ -126,7 +135,8 @@ class MacroButton(TaurusWidget): self.ui.progress.setEnabled(door_available) - def statusUpdated(self, *args): + def _statusUpdated(self, *args): + '''slot called on status changes''' # SHOULD SEE THE DOCUMENTATION ABOUT THE ARGS AND ALSO THE STATUS STATE MACHINE # ARGS FORMAT IS (GUESSING WITH PRINT STATEMENTS) # e.g. ((, [{u'step': 100.0, u'state': u'stop', u'range': [0.0, 100.0], u'id': u'b226f5e8-c807-11e0-8abe-001d0969db5b'}]),) @@ -161,7 +171,8 @@ class MacroButton(TaurusWidget): self.emit(Qt.SIGNAL('statusUpdated'), status_dict) - def resultUpdated(self, *args): + def _resultUpdated(self, *args): + '''slot called on result changes''' # ARGS APPEAR TO BE EMPTY... SHOULD THEY CONTAIN THE RESULT ?!?!?! # I have to rely on the 'macro object' received in the last status update if self.running_macro is None: @@ -170,32 +181,68 @@ class MacroButton(TaurusWidget): self.emit(Qt.SIGNAL('resultUpdated'), result) def setText(self, text): + '''set the button text + + :param text: (str) text for the button + ''' self.setButtonText(text) - def setButtonText(self, text): + def setButtonText(self, text): + '''same as :meth:`setText` + ''' # SHOULD ALSO BE POSSIBLE TO SET AN ICON self.ui.button.setText(text) - def setMacroName(self, macro_name): - self.macro_name = str(macro_name) + def setMacroName(self, name): + '''set the name of the macro to be executed + + :param name: (str) text for the button + ''' + self.macro_name = str(name) def updateMacroArgument(self, index, value): + '''change a given argument + + :param index: (int) positional index for this argument + :param value: (str) value for this argument + ''' + #make sure that the macro_args is at least as long as index while len(self.macro_args) < index + 1: self.macro_args.append('') - + #update the given argument self.macro_args[index] = str(value) - + def updateMacroArgumentFromSignal(self, index, obj, signal): - self.connect(obj, signal, functools.partial(self.updateMacroArgument, index)) + '''deprecated''' + msg = 'updateMacroArgumentFromSignal is deprecated. connectArgEditors' + self.warning(msg) + self.connect(obj, signal, + functools.partial(self.updateMacroArgument, index)) + + def connectArgEditors(self, signals): + '''Associate signals to argument changes. + + :param signals: (seq) An ordered sequence of (`obj`, `sig`) + tuples , where `obj` is a parameter editor object and + `sig` is a signature for a signal emitted by `obj` which + provides the value of a parameter as its argument. + Each (`obj`, `sig`) tuple is associated to parameter + corresponding to its position in the `signals` sequence. + ''' + + for i,(obj,sig) in enumerate(signals): + self.connect(obj, Qt.SIGNAL(sig), + functools.partial(self.updateMacroArgument, i)) - def button_clicked(self): + def _onButtonClicked(self): if self.ui.button.isChecked(): self.runMacro() else: self.abort() - @ProtectTaurusMessageBox(msg='An error occurred trying to execute the macro.') + @ProtectTaurusMessageBox(msg='Error while executing the macro.') def runMacro(self): + '''execute the macro with the current arguments''' if self.door is None: return @@ -215,6 +262,7 @@ class MacroButton(TaurusWidget): raise e def abort(self): + '''abort the macro.''' if self.door is None: return self.door.PauseMacro() @@ -235,42 +283,35 @@ class MacroButton(TaurusWidget): @classmethod def getQtDesignerPluginInfo(cls): + '''reimplemented from :class:`TaurusWidget`''' return {'container': False, 'group': 'Taurus Sardana', 'module': 'taurus.qt.qtgui.extra_macroexecutor', 'icon': ':/designer/pushbutton.png'} - -class MacroButtonAbortDoor(TaurusWidget): +class MacroButtonAbortDoor(Qt.QPushButton, TaurusBaseWidget): + '''A button for aborting macros on a door''' + #todo: why not inheriting from (TaurusBaseComponent, Qt.QPushButton)? def __init__(self, parent=None, designMode=False): - TaurusWidget.__init__(self, parent, designMode) - self.setLayout(Qt.QGridLayout()) - self.layout().setMargin(0) - self.layout().setSpacing(0) - - self.btn_abort = Qt.QPushButton('Abort') - sizePolicy = Qt.QSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - self.btn_abort.setSizePolicy(sizePolicy) - - self.door = None - self.btn_abort.setToolTip('Abort Macro') - - self.layout().addWidget(self.btn_abort, 0, 0) - self.connect(self.btn_abort, Qt.SIGNAL('clicked()'), self.abort) - - def setModel(self, model): - TaurusWidget.setModel(self, model) - try: self.door = taurus.Device(model) - except: self.door = None + name = self.__class__.__name__ + self.call__init__wo_kw(Qt.QPushButton, parent) + self.call__init__(TaurusBaseWidget, name, designMode=designMode) + + self.setText('Abort') + self.setToolTip('Abort Macro') + self.connect(self, Qt.SIGNAL('clicked()'), self.abort) + + def getModelClass(self): + '''reimplemented from :class:`TaurusBaseWidget`''' + return TaurusDevice @ProtectTaurusMessageBox(msg='An error occurred trying to abort the macro.') def abort(self): - if self.door is not None: - self.door.stopMacro() - + '''stops macros''' + door = self.getModelObj() + if door is not None: + door.stopMacro() if __name__ == '__main__': @@ -298,42 +339,23 @@ if __name__ == '__main__': w.setLayout(Qt.QGridLayout()) col = 0 - clear_button = Qt.QPushButton('clear') - w.layout().addWidget(clear_button, 0, col, 2, 1) - - col += 1 w.layout().addWidget(Qt.QLabel('macro name'), 0, col) macro_name = Qt.QLineEdit() w.layout().addWidget(macro_name, 1, col) - col += 1 - w.layout().addWidget(Qt.QLabel('arg0'), 0, col) - arg0 = Qt.QLineEdit() - w.layout().addWidget(arg0, 1, col) + _argEditors = [] + for a in range(5): + col += 1 + w.layout().addWidget(Qt.QLabel('arg0'), 0, col) + argEdit = Qt.QLineEdit() + w.layout().addWidget(argEdit, 1, col) + _argEditors.append(argEdit) - col += 1 - w.layout().addWidget(Qt.QLabel('arg1'), 0, col) - arg1 = Qt.QLineEdit() - w.layout().addWidget(arg1, 1, col) - - col += 1 - w.layout().addWidget(Qt.QLabel('arg2'), 0, col) - arg2 = Qt.QLineEdit() - w.layout().addWidget(arg2, 1, col) - - col += 1 - w.layout().addWidget(Qt.QLabel('arg3'), 0, col) - arg3 = Qt.QLineEdit() - w.layout().addWidget(arg3, 1, col) - - col += 1 - w.layout().addWidget(Qt.QLabel('arg4'), 0, col) - arg4 = Qt.QLineEdit() - w.layout().addWidget(arg4, 1, col) from sardana.taurus.qt.qtcore.tango.sardana.macroserver import registerExtensions registerExtensions() mb = MacroButton() + mb.setModel(door_name) w.layout().addWidget(mb, 2, 0, 2, 7) @@ -348,20 +370,18 @@ if __name__ == '__main__': w.layout().addWidget(show_progress, 5, 0) mb_abort = MacroButtonAbortDoor() + mb_abort.setModel(door_name) + w.layout().addWidget(mb_abort, 5, 1) # Change macro name Qt.QObject.connect(macro_name, Qt.SIGNAL('textChanged(QString)'), mb.setMacroName) Qt.QObject.connect(macro_name, Qt.SIGNAL('textChanged(QString)'), mb.setButtonText) - - # Change Nth macro argument - mb.updateMacroArgumentFromSignal(0, arg0, Qt.SIGNAL('textChanged(QString)')) - mb.updateMacroArgumentFromSignal(1, arg1, Qt.SIGNAL('textChanged(QString)')) - mb.updateMacroArgumentFromSignal(2, arg2, Qt.SIGNAL('textChanged(QString)')) - mb.updateMacroArgumentFromSignal(3, arg3, Qt.SIGNAL('textChanged(QString)')) - mb.updateMacroArgumentFromSignal(4, arg4, Qt.SIGNAL('textChanged(QString)')) - + + # connect the argument editors + signals = [(e, 'textChanged(QString)') for e in _argEditors] + mb.connectArgEditors(signals) def update_result(result): result_label.setText(str(result)) @@ -370,16 +390,10 @@ if __name__ == '__main__': visible = show_progress.isChecked() mb.toggleProgress(visible) - def clear_params(): - for line_edit in [macro_name, arg0, arg1, arg2, arg3, arg4]: - line_edit.setText('') - # Toggle progressbar Qt.QObject.connect(show_progress, Qt.SIGNAL('stateChanged(int)'), toggle_progress) # Update possible macro result Qt.QObject.connect(mb, Qt.SIGNAL('resultUpdated'), update_result) - # Clear parameters - Qt.QObject.connect(clear_button, Qt.SIGNAL('clicked()'), clear_params) # Obtain a demo motor try: @@ -392,11 +406,8 @@ if __name__ == '__main__': # Since everything is now connected, the parameters will be updated macro_name.setText('ascan') - arg0.setText(demo_motor_name) - arg1.setText('1') - arg2.setText('10') - arg3.setText('5') - arg4.setText('0.1') + for e,v in zip(_argEditors, 'mot25 1 10 5 0.1'.split()): + e.setText(v) w.show() sys.exit(app.exec_()) -- cgit v1.2.3