Merge "provide an mean to use commands in the gui"

This commit is contained in:
Enrico Faulhaber 2017-09-13 17:10:37 +02:00 committed by Gerrit Code Review
commit 0aa542f07c
5 changed files with 414 additions and 36 deletions

View File

@ -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]

View File

@ -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):

View File

@ -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)

88
secop/gui/ui/cmddialog.ui Normal file
View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>262</width>
<height>135</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>242</width>
<height>76</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout"/>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

264
secop/gui/valuewidgets.py Normal file
View File

@ -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 <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
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)