From 3c3eaa166bd48c8a908169c9638764d263d149b8 Mon Sep 17 00:00:00 2001 From: Enrico Faulhaber Date: Wed, 13 Sep 2017 17:09:41 +0200 Subject: [PATCH] provide an mean to use commands in the gui Change-Id: Ia6a3fa03b50496abcab47026637a8f292c761d0c --- secop/client/baseclient.py | 8 +- secop/gui/modulectrl.py | 69 ++++++---- secop/gui/nodectrl.py | 21 +-- secop/gui/ui/cmddialog.ui | 88 +++++++++++++ secop/gui/valuewidgets.py | 264 +++++++++++++++++++++++++++++++++++++ 5 files changed, 414 insertions(+), 36 deletions(-) create mode 100644 secop/gui/ui/cmddialog.ui create mode 100644 secop/gui/valuewidgets.py diff --git a/secop/client/baseclient.py b/secop/client/baseclient.py index e36e66a..871fccd 100644 --- a/secop/client/baseclient.py +++ b/secop/client/baseclient.py @@ -386,6 +386,10 @@ class Client(object): datatype = get_datatype(parameterData['datatype']) self.describing_data['modules'][module]['parameters'] \ [parameter]['datatype'] = datatype + for cmdname, cmdData in moduleData[ + 'commands'].items(): + cmdData['arguments'] = map(get_datatype, cmdData['arguments']) + cmdData['resulttype'] = get_datatype(cmdData['resulttype']) except Exception as exc: print(formatException(verbose=True)) raise @@ -548,9 +552,9 @@ class Client(object): def getCommands(self, module): return self.describing_data['modules'][module]['commands'] - def execCommand(self, module, command, args=None): + def execCommand(self, module, command, args): # ignore reply message + reply specifier, only return data - return self._communicate('do', '%s:%s' % (module, command), self.encode_message(args) if args else None)[2] + return self._communicate('do', '%s:%s' % (module, command), list(args) if args else None)[2] def getProperties(self, module, parameter): return self.describing_data['modules'][module]['parameters'][parameter] diff --git a/secop/gui/modulectrl.py b/secop/gui/modulectrl.py index 45a4a9c..38527da 100644 --- a/secop/gui/modulectrl.py +++ b/secop/gui/modulectrl.py @@ -29,6 +29,46 @@ from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal from secop.gui.util import loadUi from secop.gui.params import ParameterView +from secop.datatypes import * + +from PyQt4.QtGui import QDialog, QPushButton, QLabel, QApplication, QLineEdit,\ + QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QCheckBox, QRadioButton, \ + QVBoxLayout, QGridLayout, QScrollArea, QFrame + +from secop.gui.valuewidgets import get_widget + + +class CommandDialog(QDialog): + def __init__(self, cmdname, arglist, parent=None): + super(CommandDialog, self).__init__(parent) + loadUi(self, 'cmddialog.ui') + + self.setWindowTitle('Arguments for %s' % cmdname) + row = 0 + + self._labels = [] + self.widgets = [] + for row, dtype in enumerate(arglist): + l = QLabel(repr(dtype)) + l.setWordWrap(True) + w = get_widget(dtype, readonly=False) + self.gridLayout.addWidget(l, row, 0) + self.gridLayout.addWidget(w, row, 1) + self._labels.append(l) + self.widgets.append(w) + + self.gridLayout.setRowStretch(len(arglist), 1) + self.setModal(True) + self.resize(self.sizeHint()) + + def get_value(self): + return [w.get_value() for w in self.widgets] + + def exec_(self): + if super(CommandDialog, self).exec_(): + return self.get_value() + + def showCommandResultDialog(command, args, result, extras=''): m = QMessageBox() @@ -69,7 +109,6 @@ class ParameterGroup(QWidget): self._row += 1 def on_toggle_clicked(self): - print("ParameterGroup.on_toggle_clicked") if self.paramGroupBox.isChecked(): for w in self._widgets: w.show() @@ -78,19 +117,6 @@ class ParameterGroup(QWidget): w.hide() -class CommandArgumentsDialog(QDialog): - - def __init__(self, commandname, argtypes, parent=None): - super(CommandArgumentsDialog, self).__init__(parent) - - # XXX: fill in apropriate widgets + OK/Cancel - - def exec_(self): - print('CommandArgumentsDialog result is', super( - CommandArgumentsDialog, self).exec_()) - return None # XXX: if there were arguments, return them after validation or None for 'Cancel' - - class CommandButton(QButton): def __init__(self, cmdname, cmdinfo, cb, parent=None): @@ -108,10 +134,10 @@ class CommandButton(QButton): def on_pushButton_pressed(self): self.setEnabled(False) - if self._argintypes or 1: - args = CommandArgumentsDialog(self._cmdname, self._argintypes) + if self._argintypes: + dlg = CommandDialog(self._cmdname, self._argintypes) + args = dlg.exec_() if args: # not 'Cancel' clicked - print('############# %s', args) self._cb(self._cmdname, args) else: # no need for arguments @@ -140,17 +166,10 @@ class ModuleCtrl(QWidget): self._node.newData.connect(self._updateValue) def _execCommand(self, command, args=None): - if args: # try to validate input - # XXX: check datatypes with their validators? - import ast - try: - args = ast.literal_eval(args) - except Exception as e: - return showErrorDialog(e) if not args: args = tuple() result, qualifiers = self._node.execCommand( - self._module, command, *args) + self._module, command, args) showCommandResultDialog(command, args, result, qualifiers) def _initModuleWidgets(self): diff --git a/secop/gui/nodectrl.py b/secop/gui/nodectrl.py index a21476b..1beb771 100644 --- a/secop/gui/nodectrl.py +++ b/secop/gui/nodectrl.py @@ -138,7 +138,18 @@ class NodeCtrl(QWidget): else: interfaces = modprops['interfaces'] description = modprops['description'] - unit = self._node.getProperties(modname, 'value').get('unit', '') + + # fallback: allow (now) invalid 'Driveable' + if 'Drivable' in interfaces or 'Driveable' in interfaces: + widget = DrivableWidget(self._node, modname, self) + unit = self._node.getProperties(modname, 'value').get('unit', '') + elif 'Readable' in interfaces: + widget = ReadableWidget(self._node, modname, self) + unit = self._node.getProperties(modname, 'value').get('unit', '') + else: + widget = QLabel('Unsupported Interfaceclass %r' % interfaces) + unit = '' + if unit: labelstr = '%s (%s):' % (modname, unit) @@ -147,14 +158,6 @@ class NodeCtrl(QWidget): label = QLabel(labelstr) label.setFont(labelfont) - # fallback: allow (now) invalid 'Driveable' - if 'Drivable' in interfaces or 'Driveable' in interfaces: - widget = DrivableWidget(self._node, modname, self) - elif 'Readable' in interfaces: - widget = ReadableWidget(self._node, modname, self) - else: - widget = QLabel('Unsupported Interfaceclasses %r' % interfaces) - if description: widget.setToolTip(description) diff --git a/secop/gui/ui/cmddialog.ui b/secop/gui/ui/cmddialog.ui new file mode 100644 index 0000000..f9488af --- /dev/null +++ b/secop/gui/ui/cmddialog.ui @@ -0,0 +1,88 @@ + + + Dialog + + + + 0 + 0 + 262 + 135 + + + + Dialog + + + + + + true + + + + + 0 + 0 + 242 + 76 + + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/secop/gui/valuewidgets.py b/secop/gui/valuewidgets.py new file mode 100644 index 0000000..cb6fc1b --- /dev/null +++ b/secop/gui/valuewidgets.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- +# ***************************************************************************** +# Copyright (c) 2015-2016 by the authors, see LICENSE +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program 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 General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Module authors: +# Enrico Faulhaber +# +# ***************************************************************************** + +from secop.datatypes import * + +from PyQt4.QtGui import QDialog, QPushButton, QLabel, QApplication, QLineEdit,\ + QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QCheckBox, QRadioButton, \ + QVBoxLayout, QGridLayout, QScrollArea, QFrame + +from secop.gui.util import loadUi + + +# XXX: implement live validators !!!! + +class StringWidget(QLineEdit): + def __init__(self, datatype, readonly=False, parent=None): + super(StringWidget, self).__init__(parent) + self.datatype = datatype + if readonly: + self.setEnabled(False) + + def get_value(self): + res = self.text() + return self.datatype.validate(res) + + def set_value(self, value): + self.setText(value) + + +class BlobWidget(StringWidget): + # XXX: make an editable hex-table ? + pass + +# or derive from widget and switch between combobox and radiobuttons? +class EnumWidget(QComboBox): + def __init__(self, datatype, readonly=False, parent=None): + super(EnumWidget, self).__init__(parent) + self.datatype = datatype + + self._map = {} + self._revmap = {} + for idx, (val, name) in enumerate(datatype.entries.items()): + self._map[idx] = (name, val) + self._revmap[name] = idx + self._revmap[val] = idx + self.addItem(name, val) + # XXX: fill Combobox from datatype + + def get_value(self): + # XXX: return integer corresponding to the selected item + return self._map[self.currentIndex()][1] + + def set_value(self, value): + self.setCurrentIndex(self._revmap[value]) + + +class BoolWidget(QCheckBox): + def __init__(self, datatype, readonly=False, parent=None): + super(BoolWidget, self).__init__(parent) + self.datatype = datatype + if readonly: + self.setEnabled(False) + + def get_value(self): + return self.isChecked() + + def set_value(self, value): + self.setChecked(bool(value)) + + +class IntWidget(QSpinBox): + def __init__(self, datatype, readonly=False, parent=None): + super(IntWidget, self).__init__(parent) + self.datatype = datatype + if readonly: + self.setEnabled(False) + self.setMaximum(datatype.max) + self.setMinimum(datatype.min) + + def get_value(self): + return int(self.value()) + + def set_value(self, value): + self.setValue(int(value)) + + +class FloatWidget(QDoubleSpinBox): + def __init__(self, datatype, readonly=False, parent=None): + super(FloatWidget, self).__init__(parent) + self.datatype = datatype + if readonly: + self.setEnabled(False) + self.setMaximum(datatype.max) + self.setMinimum(datatype.min) + self.setDecimals(12) + + def get_value(self): + return float(self.value()) + + def set_value(self, value): + self.setValue(float(value)) + + +class TupleWidget(QFrame): + def __init__(self, datatype, readonly=False, parent=None): + super(TupleWidget, self).__init__(parent) + + self.datatypes = datatype.subtypes + + self.layout = QVBoxLayout() + self.subwidgets = [] + for t in self.datatypes: + w = get_widget(t, readonly=readonly, parent=self) + w.show() + self.layout.addWidget(w) + self.subwidgets.append(w) + self.setLayout(self.layout) + self.show() + self.update() + + def get_value(self): + return [v.validate(w.get_value()) for w,v in zip(self.subwidgets, self.datatypes)] + + def set_value(self, value): + for w, v in zip(self.subwidgets, value): + w.set_value(value) + + +class StructWidget(QGroupBox): + def __init__(self, datatype, readonly=False, parent=None): + super(StructWidget, self).__init__(parent) + + self.layout = QGridLayout() + self.subwidgets = {} + self.datatypes = [] + self._labels = [] + for idx, name in enumerate(sorted(datatype.named_subtypes)): + dt = datatype.named_subtypes[name] + w = get_widget(dt, readonly=readonly, parent=self) + l = QLabel(name) + self.layout.addWidget(l, idx, 0) + self.layout.addWidget(w, idx, 1) + self._labels.append(l) + self.subwidgets[name] = (w, dt) + self.datatypes.append(dt) + self.setLayout(self.layout) + + def get_value(self): + res = {} + for name, entry in self.subwidgets.items(): + w, dt = entry + res[name] = dt.validate(w.get_value()) + return res + + def set_value(self, value): + for k, v in value.items(): + entry = self.subwidgets[k] + w, dt = entry + w.set_value(dt.validate(v)) + + +class ArrayWidget(QGroupBox): + def __init__(self, datatype, readonly=False, parent=None): + super(ArrayWidget, self).__init__(parent) + self.datatype = datatype.subtype + + self.layout = QVBoxLayout() + self.subwidgets = [] + for i in range(datatype.maxsize): + w = get_widget(self.datatype, readonly=readonly, parent=self) + self.layout.addWidget(w) + self.subwidgets.append(w) + self.setLayout(self.layout) + + def get_value(self): + return [self.datatype.validate(w.get_value()) for w in self.subwidgets] + + def set_value(self, values): + for w, v in zip(self.subwidgets, values): + w.set_value(v) + + + +def get_widget(datatype, readonly=False, parent=None): + return {FloatRange: FloatWidget, + IntRange: IntWidget, + StringType: StringWidget, + BLOBType: BlobWidget, + EnumType: EnumWidget, + BoolType: BoolWidget, + TupleOf: TupleWidget, + StructOf: StructWidget, + ArrayOf: ArrayWidget, + }.get(datatype.__class__)(datatype, readonly, parent) + + +class msg(QDialog): + def __init__(self, stuff, parent=None): + super(msg, self).__init__(parent) + loadUi(self, 'cmddialog.ui') + print(dir(self)) + self.setWindowTitle('Please enter the arguments for calling command "blubb()"') + row = 0 + # self.gridLayout.addWidget(QLabel('String'), row, 0); self.gridLayout.addWidget(StringWidget(StringType()), row, 1); row += 1 + # self.gridLayout.addWidget(QLabel('Blob'), row, 0); self.gridLayout.addWidget(BlobWidget(BLOBType()), row, 1); row += 1 + # self.gridLayout.addWidget(QLabel('Enum'), row, 0); self.gridLayout.addWidget(EnumWidget(EnumType(a=1,b=9)), row, 1); row += 1 + # self.gridLayout.addWidget(QLabel('Bool'), row, 0); self.gridLayout.addWidget(BoolWidget(BoolType()), row, 1); row += 1 + # self.gridLayout.addWidget(QLabel('int'), row, 0); self.gridLayout.addWidget(IntWidget(IntRange(0,9)), row, 1); row += 1 + # self.gridLayout.addWidget(QLabel('float'), row, 0); self.gridLayout.addWidget(FloatWidget(FloatRange(-9,9)), row, 1); row += 1 + + #self.gridLayout.addWidget(QLabel('tuple'), row, 0); + #dt = TupleOf(BoolType(), EnumType(a=2,b=3)) + #w = TupleWidget(dt) + #self.gridLayout.addWidget(w, row, 1) + #row+=1 + + #self.gridLayout.addWidget(QLabel('array'), row, 0); + #dt = ArrayOf(IntRange(0,10), 10) + #w = ArrayWidget(dt) + #self.gridLayout.addWidget(w, row, 1) + #row+=1 + + self.gridLayout.addWidget(QLabel('struct'), row, 0); + dt = StructOf(i=IntRange(0,10), f=FloatRange(), b=BoolType()) + w = StructWidget(dt) + self.gridLayout.addWidget(w, row, 1) + row+=1 + + self.gridLayout.addWidget(QLabel('stuff'), row, 0, 1, 0); row += 1 # at pos (0,0) span 2 cols, 1 row + self.gridLayout.setRowStretch(row, 1) + self.setModal(True) + + def accept(self): + print('accepted') + super(msg, self).accept() + + def reject(self): + print('rejected') + super(msg, self).reject() + + def done(self, how): + print('done(%r)' % how) + return super(msg, self).done(how) +