Rework GUI.

Work-in-progress state of the new gui.

Change-Id: Ib5e9ad2178b372fbd2914077096a9c73f025ecb7
This commit is contained in:
Alexander Zaft 2023-01-24 11:13:26 +01:00 committed by Markus Zolliker
parent 72f9f242d7
commit 9f54d89efa
37 changed files with 4967 additions and 1403 deletions

View File

@ -69,6 +69,7 @@ def main(argv=None):
app = QApplication(argv) app = QApplication(argv)
win = MainWindow(args.node, logger) win = MainWindow(args.node, logger)
app.aboutToQuit.connect(win._onQuit)
win.show() win.show()
return app.exec_() return app.exec_()

50
frappy/gui/collapsible.py Normal file
View File

@ -0,0 +1,50 @@
from frappy.gui.qt import QToolButton, QFrame, QWidget, QGridLayout, QSizePolicy, QVBoxLayout, Qt
class CollapsibleWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.button = QToolButton()
self.widget = QWidget()
self.widgetContainer = QWidget()
self.button.setArrowType(Qt.RightArrow)
self.button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.button.setStyleSheet("QToolButton { border: none; }")
self.button.setCheckable(True)
self.button.toggled.connect(self._collapse)
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
l = QVBoxLayout()
l.addWidget(self.widget)
self.widgetContainer.setLayout(l)
self.widgetContainer.setMaximumHeight(0)
layout = QGridLayout()
layout.addWidget(self.button, 0, 0, Qt.AlignLeft)
layout.addWidget(line, 0, 1, 1, 1)
layout.addWidget(self.widgetContainer, 1, 0, -1, -1)
self.setLayout(layout)
def _collapse(self, expand):
if expand:
self.button.setArrowType(Qt.DownArrow)
self.widgetContainer.setMaximumHeight(self.widget.maximumHeight())
else:
self.button.setArrowType(Qt.RightArrow)
self.widgetContainer.setMaximumHeight(0)
self.setMaximumHeight(self.button.maximumHeight())
def replaceWidget(self, widget):
layout = self.widgetContainer.layout()
layout.removeWidget(self.widget)
self.widget = widget
layout.addWidget(self.widget)
def setTitle(self, title):
self.button.setText(title)
def getWidget(self):
return self.widget

45
frappy/gui/logwindow.py Normal file
View File

@ -0,0 +1,45 @@
from logging import Handler, DEBUG, NOTSET
from frappy.gui.qt import QMainWindow, QObject, pyqtSignal
from frappy.gui.util import loadUi
class LogWindowHandler(Handler, QObject):
logmessage = pyqtSignal(str, int)
def __init__(self, level=NOTSET):
QObject.__init__(self)
Handler.__init__(self, level)
def emit(self, record):
self.logmessage.emit(record.getMessage(), record.levelno)
class LogWindow(QMainWindow):
levels = {'Debug':10, 'Info':20, 'Warning':30, 'Error':40}
def __init__(self, logger, parent=None):
super().__init__(parent)
loadUi(self, 'logwindow.ui')
self.log = []
self.level = self.levels['Info']
handler = LogWindowHandler(DEBUG)
handler.logmessage.connect(self.newEntry)
logger.addHandler(handler)
def newEntry(self, msg, lvl):
self.log.append((lvl, msg))
if lvl >= self.level:
self.logBrowser.append(msg)
def on_logLevel_currentTextChanged(self, level):
self.level = self.levels[level]
self.logBrowser.clear()
self.logBrowser.setPlainText('\n'.join(msg for (lvl, msg) in self.log if lvl >= self.level))
def on_clear_pressed(self):
self.logBrowser.clear()
self.log.clear()
def onClose(self):
pass

View File

@ -23,13 +23,13 @@
import frappy.client import frappy.client
from frappy.gui.modulectrl import ModuleCtrl from frappy.gui.qt import QInputDialog, QMainWindow, QMessageBox, QObject, \
from frappy.gui.nodectrl import NodeCtrl QTreeWidgetItem, pyqtSignal, pyqtSlot
from frappy.gui.paramview import ParameterView from frappy.gui.util import Value, Colors, loadUi
from frappy.gui.qt import QBrush, QColor, QInputDialog, QMainWindow, \
QMessageBox, QObject, QTreeWidgetItem, pyqtSignal, pyqtSlot
from frappy.gui.util import Value, loadUi
from frappy.lib import formatExtendedTraceback from frappy.lib import formatExtendedTraceback
from frappy.gui.logwindow import LogWindow
from frappy.gui.tabwidget import TearOffTabWidget
from frappy.gui.nodewidget import NodeWidget
ITEM_TYPE_NODE = QTreeWidgetItem.UserType + 1 ITEM_TYPE_NODE = QTreeWidgetItem.UserType + 1
ITEM_TYPE_GROUP = QTreeWidgetItem.UserType + 2 ITEM_TYPE_GROUP = QTreeWidgetItem.UserType + 2
@ -102,6 +102,7 @@ class QSECNode(QObject):
return frappy.client.decode_msg(msg.encode('utf-8')) return frappy.client.decode_msg(msg.encode('utf-8'))
def _getDescribingParameterData(self, module, parameter): def _getDescribingParameterData(self, module, parameter):
#print(module, parameter, self.modules[module]['parameters'])
return self.modules[module]['parameters'][parameter] return self.modules[module]['parameters'][parameter]
def updateEvent(self, module, parameter, value, timestamp, readerror): def updateEvent(self, module, parameter, value, timestamp, readerror):
@ -118,22 +119,23 @@ class MainWindow(QMainWindow):
def __init__(self, hosts, logger, parent=None): def __init__(self, hosts, logger, parent=None):
super().__init__(parent) super().__init__(parent)
loadUi(self, 'mainwindow.ui') self.log = logger
self.logwin = LogWindow(logger, self)
self.logwin.hide()
loadUi(self, 'mainwin.ui')
Colors._setPalette(self.palette())
self.toolBar.hide() self.toolBar.hide()
self.splitter.setStretchFactor(0, 1) # what is which?
self.splitter.setStretchFactor(1, 70) self.tab = TearOffTabWidget(self, self, self, self)
self.splitter.setSizes([50, 500]) self.tab.setTabsClosable(True)
self.tab.tabCloseRequested.connect(self._handleTabClose)
self.setCentralWidget(self.tab)
self._nodes = {} self._nodes = {}
self._nodeCtrls = {} self._nodeWidgets = {}
self._moduleCtrls = {}
self._paramCtrls = {}
self._topItems = {}
self._currentWidget = self.splitter.widget(1).layout().takeAt(0)
self.log = logger
# add localhost (if available) and SEC nodes given as arguments # add localhost (if available) and SEC nodes given as arguments
for host in hosts: for host in hosts:
@ -159,87 +161,49 @@ class MainWindow(QMainWindow):
QMessageBox.critical(self.parent(), QMessageBox.critical(self.parent(),
'Connecting to %s failed!' % host, str(e)) 'Connecting to %s failed!' % host, str(e))
def on_validateCheckBox_toggled(self, state): def on_actionDetailed_View_toggled(self, toggled):
print("validateCheckBox_toggled", state) self._rebuildAdvanced(toggled)
def on_visibilityComboBox_activated(self, level): def on_actionShow_Logs_toggled(self, active):
if level in ['user', 'admin', 'expert']: self.logwin.setHidden(not active)
print("visibility Level now:", level)
def on_treeWidget_currentItemChanged(self, current, previous): # def on_validateCheckBox_toggled(self, state):
if current.type() == ITEM_TYPE_NODE: # print('validateCheckBox_toggled', state)
self._displayNode(current.text(0))
elif current.type() == ITEM_TYPE_GROUP:
self._displayGroup(current.parent().text(0), current.text(0))
elif current.type() == ITEM_TYPE_MODULE:
self._displayModule(current.parent().text(0), current.text(0))
elif current.type() == ITEM_TYPE_PARAMETER:
self._displayParameter(current.parent().parent().text(0),
current.parent().text(0), current.text(0))
def _removeSubTree(self, toplevel_item): # def on_visibilityComboBox_activated(self, level):
self.treeWidget.invisibleRootItem().removeChild(toplevel_item) # if level in ['user', 'admin', 'expert']:
# print('visibility Level now:', level)
def _set_node_state(self, nodename, online, state):
node = self._nodes[nodename]
if online:
self._topItems[node].setBackground(0, QBrush(QColor('white')))
else:
self._topItems[node].setBackground(0, QBrush(QColor('orange')))
# TODO: make connection state be a separate row
node.contactPoint = '%s (%s)' % (node.conn.uri, state)
if nodename in self._nodeCtrls:
self._nodeCtrls[nodename].contactPointLabel.setText(node.contactPoint)
def _addNode(self, host): def _addNode(self, host):
# create client # create client
node = QSECNode(host, self.log, parent=self) node = QSECNode(host, self.log, parent=self)
nodename = node.nodename nodename = node.nodename
self._nodes[nodename] = node self._nodes[nodename] = node
# fill tree nodeWidget = NodeWidget(node, self)
nodeItem = QTreeWidgetItem(None, [nodename], ITEM_TYPE_NODE) self.tab.addTab(nodeWidget, node.equipmentId)
self._nodeWidgets[nodename] = nodeWidget
self.tab.setCurrentWidget(nodeWidget)
return nodename
for module in sorted(node.modules): def _handleTabClose(self, index):
moduleItem = QTreeWidgetItem(nodeItem, [module], ITEM_TYPE_MODULE) node = self.tab.widget(index).getSecNode()
for param in sorted(node.getParameters(module)): # disconnect node from all events
paramItem = QTreeWidgetItem(moduleItem, [param], self._nodes.pop(node.nodename)
ITEM_TYPE_PARAMETER) self.tab.removeTab(index)
paramItem.setDisabled(False)
self.treeWidget.addTopLevelItem(nodeItem) def _rebuildAdvanced(self, advanced):
self._topItems[node] = nodeItem if advanced:
node.stateChange.connect(self._set_node_state) pass
else:
pass
for widget in self._nodeWidgets.values():
widget._rebuildAdvanced(advanced)
def _displayNode(self, node): def _onQuit(self):
ctrl = self._nodeCtrls.get(node, None) for node in self._nodes.values():
if ctrl is None: # this is only qt signals deconnecting!
ctrl = self._nodeCtrls[node] = NodeCtrl(self._nodes[node]) # TODO: terminate node.conn explicitly?
self._nodes[node].unhandledMsg.connect(ctrl._addLogEntry) node.disconnect()
self.logwin.onClose()
self._replaceCtrlWidget(ctrl)
def _displayModule(self, node, module):
ctrl = self._moduleCtrls.get((node, module), None)
if ctrl is None:
ctrl = self._moduleCtrls[(node, module)] = ModuleCtrl(self._nodes[node], module)
self._replaceCtrlWidget(ctrl)
def _displayParameter(self, node, module, parameter):
ctrl = self._paramCtrls.get((node, module, parameter), None)
if ctrl is None:
ctrl = ParameterView(self._nodes[node], module, parameter)
self._paramCtrls[(node, module, parameter)] = ctrl
self._replaceCtrlWidget(ctrl)
def _replaceCtrlWidget(self, new):
old = self.splitter.widget(1).layout().takeAt(0)
if old:
old.widget().hide()
self.splitter.widget(1).layout().addWidget(new)
new.show()
self._currentWidget = new

View File

@ -1,429 +0,0 @@
# -*- 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 os import path
from frappy.gui.qt import QBrush, QColor, QPainter, QPen, \
QPointF, QPolygonF, QRectF, QSize, Qt, QWidget
_magenta = QBrush(QColor('#A12F86'))
_yellow = QBrush(QColor('yellow'))
_white = QBrush(QColor('white'))
_lightgrey = QBrush(QColor('lightgrey'))
_grey = QBrush(QColor('grey'))
_darkgrey = QBrush(QColor('#404040'))
_black = QBrush(QColor('black'))
_blue = QBrush(QColor('blue'))
_green = QBrush(QColor('green'))
_red = QBrush(QColor('red'))
_olive = QBrush(QColor('olive'))
_orange = QBrush(QColor('#ffa500'))
my_uipath = path.dirname(__file__)
class MiniPlotCurve:
# placeholder for data
linecolor = _black
linewidth = 0 # set to 0 to disable lines
symbolcolors = (_black, _white) # line, fill
symbolsize = 3 # both symbol linewidth and symbolsize, set to 0 to disable
errorbarcolor = _darkgrey
errorbarwidth = 3 # set to 0 to disable errorbar
def __init__(self):
self.data = [] # tripels of x, y, err (err may be None)
@property
def xvalues(self):
return [p[0] for p in self.data] if self.data else [0]
@property
def yvalues(self):
return [p[1] for p in self.data] if self.data else [0]
@property
def errvalues(self):
return [p[2] or 0.0 for p in self.data] if self.data else [0]
@property
def xmin(self):
return min(self.xvalues)
@property
def xmax(self):
return max(self.xvalues)
@property
def ymin(self):
return min(self.yvalues)
@property
def ymax(self):
return max(self.yvalues)
@property
def yemin(self):
return min(y-(e or 0) for _, y, e in self.data) if self.data else 0
@property
def yemax(self):
return max(y+(e or 0) for _, y, e in self.data) if self.data else 0
def paint(self, scale, painter):
# note: scale returns a screen-XY tuple for data XY
# draw errorbars, lines and symbols in that order
if self.errorbarwidth > 0:
pen = QPen()
pen.setBrush(self.errorbarcolor)
pen.setWidth(self.errorbarwidth)
painter.setPen(pen)
for _x,_y,_e in self.data:
if _e is None:
continue
x, y = scale(_x,_y)
e = scale(_x,_y + _e)[1] - y
painter.drawLine(x, y-e, x, y+e)
painter.fillRect(x - self.errorbarwidth / 2., y - e,
self.errorbarwidth, 2 * e, self.errorbarcolor)
points = [QPointF(*scale(p[0], p[1])) for p in self.data]
if self.linewidth > 0:
pen = QPen()
pen.setBrush(self.linecolor)
pen.setWidth(self.linewidth)
painter.setPen(pen)
painter.drawPolyline(QPolygonF(points))
if self.symbolsize > 0:
pen = QPen()
pen.setBrush(self.symbolcolors[0]) # linecolor
pen.setWidth(self.symbolsize) # linewidth
painter.setPen(pen)
painter.setBrush(self.symbolcolors[1]) # fill color
if self.symbolsize > 0:
for p in points:
painter.drawEllipse(p, 2*self.symbolsize, 2*self.symbolsize)
def preparepainting(self, scale, xmin, xmax):
pass # nothing to do
class MiniPlotFitCurve(MiniPlotCurve):
# do not influence scaling of plotting window
@property
def xmin(self):
return float('inf')
@property
def xmax(self):
return float('-inf')
@property
def ymin(self):
return float('inf')
@property
def ymax(self):
return float('-inf')
@property
def yemin(self):
return float('inf')
@property
def yemax(self):
return float('-inf')
def __init__(self, formula, params):
super().__init__()
self.formula = formula
self.params = params
linecolor = _blue
linewidth = 5 # set to 0 to disable lines
symbolsize = 0 # both symbol linewidth and symbolsize, set to 0 to disable
errorbarwidth = 0 # set to 0 to disable errorbar
def preparepainting(self, scale, xmin, xmax):
# recalculate data
points = int(scale(xmax) - scale(xmin))
self.data = []
for idx in range(points+1):
x = xmin + idx * (xmax-xmin) / points
y = self.formula(x, *self.params)
self.data.append((x,y,None))
class MiniPlot(QWidget):
ticklinecolors = (_grey, _lightgrey) # ticks, subticks
ticklinewidth = 1
bordercolor = _black
borderwidth = 1
labelcolor = _black
xlabel = 'x'
ylabel = 'y'
xfmt = '%.1f'
yfmt = '%g'
autotickx = True
autoticky = True
def __init__(self, parent=None):
super().__init__(parent)
self.xmin = self.xmax = None
self.ymin = self.ymax = None
self.curves = []
self.plotx = 0 # left of this are labels
self.ploty = self.height() # below this are labels
def scaleX(self, x):
if not self.curves:
return x # XXX: !!!!
x = self.plotx + (self.width() - self.plotx) * (x - self.xmin) / (self.xmax - self.xmin)
# x = max(min(x, self.width()), self.plotx)
return x
def scaleY(self, y):
if not self.curves:
return y # XXX: !!!!
y = self.ploty * (self.ymax - y) / (self.ymax - self.ymin)
# y = max(min(y, self.ploty), 0)
return y
def scale(self, x, y):
# scales a plotting xx/y to a screen x/y to be used for painting...
return self.scaleX(x), self.scaleY(y)
def removeCurve(self, curve):
if curve in self.curves:
self.curves.remove(curve)
self.updatePlot()
def addCurve(self, curve):
if curve is not None and curve not in self.curves:
# new curve, recalculate all
self.curves.append(curve)
self.updatePlot()
def updatePlot(self):
xmin,xmax = -1,1
ymin,ymax = -1,1
# find limits of known curves
if self.curves:
xmin = min(c.xmin for c in self.curves)
xmax = max(c.xmax for c in self.curves)
ymin = min(c.yemin for c in self.curves)
ymax = max(c.yemax for c in self.curves)
# fallback values for no curve
while xmin >= xmax:
xmin, xmax = xmin - 1, xmax + 1
while ymin >= ymax:
ymin, ymax = ymin - 1, ymax + 1
# adjust limits a little
self.xmin = xmin - 0.05 * (xmax - xmin)
self.xmax = xmax + 0.05 * (xmax - xmin)
self.ymin = ymin - 0.05 * (ymax - ymin)
self.ymax = ymax + 0.05 * (ymax - ymin)
# (re-)generate x/yticks
if self.autotickx:
self.calc_xticks(xmin, xmax)
if self. autoticky:
self.calc_yticks(ymin, ymax)
# redraw
self.update()
def calc_xticks(self, xmin, xmax):
self.xticks = self.calc_ticks(xmin, xmax, self.xfmt)
def calc_yticks(self, ymin, ymax):
self.yticks = self.calc_ticks(ymin, ymax, self.yfmt)
def calc_ticks(self, _min, _max, fmt):
min_intervals = 2
diff = _max - _min
if diff <= 0:
return [0]
# find a 'good' step size
step = abs(diff / min_intervals)
# split into mantissa and exp.
expo = 0
while step >= 10:
step /= 10.
expo += 1
while step < 1:
step *= 10.
expo -= 1
# make step 'latch' into smalle bigger magic number
subs = 1
for n, subs in reversed([(1,5.), (1.5,3.), (2,4.), (3,3.), (5,5.), (10,2.)]):
if step >= n:
step = n
break
# convert back to normal number
while expo > 0:
step *= 10.
expo -= 1
while expo < 0:
step /= 10.
expo += 1
substep = step / subs
# round lower
rounded_min = step * int(_min / step)
# generate ticks list
ticks = []
x = rounded_min
while x + substep < _min:
x += substep
for _ in range(100):
if x < _max + substep:
break
# check if x is a tick or a subtick
x = substep * int(x / substep)
if abs(x - step * int(x / step)) <= substep / 2:
# tick
ticks.append((x, fmt % x))
else:
# subtick
ticks.append((x, ''))
x += substep
return ticks
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# obtain a few properties we need for proper drawing
painter.setFont(self.font())
fm = painter.fontMetrics()
label_height = fm.height()
self.plotx = 3 + 2 * label_height
self.ploty = self.height() - 3 - 2 * label_height
# fill bg of plotting area
painter.fillRect(self.plotx ,0,self.width()-self.plotx, self.ploty,_white)
# paint ticklines
if self.curves and self.ticklinewidth > 0:
for e in self.xticks:
try:
_x = e[0] # pylint: disable=unsubscriptable-object
_l = e[1] # pylint: disable=unsubscriptable-object
except TypeError:
_x = e
_l = self.xfmt % _x
x = self.scaleX(_x)
pen = QPen()
pen.setBrush(self.ticklinecolors[0 if _l else 1])
pen.setWidth(self.ticklinewidth)
painter.setPen(pen)
painter.drawLine(x, 0, x, self.ploty)
for e in self.yticks:
try:
_y = e[0] # pylint: disable=unsubscriptable-object
_l = e[1] # pylint: disable=unsubscriptable-object
except TypeError:
_y = e
_l = self.xfmt % _x
y = self.scaleY(_y)
pen = QPen()
pen.setBrush(self.ticklinecolors[0 if _l else 1])
pen.setWidth(self.ticklinewidth)
painter.setPen(pen)
painter.drawLine(self.plotx, y, self.width(), y)
# paint curves
painter.setClipRect(QRectF(self.plotx, 0, self.width()-self.plotx, self.ploty))
for c in self.curves:
c.preparepainting(self.scaleX, self.xmin, self.xmax)
c.paint(self.scale, painter)
painter.setClipping(False)
# paint frame
pen = QPen()
pen.setBrush(self.bordercolor)
pen.setWidth(self.borderwidth)
painter.setPen(pen)
painter.drawPolyline(QPolygonF([
QPointF(self.plotx, 0),
QPointF(self.width()-1, 0),
QPointF(self.width()-1, self.ploty),
QPointF(self.plotx, self.ploty),
QPointF(self.plotx, 0),
]))
# draw labels
painter.setBrush(self.labelcolor)
h2 = (self.height()-self.ploty)/2.
# XXX: offset axis labels from axis a little
painter.drawText(self.plotx, self.ploty + h2,
self.width() - self.plotx, h2,
Qt.AlignCenter | Qt.AlignVCenter, self.xlabel)
# rotate ylabel?
painter.resetTransform()
painter.translate(0, self.ploty / 2.)
painter.rotate(-90)
w = fm.width(self.ylabel)
painter.drawText(-w, -fm.height() / 2., w * 2, self.plotx,
Qt.AlignCenter | Qt.AlignTop, self.ylabel)
painter.resetTransform()
if self.curves:
for e in self.xticks:
try:
_x = e[0] # pylint: disable=unsubscriptable-object
l = e[1] # pylint: disable=unsubscriptable-object
except TypeError:
_x = e
l = self.xfmt % _x
x = self.scaleX(_x)
w = fm.width(l)
painter.drawText(x - w, self.ploty + 2, 2 * w, h2,
Qt.AlignCenter | Qt.AlignVCenter, l)
for e in self.yticks:
try:
_y = e[0] # pylint: disable=unsubscriptable-object
l = e[1] # pylint: disable=unsubscriptable-object
except TypeError:
_y = e
l = self.yfmt % _y
y = self.scaleY(_y)
w = fm.width(l)
painter.resetTransform()
painter.translate(self.plotx - fm.height(), y + w)
painter.rotate(-90)
painter.drawText(0, -1,
2 * w, fm.height(),
Qt.AlignCenter | Qt.AlignBottom, l)
painter.resetTransform()
def sizeHint(self):
return QSize(320, 240)

View File

@ -183,6 +183,8 @@ class ModuleCtrl(QWidget):
cmdWidgets[command] = w cmdWidgets[command] = w
self.commandGroupBox.layout().addWidget(w, 0, row) self.commandGroupBox.layout().addWidget(w, 0, row)
row += 1 row += 1
if not commands:
self.commandGroupBox.hide()
row = 0 row = 0
# collect grouping information # collect grouping information

View File

@ -0,0 +1,176 @@
import frappy.gui.resources # pylint: disable=unused-import
from frappy.gui.qt import QTreeWidget, QTreeWidgetItem, pyqtSignal, QIcon, Qt
class ParamItem(QTreeWidgetItem):
def __init__(self, node, module, param):
super().__init__()
self.module = module
self.param = param
self.setText(0, param)
class ModuleItem(QTreeWidgetItem):
display = {'status': 0, 'value': 1, 'target': 2, 'status/text': 3}
def __init__(self, node, module):
super().__init__()
ModuleItem._loadicons()
self.module = module
self.param = None
#self._expanded = False
#self._params = {}
parameters = node.getParameters(module)
self._hasTarget = 'target' in parameters
#if self._hasTarget:
# self.setFlags(self.flags() | Qt.ItemIsEditable)
if 'value' in parameters:
props = node.getProperties(self.module, 'value')
self._unit = props.get('unit', '')
else:
self.setIcon(self.display['status'], ModuleItem.icons['clear'])
self.setText(0, self.module)
# initial read
cache = node.queryCache(self.module)
for param, val in cache.items():
self.valueChanged(param, val)
self.params = []
for param in parameters:
self.params.append(ParamItem(node, module, param))
@classmethod
def _loadicons(cls):
if hasattr(cls, 'icons'):
return
cls.icons = {
'disabled': QIcon(':/leds/gray'),
'idle': QIcon(':/leds/green'),
'warn':QIcon(':/leds/orange'),
'error': QIcon(':/leds/red'),
'busy': QIcon(':/leds/yellow'),
'unknown': QIcon(':/leds/unknown'),
'clear': QIcon(':/leds/clear'),
}
@classmethod
def statusIcon(cls, statuscode):
if statuscode == 0:
return ModuleItem.icons['disabled']
if statuscode in range(100, 200):
return ModuleItem.icons['idle']
if statuscode in range(200, 300):
return ModuleItem.icons['warn']
if statuscode in range(300, 400):
return ModuleItem.icons['busy']
if statuscode in range(400, 500):
return ModuleItem.icons['error']
return ModuleItem.icons['clear']
def valueChanged(self, parameter, value):
if parameter not in self.display:
return
if parameter == 'status':
self.setIcon(self.display[parameter], ModuleItem.statusIcon(value.value[0].value))
self.setText(self.display['status/text'], value.value[1])
else:
self.setText(self.display[parameter], '%s%s' % (str(value), self._unit))
def disconnected(self):
self.setIcon(self.display['status'], ModuleItem.icons['unknown'])
def setClearIcon(self):
self.setIcon(self.display['status'], ModuleItem.icons['clear'])
def hasTarget(self):
return self._hasTarget
def _rebuildAdvanced(self, advanced):
if advanced:
self.addChildren(self.params)
else:
for p in self.params:
self.removeChild(p)
class ModuleOverview(QTreeWidget):
itemChanged = pyqtSignal(str, str, str, str)
def __init__(self, node, parent=None):
super().__init__(parent)
self._node = node
self._modules = {}
self.last_was_clear = False
#self.setHeaderHidden(True)
#self.setChildIndicatorPolicy(QTreeWidgetItem.DontShowIndicator)
self.setRootIsDecorated(False)
self.setExpandsOnDoubleClick(False)
self.setColumnCount(4)
header = self.headerItem()
header.setText(0, 'Name')
header.setText(1, 'Value')
header.setText(2, 'Target')
header.setText(3, 'Status')
for module in sorted(self._node.modules):
mod = ModuleItem(self._node, module)
self._modules[module] = mod
self.addTopLevelItem(mod)
self._resizeColumns()
self.itemExpanded.connect(self._resizeColumns)
self.itemCollapsed.connect(self._resizeColumns)
self.setContextMenuPolicy(Qt.CustomContextMenu)
# self.customContextMenuRequested.connect(self._contextMenu)
self._node.newData.connect(self._updateValue)
self.currentItemChanged.connect(self.handleCurrentItemChanged)
#self.itemDoubleClicked.connect(self.handleDoubleClick)
# def handleDoubleClick(self, item, column):
# if item.hasTarget() and column == 2:
# self.editItem(item, column)
def handleCurrentItemChanged(self, current, previous):
if previous is None or self.last_was_clear:
pmod = ''
pparam = ''
self.last_was_clear = False
else:
pmod = previous.module
pparam = previous.param or ''
cparam = current.param or ''
self.itemChanged.emit(current.module, cparam, pmod, pparam)
def setToDisconnected(self):
for module in self._modules.values():
module.disconnected()
def setToReconnected(self):
for mname, module in self._modules.items():
cache = self._node.queryCache(mname)
if not 'status' in cache:
module.setClearIcon()
continue
module.valueChanged('status', cache['status'])
def _updateValue(self, module, parameter, value):
self._modules[module].valueChanged(parameter, value)
def _rebuildAdvanced(self, advanced):
self.setRootIsDecorated(advanced)
for module in self._modules.values():
module._rebuildAdvanced(advanced)
self._resizeColumns()
def _resizeColumns(self):
for i in range(self.columnCount()):
self.resizeColumnToContents(i)
def clearTreeSelection(self):
prev = self.selectedItems()[0]
pmod, pparam = prev.module, prev.param
self.clearSelection()
self.itemChanged.emit('', '', pmod, pparam)
self.last_was_clear = True

255
frappy/gui/modulewidget.py Normal file
View File

@ -0,0 +1,255 @@
from frappy.gui.qt import QLabel, QMessageBox, QWidget, QLineEdit, \
QPushButton, QIcon, pyqtSignal, QToolButton, QDialog
from frappy.gui.util import loadUi
from frappy.gui.valuewidgets import get_widget
import frappy.gui.resources # pylint: disable=unused-import
class CommandDialog(QDialog):
def __init__(self, cmdname, argument, parent=None):
super().__init__(parent)
loadUi(self, 'cmddialog.ui')
self.setWindowTitle('Arguments for %s' % cmdname)
# row = 0
self._labels = []
self.widgets = []
# improve! recursive?
dtype = argument
label = QLabel(repr(dtype))
label.setWordWrap(True)
widget = get_widget(dtype, readonly=False)
self.gridLayout.addWidget(label, 0, 0)
self.gridLayout.addWidget(widget, 0, 1)
self._labels.append(label)
self.widgets.append(widget)
self.gridLayout.setRowStretch(1, 1)
self.setModal(True)
self.resize(self.sizeHint())
def get_value(self):
return True, self.widgets[0].get_value()
def exec_(self):
if super().exec_():
return self.get_value()
return None
def showCommandResultDialog(command, args, result, extras=''):
m = QMessageBox()
args = '' if args is None else repr(args)
m.setText('calling: %s(%s)\nyielded: %r\nqualifiers: %s' %
(command, args, result, extras))
m.exec_()
def showErrorDialog(command, args, error):
m = QMessageBox()
args = '' if args is None else repr(args)
m.setText('calling: %s(%s)\nraised %r' % (command, args, error))
m.exec_()
class CommandButton(QPushButton):
def __init__(self, cmdname, cmdinfo, cb, parent=None):
super().__init__(parent)
self._cmdname = cmdname
self._argintype = cmdinfo['datatype'].argument # single datatype
self.result = cmdinfo['datatype'].result
self._cb = cb # callback function for exection
self.setText(cmdname)
if cmdinfo['description']:
self.setToolTip(cmdinfo['description'])
self.pressed.connect(self.on_pushButton_pressed)
def on_pushButton_pressed(self):
#self.setEnabled(False)
if self._argintype:
dlg = CommandDialog(self._cmdname, self._argintype)
args = dlg.exec_()
if args: # not 'Cancel' clicked
self._cb(self._cmdname, args[1])
else:
# no need for arguments
self._cb(self._cmdname, None)
#self.setEnabled(True)
class ModuleWidget(QWidget):
plot = pyqtSignal(str)
plotAdd = pyqtSignal(str)
def __init__(self, node, name, parent=None):
super().__init__()
loadUi(self, 'modulewidget.ui')
self._node = node
self._name = name
self._paramDisplays = {}
self._paramInputs = {}
self._addbtns = []
self._paramWidgets = {}
self.moduleName.setText(name)
props = self._node.getModuleProperties(self._name)
description = props.get('description', '')
self.moduleDescription.setText(description)
row = 0
params = dict(self._node.getParameters(self._name))
if 'status' in params:
params.pop('status')
self._addRParam('status', row)
row += 1
if 'value' in params:
params.pop('value')
self._addRParam('value', row)
row += 1
if 'target' in params:
params.pop('target')
self._addRWParam('target', row)
row += 1
for param in params:
paramProps = self._node.getProperties(self._name, param)
if paramProps['readonly']:
self._addRParam(param, row)
else:
self._addRWParam(param, row)
row += 1
self._setParamHidden(param, True)
self._addCommands(row)
cache = self._node.queryCache(self._name)
for param, val in cache.items():
self._updateValue(self._name, param, val)
node.newData.connect(self._updateValue)
def _updateValue(self, mod, param, val):
if mod != self._name:
return
if param in self._paramDisplays:
self._paramDisplays[param].setText(str(val.value))
def _addRParam(self, param, row):
props = self._node.getProperties(self._name, param)
nameLabel = QLabel(param)
unitLabel = QLabel(props.get('unit', ''))
display = QLineEdit()
self._paramDisplays[param] = display
self._paramWidgets[param] = [nameLabel, unitLabel, display]
l = self.moduleDisplay.layout()
l.addWidget(nameLabel, row,0,1,1)
l.addWidget(display, row,1,1,5)
l.addWidget(unitLabel, row,6)
self._addPlotButtons(param, row)
def _addRWParam(self, param, row):
props = self._node.getProperties(self._name, param)
nameLabel = QLabel(param)
unitLabel = QLabel(props.get('unit', ''))
unitLabel2 = QLabel(props.get('unit', ''))
display = QLineEdit()
inputEdit = QLineEdit()
submitButton = QPushButton('Go')
submitButton.setIcon(QIcon(':/icons/submit'))
submitButton.pressed.connect(lambda: self._button_pressed(param))
inputEdit.returnPressed.connect(lambda: self._button_pressed(param))
self._paramDisplays[param] = display
self._paramInputs[param] = inputEdit
self._paramWidgets[param] = [nameLabel, unitLabel, unitLabel2,
display, inputEdit, submitButton]
l = self.moduleDisplay.layout()
l.addWidget(nameLabel, row,0,1,1)
l.addWidget(display, row,1,1,2)
l.addWidget(unitLabel, row,3,1,1)
l.addWidget(inputEdit, row,4,1,2)
l.addWidget(unitLabel2, row,6,1,1)
l.addWidget(submitButton, row, 7)
self._addPlotButtons(param, row)
def _addPlotButtons(self, param, row):
if param == 'status':
return
plotButton = QToolButton()
plotButton.setIcon(QIcon(':/icons/plot'))
plotButton.setToolTip('Plot %s' % param)
plotAddButton = QToolButton()
plotAddButton.setIcon(QIcon(':/icons/plot-add'))
plotAddButton.setToolTip('Plot With...')
plotButton.clicked.connect(lambda: self.plot.emit(param))
plotAddButton.clicked.connect(lambda: self.plotAdd.emit(param))
self._addbtns.append(plotAddButton)
plotAddButton.setDisabled(True)
self._paramWidgets[param].append(plotButton)
self._paramWidgets[param].append(plotAddButton)
l = self.moduleDisplay.layout()
l.addWidget(plotButton, row, 8)
l.addWidget(plotAddButton, row, 9)
def _addCommands(self, startrow):
cmdicons = {
'stop': QIcon(':/icons/stop'),
}
cmds = self._node.getCommands(self._name)
if not cmds:
return
l = self.moduleDisplay.layout()
# max cols in GridLayout, find out programmatically?
maxcols = 7
l.addWidget(QLabel('Commands:'))
for (i, cmd) in enumerate(cmds):
cmdb = CommandButton(cmd, cmds[cmd], self._execCommand)
if cmd in cmdicons:
cmdb.setIcon(cmdicons[cmd])
row = startrow + i // maxcols
col = (i % maxcols) + 1
l.addWidget(cmdb, row, col)
def _execCommand(self, command, args=None):
try:
result, qualifiers = self._node.execCommand(
self._name, command, args)
except Exception as e:
showErrorDialog(command, args, e)
return
if result is not None:
showCommandResultDialog(command, args, result, qualifiers)
def _setParamHidden(self, param, hidden):
for w in self._paramWidgets[param]:
w.setHidden(hidden)
def _setGroupHidden(self, group):
pass
def rebuildAdvanced(self, advanced):
for param in self._paramWidgets:
if param in ['value', 'status', 'target']:
continue
self._setParamHidden(param, not advanced)
def _button_pressed(self, param):
target = self._paramInputs[param].text()
try:
self._node.setParameter(self._name, param, target)
except Exception as e:
QMessageBox.warning(self.parent(), 'Operation failed', str(e))
def plotsPresent(self, present):
for btn in self._addbtns:
btn.setDisabled(present)

View File

@ -1,331 +0,0 @@
# -*- 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:
# Alexander Lenz <alexander.lenz@frm2.tum.de>
#
# *****************************************************************************
import json
import pprint
from time import sleep
import mlzlog
import frappy.lib
from frappy.datatypes import EnumType, StringType
from frappy.errors import SECoPError
from frappy.gui.qt import QFont, QFontMetrics, QLabel, \
QMessageBox, QTextCursor, QWidget, pyqtSlot, toHtmlEscaped
from frappy.gui.util import Value, loadUi
class NodeCtrl(QWidget):
def __init__(self, node, parent=None):
super().__init__(parent)
loadUi(self, 'nodectrl.ui')
self._node = node
self.contactPointLabel.setText(self._node.contactPoint)
self.equipmentIdLabel.setText(self._node.equipmentId)
self.protocolVersionLabel.setText(self._node.protocolVersion)
self.nodeDescriptionLabel.setText(self._node.properties.get('description',
'no description available'))
self._clearLog()
# now populate modules tab
self._init_modules_tab()
node.logEntry.connect(self._addLogEntry)
@pyqtSlot()
def on_sendPushButton_clicked(self):
msg = self.msgLineEdit.text().strip()
if not msg:
return
self._addLogEntry(
'<span style="font-weight:bold">Request:</span> '
'%s:' % msg,
raw=True)
# msg = msg.split(' ', 2)
try:
reply = self._node.syncCommunicate(*self._node.decode_message(msg))
if msg == 'describe':
_, eid, stuff = self._node.decode_message(reply)
reply = '%s %s %s' % (_, eid, json.dumps(
stuff, indent=2, separators=(',', ':'), sort_keys=True))
self._addLogEntry(reply, newline=True, pretty=False)
else:
self._addLogEntry(reply, newline=True, pretty=False)
except SECoPError as e:
einfo = e.args[0] if len(e.args) == 1 else json.dumps(e.args)
self._addLogEntry(
'%s: %s' % (e.name, einfo),
newline=True,
pretty=False,
error=True)
except Exception as e:
self._addLogEntry(
'error when sending %r: %r' % (msg, e),
newline=True,
pretty=False,
error=True)
@pyqtSlot()
def on_clearPushButton_clicked(self):
self._clearLog()
def _clearLog(self):
self.logTextBrowser.clear()
self._addLogEntry('SECoP Communication Shell')
self._addLogEntry('=========================')
self._addLogEntry('', newline=True)
def _addLogEntry(self,
msg,
newline=False,
pretty=False,
raw=False,
error=False):
if pretty:
msg = pprint.pformat(msg, width=self._getLogWidth())
msg = msg[1:-1]
if not raw:
if error:
msg = '<div style="color:#FF0000"><b><pre>%s</pre></b></div>' % toHtmlEscaped(
str(msg)).replace('\n', '<br />')
else:
msg = '<pre>%s</pre>' % toHtmlEscaped(
str(msg)).replace('\n', '<br />')
content = ''
if self.logTextBrowser.toPlainText():
content = self.logTextBrowser.toHtml()
content += msg
if newline:
content += '<br />'
self.logTextBrowser.setHtml(content)
self.logTextBrowser.moveCursor(QTextCursor.End)
def _getLogWidth(self):
fontMetrics = QFontMetrics(QFont('Monospace'))
# calculate max avail characters by using an m (which is possible
# due to monospace)
result = self.logTextBrowser.width() / fontMetrics.width('m')
return result
def _init_modules_tab(self):
self._moduleWidgets = []
layout = self.scrollAreaWidgetContents.layout()
labelfont = self.font()
labelfont.setBold(True)
row = 0
for modname in sorted(self._node.modules):
modprops = self._node.getModuleProperties(modname)
interfaces = modprops.get('interface_classes', '')
description = modprops.get('description', '!!! missing description !!!')
# fallback: allow (now) invalid 'Driveable'
unit = ''
try:
if 'Drivable' in interfaces or 'Driveable' in interfaces:
widget = DrivableWidget(self._node, modname, self)
unit = self._node.getProperties(modname, 'value').get('unit', '')
elif 'Writable' in interfaces or 'Writeable' in interfaces:
# XXX !!!
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)
except Exception as e:
print(frappy.lib.formatExtendedTraceback())
widget = QLabel('Bad configured Module %s! (%s)' % (modname, e))
if unit:
labelstr = '%s (%s):' % (modname, unit)
else:
labelstr = '%s:' % (modname,)
label = QLabel(labelstr)
label.setFont(labelfont)
if description:
widget.setToolTip(description)
layout.addWidget(label, row, 0)
layout.addWidget(widget, row, 1)
row += 1
self._moduleWidgets.extend((label, widget))
layout.setRowStretch(row, 1)
class ReadableWidget(QWidget):
def __init__(self, node, module, parent=None):
super().__init__(parent)
self._node = node
self._module = module
# XXX: avoid a nasty race condition, mainly biting on M$
for i in range(15):
if 'status' in self._node.modules[module]['parameters']:
break
sleep(0.01*i)
self._status_type = self._node.getProperties(
self._module, 'status').get('datatype')
try:
props = self._node.getProperties(self._module, 'target')
datatype = props.get('datatype', StringType())
self._is_enum = isinstance(datatype, EnumType)
except KeyError:
self._is_enum = False
loadUi(self, 'modulebuttons.ui')
# populate comboBox, keeping a mapping of Qt-index to EnumValue
if self._is_enum:
self._map = {} # maps QT-idx to name/value
self._revmap = {} # maps value/name to QT-idx
for idx, member in enumerate(datatype._enum.members):
self._map[idx] = member
self._revmap[member.name] = idx
self._revmap[member.value] = idx
self.targetComboBox.addItem(member.name, member.value)
self._init_status_widgets()
self._init_current_widgets()
self._init_target_widgets()
self._node.newData.connect(self._updateValue)
def _get(self, pname, fallback=Ellipsis):
try:
return Value(*self._node.getParameter(self._module, pname))
except Exception as e:
# happens only, if there is no response form read request
mlzlog.getLogger('cached values').warn(
'no cached value for %s:%s %r' % (self._module, pname, e))
return Value(fallback)
def _init_status_widgets(self):
self.update_status(self._get('status', (400, '<not supported>')))
# XXX: also connect update_status signal to LineEdit ??
def update_status(self, status):
self.statusLineEdit.setText(str(status))
# may change meaning of cmdPushButton
def _init_current_widgets(self):
self.update_current(self._get('value', ''))
def update_current(self, value):
self.currentLineEdit.setText(str(value))
def _init_target_widgets(self):
# Readable has no target: disable widgets
self.targetLineEdit.setHidden(True)
self.targetComboBox.setHidden(True)
self.cmdPushButton.setHidden(True)
def update_target(self, target):
pass
def _updateValue(self, module, parameter, value):
if module != self._module:
return
if parameter == 'status':
self.update_status(value)
elif parameter == 'value':
self.update_current(value)
elif parameter == 'target':
self.update_target(value)
class DrivableWidget(ReadableWidget):
def _init_target_widgets(self):
if self._is_enum:
# EnumType: disable Linedit
self.targetLineEdit.setHidden(True)
self.cmdPushButton.setHidden(True)
else:
# normal types: disable Combobox
self.targetComboBox.setHidden(True)
target = self._get('target', None)
if target.value is not None:
if isinstance(target.value, list) and isinstance(target.value[1], dict):
self.update_target(Value(target.value[0]))
else:
self.update_target(target)
def update_current(self, value):
self.currentLineEdit.setText(str(value))
# elif self._is_enum:
# member = self._map[self._revmap[value.value]]
# self.currentLineEdit.setText('%s.%s (%d)' % (member.enum.name, member.name, member.value))
def update_target(self, target):
if self._is_enum:
if target.readerror:
return
# update selected item
value = target.value
if value in self._revmap:
self.targetComboBox.setCurrentIndex(self._revmap[value])
else:
print(
"%s: Got invalid target value %r!" %
(self._module, value))
else:
self.targetLineEdit.setText(str(target))
def target_go(self, target):
try:
self._node.setParameter(self._module, 'target', target)
except Exception as e:
self._node.log.exception(e)
QMessageBox.warning(self.parent(), 'Operation failed', str(e))
@pyqtSlot()
def on_cmdPushButton_clicked(self):
if self._is_enum:
self.on_targetComboBox_activated(self.targetComboBox.currentText())
else:
self.on_targetLineEdit_returnPressed()
@pyqtSlot()
def on_targetLineEdit_returnPressed(self):
self.target_go(self.targetLineEdit.text())
@pyqtSlot(str)
def on_targetComboBox_activated(self, selection):
self.target_go(selection)

308
frappy/gui/nodewidget.py Normal file
View File

@ -0,0 +1,308 @@
from collections import OrderedDict
import json
import pprint
import frappy.gui.resources # pylint: disable=unused-import
from frappy.gui.qt import QFont, QFontMetrics, QLabel, QTextCursor, QWidget, \
pyqtSlot, toHtmlEscaped, QVBoxLayout, QGridLayout, QPlainTextEdit, \
QMenu, QCursor, QIcon, QInputDialog, pyqtSignal
from frappy.gui.util import Colors, loadUi
from frappy.gui.plotting import getPlotWidget
from frappy.gui.modulectrl import ModuleCtrl
from frappy.gui.paramview import ParameterView
from frappy.gui.modulewidget import ModuleWidget
from frappy.gui.moduleoverview import ModuleOverview
from frappy.errors import SECoPError
class Console(QWidget):
def __init__(self, node, parent=None):
super().__init__(parent)
loadUi(self, 'console.ui')
self._node = node
self._clearLog()
@pyqtSlot()
def on_sendPushButton_clicked(self):
msg = self.msgLineEdit.text().strip()
if not msg:
return
self._addLogEntry(
'<span style="font-weight:bold">Request:</span> '
'%s:' % msg,
raw=True)
# msg = msg.split(' ', 2)
try:
reply = self._node.syncCommunicate(*self._node.decode_message(msg))
if msg == 'describe':
_, eid, stuff = self._node.decode_message(reply)
reply = '%s %s %s' % (_, eid, json.dumps(
stuff, indent=2, separators=(',', ':'), sort_keys=True))
self._addLogEntry(reply, newline=True, pretty=False)
else:
self._addLogEntry(reply, newline=True, pretty=False)
except SECoPError as e:
einfo = e.args[0] if len(e.args) == 1 else json.dumps(e.args)
self._addLogEntry(
'%s: %s' % (e.name, einfo),
newline=True,
pretty=False,
error=True)
except Exception as e:
self._addLogEntry(
'error when sending %r: %r' % (msg, e),
newline=True,
pretty=False,
error=True)
@pyqtSlot()
def on_clearPushButton_clicked(self):
self._clearLog()
def _clearLog(self):
self.logTextBrowser.clear()
self._addLogEntry('SECoP Communication Shell')
self._addLogEntry('=========================')
self._addLogEntry('', newline=True)
def _addLogEntry(self,
msg,
newline=False,
pretty=False,
raw=False,
error=False):
if pretty:
msg = pprint.pformat(msg, width=self._getLogWidth())
msg = msg[1:-1]
if not raw:
if error:
msg = '<div style="color:#FF0000"><b><pre>%s</pre></b></div>' % toHtmlEscaped(
str(msg)).replace('\n', '<br />')
else:
msg = '<pre>%s</pre>' % toHtmlEscaped(
str(msg)).replace('\n', '<br />')
content = ''
if self.logTextBrowser.toPlainText():
content = self.logTextBrowser.toHtml()
content += msg
if newline:
content += '<br />'
self.logTextBrowser.setHtml(content)
self.logTextBrowser.moveCursor(QTextCursor.End)
def _getLogWidth(self):
fontMetrics = QFontMetrics(QFont('Monospace'))
# calculate max avail characters by using an m (which is possible
# due to monospace)
result = self.logTextBrowser.width() / fontMetrics.width('m')
return result
class NodeWidget(QWidget):
noPlots = pyqtSignal(bool)
def __init__(self, node, parent=None):
super().__init__(parent)
loadUi(self, 'nodewidget.ui')
self._node = node
self._node.stateChange.connect(self._set_node_state)
self._modules = OrderedDict()
self._detailedModules = {}
self._detailedParams = {}
self._activePlots = {}
self.top_splitter.setStretchFactor(0, 2)
self.top_splitter.setStretchFactor(1, 10)
self.top_splitter.setSizes([180, 500])
self.middle_splitter.setCollapsible(self.middle_splitter.indexOf(self.view), False)
self.middle_splitter.setStretchFactor(0, 20)
self.middle_splitter.setStretchFactor(1, 1)
self.infobox_splitter.setStretchFactor(0,3)
self.infobox_splitter.setStretchFactor(1,2)
self.consoleWidget.setTitle('Console')
cmd = Console(node, self.consoleWidget)
self.consoleWidget.replaceWidget(cmd)
viewLayout = self.viewContent.layout()
for module in node.modules:
widget = ModuleWidget(node, module, self.view)
widget.plot.connect(lambda param, module=module:
self.plotParam(module, param))
widget.plotAdd.connect(lambda param, module=module:
self._plotPopUp(module, param))
self.noPlots.connect(widget.plotsPresent)
self._modules[module] = widget
details = ModuleCtrl(node, module)
self._detailedModules[module] = details
viewLayout.addWidget(details)
details.setHidden(True)
self._detailedParams[module] = {}
for param in node.getParameters(module):
view = ParameterView(node, module, param)
self._detailedParams[module][param] = view
view.setHidden(True)
viewLayout.addWidget(view)
viewLayout.addWidget(widget)
self._initNodeInfo()
def _initNodeInfo(self):
self.tree = ModuleOverview(self._node)
infolayout = QVBoxLayout()
infolayout.addWidget(self.tree)
self.infotree.setLayout(infolayout)
# disabled until i find a way to deselect and go back to overview
self.tree.itemChanged.connect(self.changeViewContent)
self.tree.customContextMenuRequested.connect(self._treeContextMenu)
self._description = QPlainTextEdit(self._node.properties.get('description','no description available'))
self._description_label = QLabel('Description:')
self._description.setReadOnly(True)
self._host = QLabel(self._node.conn.uri)
self._host.hide()
self._host_label = QLabel('Host:')
self._host_label.hide()
self._protocoll = QLabel(self._node.conn.secop_version)
self._protocoll.setWordWrap(True)
self._protocoll.hide()
self._protocoll_label = QLabel('Protocoll Version:')
self._protocoll_label.hide()
layout = QGridLayout()
layout.addWidget(self._host_label, 0, 0)
layout.addWidget(self._host, 0, 1)
layout.addWidget(self._protocoll_label, 1, 0)
layout.addWidget(self._protocoll, 1, 1)
layout.addWidget(self._description_label, 2, 0)
layout.addWidget(self._description, 3, 0, -1, -1)
self.nodeinfo.setLayout(layout)
def _set_node_state(self, nodename, online, state):
p = self.palette()
if online:
p.setColor(self.backgroundRole(),Colors.palette.window().color())
self.tree.setToReconnected()
else:
p.setColor(self.backgroundRole(), Colors.colors['orange'])
# TODO: reset this for non-status modules!
self.tree.setToDisconnected()
self.setPalette(p)
def _rebuildAdvanced(self, advanced):
self._host.setHidden(not advanced)
self._host_label.setHidden(not advanced)
self._protocoll.setHidden(not advanced)
self._protocoll_label.setHidden(not advanced)
self.tree._rebuildAdvanced(advanced)
for m in self._modules.values():
m.rebuildAdvanced(advanced)
def getSecNode(self):
return self._node
def changeViewContent(self, module, param, prevmod, prevparam):
if prevmod == '':
for mod in self._modules.values():
mod.setHidden(True)
elif prevparam == '':
self._detailedModules[prevmod].setHidden(True)
else:
self._detailedParams[prevmod][prevparam].setHidden(True)
if module == '':
# no module -> reset to overview
for mod in self._modules.values():
mod.setHidden(False)
elif param == '':
# set to single module view
self._detailedModules[module].setHidden(False)
else:
# set to single param view
self._detailedParams[module][param].setHidden(False)
def _treeContextMenu(self, pos):
index = self.tree.indexAt(pos)
if not index.isValid():
return
item = self.tree.itemFromIndex(index)
menu = QMenu()
opt_plot = menu.addAction('Plot')
opt_plot.setIcon(QIcon(':/icons/plot'))
menu_plot_ext = menu.addMenu("Plot with...")
menu_plot_ext.setIcon(QIcon(':/icons/plot'))
if not self._activePlots:
menu_plot_ext.setEnabled(False)
else:
for (m,p),plot in self._activePlots.items():
opt_ext = menu_plot_ext.addAction("%s:%s" % (m,p))
opt_ext.triggered.connect(
lambda plot=plot: self._requestPlot(item, plot))
menu.addSeparator()
opt_clear = menu.addAction('Clear Selection')
opt_plot.triggered.connect(lambda: self._requestPlot(item))
opt_clear.triggered.connect(self.tree.clearTreeSelection)
#menu.exec_(self.mapToGlobal(pos))
menu.exec_(QCursor.pos())
def _requestPlot(self, item, plot=None):
module = item.module
param = item.param or 'value'
self.plotParam(module, param, plot)
def _plotPopUp(self, module, param):
plots = {'%s -> %s' % (m,p): (m,p) for (m,p) in self._activePlots}
dialog = QInputDialog()
#dialog.setInputMode()
dialog.setOption(QInputDialog.UseListViewForComboBoxItems)
dialog.setComboBoxItems(plots.keys())
dialog.setTextValue(list(plots)[0])
dialog.setWindowTitle('Plot %s with...' % param)
dialog.setLabelText('')
if dialog.exec_() == QInputDialog.Accepted:
item = dialog.textValue()
self.plotParam(module, param, self._activePlots[plots[item]])
def plotParam(self, module, param, plot=None):
# - liveness?
# - better plot window management?
# only allow one open plot per parameter TODO: change?
if (module, param) in self._activePlots:
return
if plot:
plot.addCurve(self._node, module, param)
plot.setCurveColor(module, param, Colors.colors[len(plot.curves) % 6])
else:
plot = getPlotWidget()
plot.addCurve(self._node, module, param)
plot.setCurveColor(module, param, Colors.colors[1])
self._activePlots[(module, param)] = plot
plot.closed.connect(lambda: self._removePlot(module, param))
plot.show()
self.noPlots.emit(len(self._activePlots) == 0)
# initial datapoint
cache = self._node.queryCache(module)
if param in cache:
plot.update(module, param, cache[param])
def _removePlot(self, module, param):
self._activePlots.pop((module,param))
self.noPlots.emit(len(self._activePlots) == 0)

114
frappy/gui/plotting.py Normal file
View File

@ -0,0 +1,114 @@
import time
try:
import pyqtgraph as pg
import numpy as np
except ImportError:
pg = None
np = None
from frappy.gui.util import Colors
from frappy.gui.qt import QWidget, QVBoxLayout, QLabel, Qt, pyqtSignal
def getPlotWidget():
if pg:
pg.setConfigOption('background', Colors.colors['plot-bg'])
pg.setConfigOption('foreground', Colors.colors['plot-fg'])
if pg is None:
return PlotPlaceHolderWidget()
return PlotWidget()
class PlotPlaceHolderWidget(QWidget):
closed = pyqtSignal(object)
def __init__(self, parent=None):
super().__init__(parent)
l = QVBoxLayout()
label = QLabel("pyqtgraph is not installed!")
label.setAlignment(Qt.AlignCenter)
l.addWidget(label)
self.setLayout(l)
self.setMinimumWidth(300)
self.setMinimumHeight(150)
self.curves = {}
def addCurve(self, node, module, param):
pass
def setCurveColor(self, module, param, color):
pass
def update(self, module, param, value):
pass
def closeEvent(self, event):
self.closed.emit(self)
event.accept()
# TODO:
# - timer-based updates when receiving no updates from the node
# in order to make slower updates not jump that much?
# - remove curves again
class PlotWidget(QWidget):
closed = pyqtSignal(object)
def __init__(self, parent=None):
super().__init__(parent)
self.win = pg.GraphicsLayoutWidget()
self.curves = {}
self.data = {}
self.timer = pg.QtCore.QTimer()
self.timer.timeout.connect(self.scrollUpdate)
# TODO: Shoud this scrolling be done? or with configuration?
self.timer.start(100)
self.plot = self.win.addPlot()
self.plot.addLegend()
self.plot.setAxisItems({'bottom': pg.DateAxisItem()})
self.plot.setLabel('bottom', 'Time')
self.plot.setLabel('left', 'Value')
l = QVBoxLayout()
l.addWidget(self.win)
self.setLayout(l)
def addCurve(self, node, module, param):
paramData = node._getDescribingParameterData(module, param)
name = '%s:%s' % (module, param)
unit = paramData.get('unit', '')
if unit:
unit = '/' + unit
curve = self.plot.plot(name='%s%s' % (name, unit))
if 'min' in paramData and 'max' in paramData:
curve.setXRange(paramData['min'], paramData['max'])
curve.setDownsampling(method='peak')
self.data[name] = (np.array([]),np.array([]))
self.curves[name] = curve
node.newData.connect(self.update)
def setCurveColor(self, module, param, color):
curve = self.curves['%s:%s' % (module, param)]
curve.setPen(color)
def scrollUpdate(self):
for cname, curve in self.curves.items():
x,y = self.data[cname]
x = np.append(x, time.time())
y = np.append(y, y[-1])
curve.setData(x,y)
def update(self, module, param, value):
name = '%s:%s' % (module, param)
if name not in self.curves:
return
curve = self.curves[name]
x,y = self.data[name]
x = np.append(x, value.timestamp)
y = np.append(y, value.value)
self.data[name] = (x,y)
curve.setData(x,y)
def closeEvent(self, event):
self.closed.emit(self)
event.accept()

View File

@ -32,15 +32,17 @@ try:
from PyQt5 import uic from PyQt5 import uic
from PyQt5.QtCore import Qt, QObject, pyqtSignal, pyqtSlot, QSize, QPointF, \ from PyQt5.QtCore import Qt, QObject, pyqtSignal, pyqtSlot, QSize, QPointF, \
QRectF, QPoint QRectF, QPoint, QByteArray, QEvent, QMimeData
from PyQt5.QtGui import QFont, QTextCursor, QFontMetrics, QColor, QBrush, \ from PyQt5.QtGui import QFont, QTextCursor, QFontMetrics, QColor, QBrush, \
QPainter, QPolygonF, QPen, QIcon, QStandardItemModel, QStandardItem QPainter, QPolygonF, QPen, QIcon, QStandardItemModel, QStandardItem, \
QPalette, QCursor, QDrag, QMouseEvent, QPixmap
from PyQt5.QtWidgets import QLabel, QWidget, QDialog, QLineEdit, QCheckBox, \ from PyQt5.QtWidgets import QLabel, QWidget, QDialog, QLineEdit, QCheckBox, \
QPushButton, QSizePolicy, QMainWindow, QMessageBox, QInputDialog, \ QPushButton, QSizePolicy, QMainWindow, QMessageBox, QInputDialog, \
QTreeWidgetItem, QApplication, QGroupBox, QSpinBox, QDoubleSpinBox, \ QTreeWidgetItem, QApplication, QGroupBox, QSpinBox, QDoubleSpinBox, \
QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, QGridLayout, \ QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, QGridLayout, \
QScrollArea, QFrame, QTreeWidget, QFileDialog, QTabBar, QAction, QMenu,\ QScrollArea, QFrame, QTreeWidget, QFileDialog, QTabBar, QAction, QMenu,\
QDialogButtonBox, QTextEdit, QAbstractItemView, QSpacerItem, QTreeView QDialogButtonBox, QTextEdit, QSpacerItem, QTreeView, QStyle, \
QStyleOptionTab, QStylePainter, QTabWidget, QToolButton, QPlainTextEdit
from xml.sax.saxutils import escape as toHtmlEscaped from xml.sax.saxutils import escape as toHtmlEscaped
@ -55,7 +57,7 @@ except ImportError:
QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, \ QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, \
QGridLayout, QScrollArea, QFrame, QColor, QBrush, QPainter, QPolygonF, QPen, QIcon, \ QGridLayout, QScrollArea, QFrame, QColor, QBrush, QPainter, QPolygonF, QPen, QIcon, \
QTreeWidget, QFileDialog, QTabBar, QAction, QMenu, QDialogButtonBox, QAbstractItemView, \ QTreeWidget, QFileDialog, QTabBar, QAction, QMenu, QDialogButtonBox, QAbstractItemView, \
QSpacerItem, QTreeView, QStandardItemModel, QStandardItem QSpacerItem, QTreeView, QStandardItemModel, QStandardItem, QPlainTextEdit
import frappy.gui.cfg_editor.icon_rc_qt4 import frappy.gui.cfg_editor.icon_rc_qt4

2773
frappy/gui/resources.py Normal file

File diff suppressed because it is too large Load Diff

525
frappy/gui/tabwidget.py Normal file
View File

@ -0,0 +1,525 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
# NICOS, the Networked Instrument Control System of the MLZ
# Copyright (c) 2009-2023 by the NICOS contributors (see AUTHORS)
#
# 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:
# Jens Krüger <jens.krueger@frm2.tum.de>
#
# *****************************************************************************
"""Detachable TabWidget, taken from NICOS GUI TearOffTabBar."""
from frappy.gui.qt import QApplication, QCursor, QDrag, \
QEvent, QMainWindow, QMimeData, QMouseEvent, QPixmap, QPoint, QSize, \
QStyle, QStyleOptionTab, QStylePainter, Qt, QTabBar, QTabWidget, QWidget, \
pyqtSignal, pyqtSlot
# def findTab(tab, w):
# widget = w
# while True:
# parent = widget.parent()
# if not parent:
# return False
# widget = parent
# if isinstance(widget, AuxiliarySubWindow) and tab == widget:
# return True
# return False
#
#
# def findTabIndex(tabwidget, w):
# for i in range(len(tabwidget)):
# if findTab(tabwidget.widget(i), w):
# return i
# return None
class TearOffTabBar(QTabBar):
tabDetached = pyqtSignal(object, object)
tabMoved = pyqtSignal(object, object)
def __init__(self, parent=None):
QTabBar.__init__(self, parent)
self.setAcceptDrops(True)
self.setElideMode(Qt.ElideRight)
self.setSelectionBehaviorOnRemove(QTabBar.SelectLeftTab)
self.setMovable(False)
self._dragInitiated = False
self._dragDroppedPos = QPoint()
self._dragStartPos = QPoint()
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._dragStartPos = event.pos()
self._dragInitiated = False
self._dragDroppedPos = QPoint()
QTabBar.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if not event.buttons() & Qt.LeftButton:
return
if not self._dragStartPos.isNull() and \
self.tabAt(self._dragStartPos) != -1 and \
(event.pos() - self._dragStartPos).manhattanLength() \
< QApplication.startDragDistance():
self._dragInitiated = True
if (event.buttons() == Qt.LeftButton) and self._dragInitiated and \
not self.geometry().contains(event.pos()):
finishMoveEvent = QMouseEvent(QEvent.MouseMove, event.pos(),
Qt.NoButton, Qt.NoButton,
Qt.NoModifier)
QTabBar.mouseMoveEvent(self, finishMoveEvent)
drag = QDrag(self)
mimedata = QMimeData()
mimedata.setData('action', b'application/tab-detach')
drag.setMimeData(mimedata)
pixmap = self.parentWidget().currentWidget().grab()
pixmap = pixmap.scaled(640, 480, Qt.KeepAspectRatio)
drag.setPixmap(pixmap)
drag.setDragCursor(QPixmap(), Qt.LinkAction)
dragged = drag.exec(Qt.MoveAction)
if dragged == Qt.IgnoreAction:
# moved outside of tab widget
event.accept()
self.tabDetached.emit(self.tabAt(self._dragStartPos),
QCursor.pos())
elif dragged == Qt.MoveAction:
# moved inside of tab widget
if not self._dragDroppedPos.isNull():
event.accept()
self.tabMoved.emit(self.tabAt(self._dragStartPos),
self.tabAt(self._dragDroppedPos))
self._dragDroppedPos = QPoint()
else:
QTabBar.mouseMoveEvent(self, event)
def dragEnterEvent(self, event):
mimedata = event.mimeData()
formats = mimedata.formats()
if 'action' in formats and \
mimedata.data('action') == 'application/tab-detach':
event.acceptProposedAction()
QTabBar.dragEnterEvent(self, event)
def dropEvent(self, event):
self._dragDroppedPos = event.pos()
event.acceptProposedAction()
QTabBar.dropEvent(self, event)
class LeftTabBar(TearOffTabBar):
def __init__(self, parent, text_padding):
TearOffTabBar.__init__(self, parent)
self.text_padding = text_padding
def paintEvent(self, event):
painter = QStylePainter(self)
option = QStyleOptionTab()
for index in range(self.count()):
self.initStyleOption(option, index)
tabRect = self.tabRect(index)
tabRect.moveLeft(10)
painter.drawControl(QStyle.CE_TabBarTabShape, option)
text = self.tabText(index)
painter.drawText(tabRect, Qt.AlignVCenter | Qt.TextDontClip |
Qt.TextShowMnemonic, text)
def tabSizeHint(self, index):
fm = self.fontMetrics()
tabSize = fm.boundingRect(
self.tabText(index) or 'Ag').size() + QSize(*self.text_padding)
return tabSize
class TearOffTabWidget(QTabWidget):
"""Tab widget with detachable tabs.
Detached tabs will reattach when closed.
Options:
* ``position`` (default top) -- sets the position of the tab selector.
Choices are top or left.
* ``margins`` (default (0, 6, 0, 0)) -- sets the margin around the tab item.
* ``textpadding`` (default (20, 10)) -- sets the right padding and vertical
padding for the text in the tab label.
"""
class TabWidgetStorage:
def __init__(self, index, widget, title, visible=True):
self.index = index
self.widget = widget
self.title = title
self.visible = visible
self.detached = None
def setDetached(self, detached):
self.detached = detached
def __repr__(self):
return 'index %d, widget %r, title %s, visible %r, detached %r' % (
self.index, self.widget, self.title, self.visible,
self.detached)
def __init__(self, item, window, menuwindow, parent=None):
QTabWidget.__init__(self, parent)
self.menuwindow = menuwindow
# if item.options.get('position', 'top') == 'left':
# tabBar = LeftTabBar(self, item.options.get('textpadding', (20, 10)))
# self.setTabBar(tabBar)
# self.setTabPosition(QTabWidget.West)
# else:
# tabBar = TearOffTabBar(self)
# self.setTabBar(tabBar)
tabBar = TearOffTabBar(self)
self.setTabBar(tabBar)
self.setMovable(False)
self.previousTabIdx = 0
tabBar.tabDetached.connect(self.detachTab)
tabBar.tabMoved.connect(self.moveTab)
self.currentChanged.connect(self.tabChangedTab)
self.tabIdx = {}
# don't draw a frame around the tab contents
self.setStyleSheet('QTabWidget:tab:disabled{width:0;height:0;'
'margin:0;padding:0;border:none}')
self.setDocumentMode(True)
# default: only keep margin at the top (below the tabs)
# margins = item.options.get('margins', (0, 6, 0, 0))
# for entry in item.children:
# self.addPanel(
# AuxiliarySubWindow(entry[1:], window, menuwindow, self,
# margins), entry[0])
def moveTab(self, from_ind, to_ind):
w = self.widget(from_ind)
text = self.tabText(from_ind)
self.removeTab(from_ind)
self.insertTab(to_ind, w, text)
self.setCurrentIndex(to_ind)
def _findFirstWindow(self, w):
widget = w
while True:
parent = widget.parent()
if not parent:
break
widget = parent
if isinstance(widget, QMainWindow):
break
return widget
def _tabWidgetIndex(self, widget):
for i in range(self.tabBar().count()):
if self.widget(i) == widget:
return i
return -1
def tabInserted(self, index):
w = self.widget(index)
for i in self.tabIdx.values():
if i.widget == w:
return
self.tabIdx[index] = self.TabWidgetStorage(index, self.widget(index),
self.tabText(index))
def _setPanelToolbars(self, panel, visible):
for tb in panel.getToolbars():
tb.setVisible(visible)
def _setPanelMenus(self, panel, visible):
for m in panel.getMenus():
m.menuAction().setVisible(visible)
# @pyqtSlot(QWidget, bool)
# def setWidgetVisibleSlot(self, widget, visible):
# w = self._findFirstWindow(widget) # get widget which is related to tab
# for i in self.tabIdx.values(): # search for it in the list of tabs
# if i.widget == w: # found
# if isinstance(widget, Panel):
# if not visible or (visible and self.currentWidget() ==
# widget):
# self._setPanelToolbars(widget, visible)
# self._setPanelMenus(widget, visible)
# if visible:
# if not i.visible:
# newIndex = -1
# for j in self.tabIdx.values():
# if j.visible and j.index > i.index:
# cIdx = self._tabWidgetIndex(j.widget)
# if cIdx < i.index and cIdx != -1:
# newIndex = cIdx
# else:
# newIndex = i.index
# break
# self.insertTab(newIndex, i.widget, i.title)
# else:
# i.visible = False
# index = self._tabWidgetIndex(i.widget)
# self.removeTab(index)
def _getPanel(self, widget):
panel = widget
if isinstance(widget, QMainWindow): # check for main window type
panel = widget.centralWidget()
if panel and panel.layout(): # check for layout
panel = panel.layout().itemAt(0).widget()
return panel
def detachTab(self, index, point):
detachWindow = DetachedWindow(self.tabText(index).replace('&', ''),
self.parentWidget())
w = self.widget(index)
for i in self.tabIdx.values():
if i.widget == w:
detachWindow.tabIdx = self.tabIdx[i.index].index
break
detachWindow.closed.connect(self.attachTab)
tearOffWidget = self.widget(index)
#panel = self._getPanel(tearOffWidget)
#if not isinstance(panel, QTabWidget):
# panel.setWidgetVisible.disconnect(self.setWidgetVisibleSlot)
# panel.setWidgetVisible.connect(detachWindow.setWidgetVisibleSlot)
tearOffWidget.setParent(detachWindow)
if self.count() < 0:
self.setCurrentIndex(0)
# self._moveMenuTools(tearOffWidget)
# self._moveActions(tearOffWidget, detachWindow)
detachWindow.setWidget(tearOffWidget)
detachWindow.resize(tearOffWidget.size())
detachWindow.move(point)
detachWindow.show()
# def _moveMenuTools(self, widget):
# for p in widget.panels:
# if hasattr(p, 'menuToolsActions'):
# topLevelWindow = self.topLevelWidget(p)
# if hasattr(topLevelWindow, 'menuTools'):
# for action in p.menuToolsActions:
# topLevelWindow.menuTools.removeAction(action)
#def _moveActions(self, widget, window):
# for p in widget.panels:
# for action in p.actions:
# action.setVisible(False)
# for menu in p.getMenus():
# action = window.menuBar().addMenu(menu)
# action.setVisible(True)
# for toolbar in p.getToolbars():
# toolbar.hide()
# topLevelWindow = self.topLevelWidget(p)
# topLevelWindow.removeToolBar(toolbar)
# window.addToolBar(toolbar)
# toolbar.show()
def attachTab(self, detach_window):
detach_window.closed.connect(self.attachTab)
#detach_window.saveSettings(False)
tearOffWidget = detach_window.centralWidget()
#panel = self._getPanel(tearOffWidget)
# if panel and not isinstance(panel, QTabWidget):
# panel.setWidgetVisible.disconnect(detach_window.setWidgetVisibleSlot)
tearOffWidget.setParent(self)
# if panel and not isinstance(panel, QTabWidget):
# panel.setWidgetVisible.connect(self.setWidgetVisibleSlot)
#for p in tearOffWidget.panels:
# if hasattr(p, 'menuToolsActions'):
# topLevelWindow = self.topLevelWidget(self)
# if hasattr(topLevelWindow, 'menuTools'):
# for action in p.menuToolsActions:
# topLevelWindow.menuTools.removeAction(action)
newIndex = -1
for i in range(self.tabBar().count()):
w = self.widget(i)
for j in self.tabIdx.values():
if j.widget == w and j.index > detach_window.tabIdx:
newIndex = i
break
else:
continue
break
if newIndex == -1:
newIndex = self.tabBar().count()
newIndex = self.insertTab(newIndex, tearOffWidget,
detach_window.windowTitle())
if newIndex != -1:
self.setCurrentIndex(newIndex)
def tabChangedTab(self, index):
# for i in range(self.count()):
# for p in self.widget(i).panels:
# for toolbar in p.getToolbars():
# self.menuwindow.removeToolBar(toolbar)
# for action in p.actions:
# action.setVisible(False)
# if self.previousTabIdx < self.count():
# if self.widget(self.previousTabIdx):
# for p in self.widget(self.previousTabIdx).panels:
# if hasattr(p, 'menuToolsActions'):
# topLevelWindow = self.topLevelWidget(p)
# if hasattr(topLevelWindow, 'menuTools'):
# for action in p.menuToolsActions:
# topLevelWindow.menuTools.removeAction(action)
# if self.widget(index):
# for p in self.widget(index).panels:
# p.getMenus()
# if hasattr(p, 'menuToolsActions'):
# topLevelWindow = self.topLevelWidget(p)
# if hasattr(topLevelWindow, 'menuTools'):
# for action in p.menuToolsActions:
# topLevelWindow.menuTools.addAction(action)
# for toolbar in p.getToolbars():
# if hasattr(self.menuwindow, 'toolBarWindows'):
# self.menuwindow.insertToolBar(
# self.menuwindow.toolBarWindows, toolbar)
# else:
# self.menuwindow.addToolBar(toolbar)
# toolbar.show()
# for menu in p.actions:
# menu.setVisible(True)
self.previousTabIdx = index
def addPanel(self, widget, label):
#sgroup = SettingGroup(label)
#with sgroup as settings:
# detached = settings.value('detached', False, bool)
index = len(self.tabIdx)
self.tabIdx[index] = self.TabWidgetStorage(index, widget, label)
#if not detached:
index = self.addTab(widget, label)
if not label or label.isspace():
self.setTabEnabled(index, False)
for i in self.tabIdx.values(): # search for it in the list of tabs
if i.widget == widget:
i.setDetached(None)
#else:
# detachWindow = DetachedWindow(label.replace('&', ''),
# self.parentWidget())
# detachWindow.tabIdx = index
# detachWindow.setAttribute(Qt.WA_DeleteOnClose, True)
# self.tabIdx[index].setDetached(detachWindow)
# detachWindow.closed.connect(self.attachTab)
# panel = self._getPanel(widget)
# if panel and not isinstance(panel, QTabWidget):
# panel.setWidgetVisible.disconnect(self.setWidgetVisibleSlot)
# panel.setWidgetVisible.connect(
# detachWindow.setWidgetVisibleSlot)
# widget.setParent(detachWindow)
# self._moveMenuTools(widget)
# self._moveActions(widget, detachWindow)
# detachWindow.setWidget(widget)
# detachWindow.destroyed.connect(detachWindow.deleteLater)
# # with sgroup as settings:
# # detachWindow.restoreGeometry(settings.value('geometry', '',
# # QByteArray))
# detachWindow.show()
def topLevelWidget(self, w):
widget = w
while True:
parent = widget.parent()
if not parent:
break
widget = parent
return widget
class DetachedWindow(QMainWindow):
closed = pyqtSignal(object)
def __init__(self, title, parent):
self.tabIdx = -1
QMainWindow.__init__(self, parent)
self.setWindowTitle(title)
self.setWindowModality(Qt.NonModal)
# self.sgroup = SettingGroup(title)
# with self.sgroup as settings:
# loadBasicWindowSettings(self, settings)
@pyqtSlot(QWidget, bool)
def setWidgetVisibleSlot(self, widget, visible):
self.setVisible(visible)
def setWidget(self, widget):
self.setCentralWidget(widget)
widget.show()
def closeEvent(self, event):
# with self.sgroup as settings:
# settings.setValue('detached', False)
self.closed.emit(self)
self.deleteLater()
def moveEvent(self, event):
QMainWindow.moveEvent(self, event)
# self.saveSettings()
def resizeEvent(self, event):
QMainWindow.resizeEvent(self, event)
# self.saveSettings()
# def saveSettings(self, detached=True):
# with self.sgroup as settings:
# settings.setValue('detached', detached)
# settings.setValue('geometry', self.saveGeometry())
# settings.setValue('windowstate', self.saveState())
def firstWindow(w):
widget = w
while True:
parent = widget.parent()
if not parent:
widget = None
break
widget = parent
if widget.isWindow():
break
return widget

101
frappy/gui/ui/console.ui Normal file
View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>779</width>
<height>246</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextBrowser" name="logTextBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QPushButton" name="clearPushButton">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="msgLineEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&gt;&gt;&gt;</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="sendPushButton">
<property name="text">
<string>Send</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>msgLineEdit</sender>
<signal>returnPressed()</signal>
<receiver>sendPushButton</receiver>
<slot>animateClick()</slot>
<hints>
<hint type="sourcelabel">
<x>313</x>
<y>221</y>
</hint>
<hint type="destinationlabel">
<x>729</x>
<y>221</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>271</height>
</rect>
</property>
<property name="windowTitle">
<string>Log</string>
</property>
<widget class="QWidget" name="">
<property name="geometry">
<rect>
<x>0</x>
<y>10</y>
<width>351</width>
<height>261</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="logLevel">
<property name="currentText">
<string>Info</string>
</property>
<item>
<property name="text">
<string>Error</string>
</property>
</item>
<item>
<property name="text">
<string>Warning</string>
</property>
</item>
<item>
<property name="text">
<string>Info</string>
</property>
</item>
<item>
<property name="text">
<string>Debug</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="clear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTextBrowser" name="logBrowser"/>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

137
frappy/gui/ui/mainwin.ui Normal file
View File

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1228</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>frappy-gui</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout"/>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1228</width>
<height>30</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="separator"/>
<addaction name="actionAdd_SEC_node"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAbout_Qt"/>
</widget>
<widget class="QMenu" name="menuOptions">
<property name="title">
<string>Options</string>
</property>
<addaction name="actionShow_Logs"/>
<addaction name="actionDetailed_View"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuOptions"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionAdd_SEC_node"/>
</widget>
<action name="actionAdd_SEC_node">
<property name="icon">
<iconset resource="../../../resources/frappy-gui.qrc">
<normaloff>:/icons/connect</normaloff>:/icons/connect</iconset>
</property>
<property name="text">
<string>Add SEC node</string>
</property>
</action>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property>
</action>
<action name="actionAbout_Qt">
<property name="text">
<string>About Qt</string>
</property>
</action>
<action name="actionShow_Logs">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Log Window</string>
</property>
</action>
<action name="actionAdvanced_Mode">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Advanced Mode</string>
</property>
</action>
<action name="actionDetailed_View">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Detailed View</string>
</property>
</action>
</widget>
<resources>
<include location="../../../resources/frappy-gui.qrc"/>
</resources>
<connections>
<connection>
<sender>actionExit</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>613</x>
<y>299</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,187 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1228</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>frappy-gui</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="visibilityComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>user</string>
</property>
</item>
<item>
<property name="text">
<string>admin</string>
</property>
</item>
<item>
<property name="text">
<string>expert</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="validateCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Validate locally</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="treeWidget">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1228</width>
<height>33</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="separator"/>
<addaction name="actionAdd_SEC_node"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAbout_Qt"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionAdd_SEC_node"/>
</widget>
<action name="actionAdd_SEC_node">
<property name="text">
<string>Add SEC node</string>
</property>
</action>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property>
</action>
<action name="actionAbout_Qt">
<property name="text">
<string>About Qt</string>
</property>
</action>
</widget>
<resources/>
<connections>
<connection>
<sender>actionExit</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>613</x>
<y>299</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -6,15 +6,41 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>257</width> <width>801</width>
<height>162</height> <height>436</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item row="4" column="0"> <item>
<widget class="QLabel" name="moduleNameLabel">
<property name="font">
<font>
<pointsize>18</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="propertyGroupBox">
<property name="title">
<string>Properties:</string>
</property>
<layout class="QGridLayout" name="gridLayout_3"/>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QGroupBox" name="paramGroupBox"> <widget class="QGroupBox" name="paramGroupBox">
<property name="title"> <property name="title">
<string>Parameters:</string> <string>Parameters:</string>
@ -37,8 +63,15 @@
</property> </property>
</layout> </layout>
</widget> </widget>
<widget class="QGroupBox" name="commandGroupBox">
<property name="title">
<string>Commands:</string>
</property>
<layout class="QGridLayout" name="gridLayout_5"/>
</widget>
</widget>
</item> </item>
<item row="6" column="0"> <item>
<spacer name="verticalSpacer_3"> <spacer name="verticalSpacer_3">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -51,54 +84,6 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="2" column="0">
<widget class="QGroupBox" name="propertyGroupBox">
<property name="title">
<string>Properties:</string>
</property>
<layout class="QGridLayout" name="gridLayout_3"/>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Module name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="moduleNameLabel">
<property name="font">
<font>
<pointsize>18</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QGroupBox" name="commandGroupBox">
<property name="title">
<string>Commands:</string>
</property>
<layout class="QGridLayout" name="gridLayout_5"/>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>656</width>
<height>428</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1,10,0">
<item>
<widget class="QLabel" name="moduleName">
<property name="font">
<font>
<pointsize>18</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>moduleName</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="moduleDescription">
<property name="text">
<string>TextLabel</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="moduleDisplay"/>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,291 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>652</width>
<height>490</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>652</width>
<height>490</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="consoleTab">
<attribute name="title">
<string>Console</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextBrowser" name="logTextBrowser">
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QPushButton" name="clearPushButton">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="msgLineEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&gt;&gt;&gt;</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="sendPushButton">
<property name="text">
<string>Send</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="nodeInfoTab">
<attribute name="title">
<string>NodeInfo</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="0">
<widget class="QScrollArea" name="scrollArea_2">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>610</width>
<height>413</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Contact point:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="contactPointLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Equipment ID:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="equipmentIdLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Protocol version:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="protocolVersionLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="nodeDescriptionLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>description</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="label55">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
<zorder>label</zorder>
<zorder>contactPointLabel</zorder>
<zorder>equipmentIdLabel</zorder>
<zorder>label_3</zorder>
<zorder>protocolVersionLabel</zorder>
<zorder>label_5</zorder>
<zorder>nodeDescriptionLabel</zorder>
<zorder>label_2</zorder>
<zorder>verticalSpacer</zorder>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="modulesTab">
<property name="enabled">
<bool>true</bool>
</property>
<attribute name="title">
<string>Modules</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<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>610</width>
<height>413</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>msgLineEdit</sender>
<signal>returnPressed()</signal>
<receiver>sendPushButton</receiver>
<slot>animateClick()</slot>
<hints>
<hint type="sourcelabel">
<x>387</x>
<y>459</y>
</hint>
<hint type="destinationlabel">
<x>498</x>
<y>462</y>
</hint>
</hints>
</connection>
</connections>
</ui>

136
frappy/gui/ui/nodewidget.ui Normal file
View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Node</class>
<widget class="QWidget" name="Node">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>788</width>
<height>531</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSplitter" name="top_splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QWidget" name="infobox" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>50</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="infobox_splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="infotree" native="true"/>
<widget class="QGroupBox" name="nodeinfo">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>50</height>
</size>
</property>
<property name="title">
<string>NodeInfo</string>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QSplitter" name="middle_splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QScrollArea" name="view">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="viewContent">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>93</width>
<height>176</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
</layout>
</widget>
</widget>
<widget class="CollapsibleWidget" name="consoleWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>CollapsibleWidget</class>
<extends>QWidget</extends>
<header>frappy.gui.collapsible.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../resources/frappy-gui.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -24,7 +24,7 @@
from os import path from os import path
from frappy.gui.qt import uic from frappy.gui.qt import uic, QColor
uipath = path.dirname(__file__) uipath = path.dirname(__file__)
@ -52,3 +52,36 @@ class Value:
if self.readerror: if self.readerror:
args += (self.readerror,) args += (self.readerror,)
return 'Value%s' % repr(args) return 'Value%s' % repr(args)
class Colors:
@classmethod
def _setPalette(cls, palette):
if hasattr(cls, 'palette'):
return
cls.palette = palette
background = palette.window().color().lightness()
foreground = palette.windowText().color().lightness()
if background > foreground: # light
cls.colors = {
'orange': QColor('#FA6800'),
'plot-fg': QColor('black'),
'plot-bg': QColor('white'),
0: QColor('black'),
1: QColor('blue'),
2: QColor('#FA6800'),
3: QColor('green'),
4: QColor('red'),
5: QColor('purple'),
}
else:
cls.colors = {
'orange': QColor('#FA6800'),
'plot-fg': QColor('white'),
'plot-bg': QColor('black'),
0: QColor('white'),
1: QColor('#72ADD4'),
2: QColor('#FA6800'),
3: QColor('olive'),
4: QColor('red'),
5: QColor('purple'),
}

19
resources/frappy-gui.qrc Normal file
View File

@ -0,0 +1,19 @@
<RCC>
<qresource prefix="leds">
<file alias="gray">leds/simplegray.png</file>
<file alias="clear">leds/clear.png</file>
<file alias="green">leds/simplegreen.png</file>
<file alias="orange">leds/simpleorange.png</file>
<file alias="red">leds/simplered.png</file>
<file alias="unknown">leds/simpleunknown.png</file>
<file alias="white">leds/simplewhite.png</file>
<file alias="yellow">leds/simpleyellow.png</file>
</qresource>
<qresource prefix="icons">
<file alias="stop">icons/cross-circle.png</file>
<file alias="plot-add">icons/system-monitor--plus.png</file>
<file alias="submit">icons/arrow-turn-180.png</file>
<file alias="connect">icons/plug--plus.png</file>
<file alias="plot">icons/system-monitor.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

BIN
resources/leds/clear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB