Rework GUI.
Work-in-progress state of the new gui. Change-Id: Ib5e9ad2178b372fbd2914077096a9c73f025ecb7
@ -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
@ -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
@ -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
|
@ -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
|
|
||||||
|
@ -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)
|
|
@ -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
|
||||||
|
176
frappy/gui/moduleoverview.py
Normal 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
@ -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)
|
@ -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
@ -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
@ -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()
|
@ -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
525
frappy/gui/tabwidget.py
Normal 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
@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
|
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||||
|
p, li { white-space: pre-wrap; }
|
||||||
|
</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||||
|
<p style="-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;"><br /></p></body></html></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>>>></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>
|
85
frappy/gui/ui/logwindow.ui
Normal 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
@ -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>
|
@ -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>
|
|
@ -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/>
|
||||||
|
91
frappy/gui/ui/modulewidget.ui
Normal 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>
|
@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
|
||||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
|
||||||
p, li { white-space: pre-wrap; }
|
|
||||||
</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;">
|
|
||||||
<p style="-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;"><br /></p></body></html></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>>>></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
@ -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>
|
@ -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
@ -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>
|
BIN
resources/icons/arrow-turn-180.png
Normal file
After Width: | Height: | Size: 647 B |
BIN
resources/icons/cross-circle.png
Normal file
After Width: | Height: | Size: 729 B |
BIN
resources/icons/plug--plus.png
Normal file
After Width: | Height: | Size: 747 B |
BIN
resources/icons/system-monitor--plus.png
Normal file
After Width: | Height: | Size: 654 B |
BIN
resources/icons/system-monitor.png
Normal file
After Width: | Height: | Size: 547 B |
BIN
resources/leds/clear.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
resources/leds/simplegray.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
resources/leds/simplegreen.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
resources/leds/simpleorange.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
resources/leds/simplered.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
resources/leds/simpleunknown.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
resources/leds/simplewhite.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
resources/leds/simpleyellow.png
Normal file
After Width: | Height: | Size: 1.3 KiB |