# HG changeset patch # User windel # Date 1354035613 -3600 # Node ID f3fe557be5ede46db518884fe4d9806c16589d46 # Parent 367006d423ae0f5d85132b393070e46bc2104942 Split off of items to reduce file size diff -r 367006d423ae -r f3fe557be5ed python/apps/diagrameditor.py --- a/python/apps/diagrameditor.py Fri Nov 23 18:27:29 2012 +0100 +++ b/python/apps/diagrameditor.py Tue Nov 27 18:00:13 2012 +0100 @@ -4,6 +4,8 @@ from PyQt4.QtCore import * import sys, json, base64 +from diagramitems import Connection, ResizeSelectionHandle, BlockItem, DiagramScene + """ Author: Windel Bouwman Year: 2012 @@ -17,23 +19,6 @@ newicon = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADSUlEQVR42r2XO0xaURjHLzYmdXRi\nYXBwdDScJl3djQNLw0TSAR88FR9JU6P4QKMCaTqQdGgXsQQMQXFwcGXhCCG+4iNEo6mDMZL45kK/\n76THUO+9Qi/efskJcm4O3+/+v8f51Al/LBaLmTs6Or43gAkq7eHhoXh2dmZva2v7WusZHf9jZGTE\nPz4+blfjuFwuC6IoCsViUbi8vCwDhM1oNH75V4AAANjUAJRKJeYc183NjXB1dVU+Pj7+CIp+qxlg\neHg44PV6VQHg2z8+PjKAu7s7oampSQAVxL29PUtnZ+eP/waA6/r6WtDr9UyJ09NTcXd319LV1aUI\n8QQwNDQUmJiYqBsA5BcMBgPLC4Q4OTkRt7e3LSaTSRbiCWBwcDAwOTmpOgfQOVSBcHFxIbS0tLB9\nDgH5IGYyGYvZbJZAvAoAOsL4IwTEngGxH9fp2MJnhUJBTKfT5t7e3rAsgMfjYQB4oB4V+OJAuI+A\naGtra6nz8/P3o6OjJQnAwMBAYGpqSjUAh8Aw8JLE3OBqIMTGxgbNZrPE5/MVFQFUe6+A4M5xoWMe\nIg4ASksB+vv761aAG3eKMFwBBFhfX6ebm5sEyl0eAHOgjqvgRSBUA3KAghHouFIAt9vNFNACgIci\nmUxSKEcyNjYmBXC5XK8Wgudvz8OAAJgDigD1lGE155UAsiFwOp2vUgVKzisBZJMQAdR2wlqco62s\nrNBcLkfgzpECOByOuhXg5cc733NLJBIMQLYP2O12BqB0uJrjyk8li8fjdGtri4AfeQAMAUpXayJW\ne2MlANlWbLPZGAB2LPagCoQapZaXl+nOzg6ZmZmRB8CBhANoYTB5U5iQyOzsrDwAjmR4hWpl0WiU\nwpxI5ubmpAAwKLAQ4HWqlUUiEbq/v0/m5+flATAEWiqwtLREDw4OyMLCgjyA1iEIh8P08PCQ+P1+\nKUBPTw8D0DIEi4uL9OjoiASDQSlAd3c3A7i/v9cUAKsgFApJAaxWa2B6etp2e3v71yGlen++X60v\n4EwAAOlUKvUO+oEUoLW19UNfX9/nxsbGhsoOxy+Wl75X2+drdXX1J/zX9AkmI+lU3N7e/hYSxAAT\n0Rs+FdX69rXsA1y5ubn5Vz6fL1Q++w30VO4/0/9IewAAAABJRU5ErkJggg==\n' -def enum(**enums): - return type('Enum', (), enums) - -Position = enum(TOP=0, TOP_RIGHT=1, RIGHT=2, BOTTOM_RIGHT=3, BOTTOM=4, BOTTOM_LEFT=5, LEFT=6, TOP_LEFT=7) - -def buildPath(pts): - path = QPainterPath(pts[0]) - for pt in pts[1:]: path.lineTo(pt) - return path - -def equalSpace(n, l, offset=15): - if n == 1: - return [l / 2] - elif n > 1: - return [offset + (l - offset*2)/(n - 1)*i for i in range(n)] - return [] - def uniqify(name, names): newname, i = name, 1 while newname in names: newname, i = name + str(i), i + 1 @@ -42,101 +27,6 @@ def indent(lines): return [' ' + line for line in lines] -class Connection(QGraphicsPathItem): - """ Implementation of a connection between blocks """ - def __init__(self, fromPort=None, toPort=None): - super(Connection, self).__init__() - self.pos2 = self.fromPort = self.toPort = None - self.setFlags(self.ItemIsSelectable | self.ItemClipsToShape) - pen = QPen(Qt.blue, 2, cap=Qt.RoundCap) - self.setPen(pen) - self.arrowhead = QGraphicsPathItem(self) - self.arrowhead.setPath(buildPath([QPointF(0.0, 0.0), QPointF(-6.0, 10.0), QPointF(6.0, 10.0), QPointF(0.0, 0.0)])) - self.arrowhead.setPen(pen) - self.arrowhead.setBrush(QBrush(pen.color())) - self.vias = [] - self.setFromPort(fromPort) - self.setToPort(toPort) - def getDict(self): - d = {} - d['fromBlock'] = self.fromPort.block.name - d['fromPort'] = self.fromPort.name - d['toBlock'] = self.toPort.block.name - d['toPort'] = self.toPort.name - return d - Dict = property(getDict) - def myDelete(self): - scene = self.scene() - if scene: - self.setFromPort(None) - self.setToPort(None) - scene.removeItem(self) - def setFromPort(self, fromPort): - if self.fromPort: - self.fromPort.posCallbacks.remove(self.setBeginPos) - self.fromPort.connection = None - self.fromPort = fromPort - if self.fromPort: - self.fromPort.connection = self - self.updateLineStukken() - self.fromPort.posCallbacks.append(self.setBeginPos) - def setToPort(self, toPort): - if self.toPort: - self.toPort.posCallbacks.remove(self.setEndPos) - self.toPort.connection = None - self.toPort = toPort - if self.toPort: - self.setEndPos(toPort.scenePos()) - self.toPort.connection = self - self.toPort.posCallbacks.append(self.setEndPos) - def getPos1(self): - if self.fromPort: - return self.fromPort.scenePos() - def setBeginPos(self, pos1): self.updateLineStukken() - def setEndPos(self, endpos): - self.pos2 = endpos - self.updateLineStukken() - def itemChange(self, change, value): - if change == self.ItemSelectedHasChanged: - for via in self.vias: - via.setVisible(value) - return super(Connection, self).itemChange(change, value) - def shape(self): return self.myshape - def updateLineStukken(self): - """ - This algorithm determines the optimal routing of all signals. - TODO: implement nice automatic line router - """ - pos1 = self.getPos1() - pos2 = self.pos2 - if pos1 is None or pos2 is None: - return - scene = self.scene() - vias = [pos1 + QPointF(20, 0)] + self.vias + [pos2 + QPointF(-20, 0)] - if scene: - litem = QGraphicsLineItem() - litem.setFlags(self.ItemIsSelectable) - scene.addItem(litem) - for p1, p2 in zip(vias[:-1], vias[1:]): - line = QLineF(p1, p2) - litem.setLine(line) - citems = scene.collidingItems(litem) - citems = [i for i in citems if type(i) is BlockItem] - scene.removeItem(litem) - pts = [pos1] + vias + [pos2] - self.arrowhead.setPos(pos2) - self.arrowhead.setRotation(90) - p = buildPath(pts) - pps = QPainterPathStroker() - pps.setWidth(3) - p = pps.createStroke(p).simplified() - self.setPath(p) - """ Create a shape outline using the path stroker """ - s = super(Connection, self).shape() - pps = QPainterPathStroker() - pps.setWidth(10) - self.myshape = pps.createStroke(s).simplified() - class ParameterDialog(QDialog): def __init__(self, block, parent = None): super(ParameterDialog, self).__init__(parent) @@ -155,196 +45,6 @@ self.block.code = self.codeEdit.toPlainText() self.close() -class PortItem(QGraphicsPathItem): - """ Represents a port to a subsystem """ - def __init__(self, name, block): - super(PortItem, self).__init__(block) - self.textItem = QGraphicsTextItem(self) - self.connection = None - self.block = block - self.setCursor(QCursor(Qt.CrossCursor)) - self.setPen(QPen(Qt.blue, 2, cap=Qt.RoundCap)) - self.name = name - self.posCallbacks = [] - self.setFlag(self.ItemSendsScenePositionChanges, True) - def getName(self): return self.textItem.toPlainText() - def setName(self, name): - self.textItem.setPlainText(name) - rect = self.textItem.boundingRect() - lw, lh = rect.width(), rect.height() - lx = 3 if type(self) is InputPort else -3 - lw - self.textItem.setPos(lx, -lh / 2) - name = property(getName, setName) - def getDict(self): return {'name': self.name} - Dict = property(getDict) - def itemChange(self, change, value): - if change == self.ItemScenePositionHasChanged: - for cb in self.posCallbacks: cb(value) - return value - return super(PortItem, self).itemChange(change, value) - -class OutputPort(PortItem): - def __init__(self, name, block, d=10.0): - super(OutputPort, self).__init__(name, block) - self.setPath(buildPath([QPointF(0.0, -d), QPointF(d, 0), QPointF(0.0, d)])) - def mousePressEvent(self, event): - self.scene().startConnection(self) - -class InputPort(PortItem): - def __init__(self, name, block, d=10.0): - super(InputPort, self).__init__(name, block) - self.setPath(buildPath([QPointF(-d, -d), QPointF(0, 0), QPointF(-d, d)])) - -class Handle(QGraphicsEllipseItem): - """ A handle that can be moved by the mouse """ - def __init__(self, parent=None): - dx = 13.0 - super(Handle, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent) - self.posChangeCallbacks = [] - self.setBrush(QBrush(Qt.white)) - self.setFlags(self.ItemSendsScenePositionChanges | self.ItemIsMovable) - self.setVisible(False) - self.setCursor(QCursor(Qt.SizeFDiagCursor)) - def mouseMoveEvent(self, event): - """ Move function without moving the other selected elements """ - p = self.mapToParent(event.pos()) - self.setPos(p) - def itemChange(self, change, value): - if change == self.ItemPositionChange: - for cb in self.posChangeCallbacks: - res = cb(value) - if res: value = res - return value - return super(Handle, self).itemChange(change, value) - -class ResizeSelectionHandle(Handle): - def __init__(self, position): - super(ResizeSelectionHandle, self).__init__() - self.position = position - if self.position in [Position.TOP_LEFT, Position.BOTTOM_RIGHT]: - self.setCursor(QCursor(Qt.SizeFDiagCursor)) - elif self.position in [Position.TOP_RIGHT, Position.BOTTOM_LEFT]: - self.setCursor(QCursor(Qt.SizeBDiagCursor)) - elif self.position in [Position.TOP, Position.BOTTOM]: - self.setCursor(QCursor(Qt.SizeVerCursor)) - elif self.position in [Position.LEFT, Position.RIGHT]: - self.setCursor(QCursor(Qt.SizeHorCursor)) - def mouseMoveEvent(self, event): - self.scene().sizerMoveEvent(self, event.scenePos()) - -class BlockItem(QGraphicsRectItem): - """ Represents a block in the diagram """ - def __init__(self, name='Untitled', parent=None): - super(BlockItem, self).__init__(parent) - self.subModel = DiagramScene() - self.subModel.containingBlock = self - # Properties of the rectangle: - self.setPen(QPen(Qt.blue, 2)) - self.setBrush(QBrush(Qt.lightGray)) - self.setFlags(self.ItemIsSelectable | self.ItemIsMovable | self.ItemSendsScenePositionChanges) - self.setCursor(QCursor(Qt.PointingHandCursor)) - self.label = QGraphicsTextItem(name, self) - self.name = name - self.code = '' - # Create corner for resize: - button = QPushButton('+in') - button.clicked.connect(self.newInputPort) - self.buttonItemAddInput = QGraphicsProxyWidget(self) - self.buttonItemAddInput.setWidget(button) - self.buttonItemAddInput.setVisible(False) - button = QPushButton('+out') - button.clicked.connect(self.newOutputPort) - self.buttonItemAddOutput = QGraphicsProxyWidget(self) - self.buttonItemAddOutput.setWidget(button) - self.buttonItemAddOutput.setVisible(False) - # Inputs and outputs of the block: - self.inputs = [] - self.outputs = [] - def editParameters(self): - pd = ParameterDialog(self, self.window()) - pd.exec_() - def mouseDoubleClickEvent(self, event): - #self.editParameters() - scene = self.scene() - if scene: - for view in scene.views(): - view.diagram = self.subModel - view.zoomAll() - def newInputPort(self): - names = [i.name for i in self.inputs + self.outputs] - self.addInput(InputPort(uniqify('in', names), self)) - def newOutputPort(self): - names = [i.name for i in self.inputs + self.outputs] - self.addOutput(OutputPort(uniqify('out', names), self)) - def setName(self, name): self.label.setPlainText(name) - def getName(self): return self.label.toPlainText() - name = property(getName, setName) - def getDict(self): - d = {'x': self.scenePos().x(), 'y': self.scenePos().y()} - rect = self.rect() - d.update({'width': rect.width(), 'height': rect.height()}) - d.update({'name': self.name, 'code': self.code}) - d['inputs'] = [inp.Dict for inp in self.inputs] - d['outputs'] = [outp.Dict for outp in self.outputs] - d['submodel'] = self.subModel.Dict - return d - def setDict(self, d): - self.name = d['name'] - self.code = d['code'] - self.setPos(d['x'], d['y']) - self.changeSize(d['width'], d['height']) - for inp in d['inputs']: self.addInput(InputPort(inp['name'], self)) - for outp in d['outputs']: self.addOutput(OutputPort(outp['name'], self)) - self.subModel.Dict = d['submodel'] - Dict = property(getDict, setDict) - def gencode(self): - c = ['def {0}():'.format(self.name)] - if self.code: - c += indent(self.code.split('\n')) - else: - c += indent(['pass']) - return c - def addInput(self, i): - self.inputs.append(i) - self.updateSize() - def addOutput(self, o): - self.outputs.append(o) - self.updateSize() - def contextMenuEvent(self, event): - menu = QMenu() - pa = menu.addAction('Parameters') - pa.triggered.connect(self.editParameters) - menu.exec_(event.screenPos()) - def itemChange(self, change, value): - if change == self.ItemSelectedHasChanged: - for child in [self.buttonItemAddInput, self.buttonItemAddOutput]: - child.setVisible(value) - return super(BlockItem, self).itemChange(change, value) - def myDelete(self): - for p in self.inputs + self.outputs: - if p.connection: p.connection.myDelete() - self.scene().removeItem(self) - def updateSize(self): - rect = self.rect() - h, w = rect.height(), rect.width() - self.buttonItemAddInput.setPos(0, h + 4) - self.buttonItemAddOutput.setPos(w+10, h+4) - for inp, y in zip(self.inputs, equalSpace(len(self.inputs), h)): - inp.setPos(0.0, y) - for outp, y in zip(self.outputs, equalSpace(len(self.outputs), h)): - outp.setPos(w, y) - def setCenterAndSize(self, center, size): - self.changeSize(size.width(), size.height()) - p = QPointF(size.width(), size.height()) - self.setPos(center - p / 2) - def changeSize(self, w, h): - h = 20 if h < 20 else h - w = 40 if w < 40 else w - self.setRect(0.0, 0.0, w, h) - rect = self.label.boundingRect() - self.label.setPos((w - rect.width()) / 2, (h - rect.height()) / 2) - self.updateSize() - class EditorGraphicsView(QGraphicsView): def __init__(self, parent=None): QGraphicsView.__init__(self, parent) @@ -489,112 +189,6 @@ def columnCount(self, parent): return 1 -class DiagramScene(QGraphicsScene): - def __init__(self): - super(DiagramScene, self).__init__() - self.startedConnection = None - self.selectionHandles = [ResizeSelectionHandle(i) for i in range(8)] - for h in self.selectionHandles: - self.addItem(h) - h.setVisible(False) - self.selectionChanged.connect(self.handleSelectionChanged) - def repositionAndShowHandles(self): - r = self.selectionRect - self.selectionHandles[Position.TOP_LEFT].setPos(r.topLeft()) - self.selectionHandles[Position.TOP].setPos(r.center().x(), r.top()) - self.selectionHandles[Position.TOP_RIGHT].setPos(r.topRight()) - self.selectionHandles[Position.RIGHT].setPos(r.right(), r.center().y()) - self.selectionHandles[Position.BOTTOM_RIGHT].setPos(r.bottomRight()) - self.selectionHandles[Position.BOTTOM].setPos(r.center().x(), r.bottom()) - self.selectionHandles[Position.BOTTOM_LEFT].setPos(r.bottomLeft()) - self.selectionHandles[Position.LEFT].setPos(r.left(), r.center().y()) - for h in self.selectionHandles: - h.setVisible(True) - def handleSelectionChanged(self): - [h.setVisible(False) for h in self.selectionHandles] - items = self.selectedItems() - items = [i for i in items if type(i) is BlockItem] - if items: - r = QRectF() - for i in items: - r = r.united(i.boundingRect().translated(i.scenePos())) - self.selectionRect = r - self.repositionAndShowHandles() - def sizerMoveEvent(self, handle, pos): - if handle.position == Position.TOP_LEFT: self.selectionRect.setTopLeft(pos) - elif handle.position == Position.TOP: self.selectionRect.setTop(pos.y()) - elif handle.position == Position.TOP_RIGHT: self.selectionRect.setTopRight(pos) - elif handle.position == Position.RIGHT: self.selectionRect.setRight(pos.x()) - elif handle.position == Position.BOTTOM_RIGHT: self.selectionRect.setBottomRight(pos) - elif handle.position == Position.BOTTOM: self.selectionRect.setBottom(pos.y()) - elif handle.position == Position.BOTTOM_LEFT: self.selectionRect.setBottomLeft(pos) - elif handle.position == Position.LEFT: self.selectionRect.setLeft(pos.x()) - else: - print('invalid position') - self.repositionAndShowHandles() - items = self.selectedItems() - items = [i for i in items if type(i) is BlockItem] - if items: - item = items[0] - # TODO resize more items! - item.setCenterAndSize(self.selectionRect.center(), self.selectionRect.size()) - - blocks = property(lambda sel: [i for i in sel.items() if type(i) is BlockItem]) - connections = property(lambda sel: [i for i in sel.items() if type(i) is Connection]) - def setDict(self, d): - for block in d['blocks']: - b = BlockItem() - self.addItem(b) - b.Dict = block - for con in d['connections']: - fromPort = self.findPort(con['fromBlock'], con['fromPort']) - toPort = self.findPort(con['toBlock'], con['toPort']) - self.addItem(Connection(fromPort, toPort)) - def getDict(self): - return {'blocks': [b.Dict for b in self.blocks], 'connections': [c.Dict for c in self.connections]} - Dict = property(getDict, setDict) - def gencode(self): - c = [] - for b in self.blocks: - c += b.gencode() - for b in self.blocks: - c.append('{0}()'.format(b.name)) - return c - def findPort(self, blockname, portname): - block = self.findBlock(blockname) - if block: - for port in block.inputs + block.outputs: - if port.name == portname: return port - def findBlock(self, blockname): - for block in self.blocks: - if block.name == blockname: return block - def addNewBlock(self, pos, name): - blocknames = [item.name for item in self.blocks] - b1 = BlockItem(uniqify(name, blocknames)) - b1.setPos(pos) - self.addItem(b1) - def mouseMoveEvent(self, event): - if self.startedConnection: - pos = event.scenePos() - self.startedConnection.setEndPos(pos) - super(DiagramScene, self).mouseMoveEvent(event) - def mouseReleaseEvent(self, event): - if self.startedConnection: - for item in self.items(event.scenePos()): - if type(item) is InputPort and item.connection == None: - self.startedConnection.setToPort(item) - self.startedConnection = None - return - self.startedConnection.myDelete() - self.startedConnection = None - super(DiagramScene, self).mouseReleaseEvent(event) - def startConnection(self, port): - self.startedConnection = Connection(port, None) - pos = port.scenePos() - self.startedConnection.setEndPos(pos) - self.addItem(self.startedConnection) - def deleteItems(self): - for item in list(self.selectedItems()): item.myDelete() class LibraryWidget(QListView): def __init__(self): diff -r 367006d423ae -r f3fe557be5ed python/apps/diagramitems.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/diagramitems.py Tue Nov 27 18:00:13 2012 +0100 @@ -0,0 +1,413 @@ +from PyQt4.QtGui import * +from PyQt4.QtCore import * + +def enum(**enums): + return type('Enum', (), enums) + +Position = enum(TOP=0, TOP_RIGHT=1, RIGHT=2, BOTTOM_RIGHT=3, BOTTOM=4, BOTTOM_LEFT=5, LEFT=6, TOP_LEFT=7) + +def buildPath(pts): + path = QPainterPath(pts[0]) + for pt in pts[1:]: path.lineTo(pt) + return path + +def equalSpace(n, l, offset=15): + if n == 1: + return [l / 2] + elif n > 1: + return [offset + (l - offset*2)/(n - 1)*i for i in range(n)] + return [] + +class Connection(QGraphicsPathItem): + """ Implementation of a connection between blocks """ + def __init__(self, fromPort=None, toPort=None): + super(Connection, self).__init__() + self.pos2 = self.fromPort = self.toPort = None + self.setFlags(self.ItemIsSelectable | self.ItemClipsToShape) + pen = QPen(Qt.blue, 2, cap=Qt.RoundCap) + self.setPen(pen) + self.arrowhead = QGraphicsPathItem(self) + self.arrowhead.setPath(buildPath([QPointF(0.0, 0.0), QPointF(-6.0, 10.0), QPointF(6.0, 10.0), QPointF(0.0, 0.0)])) + self.arrowhead.setPen(pen) + self.arrowhead.setBrush(QBrush(pen.color())) + self.vias = [] + self.setFromPort(fromPort) + self.setToPort(toPort) + def getDict(self): + d = {} + d['fromBlock'] = self.fromPort.block.name + d['fromPort'] = self.fromPort.name + d['toBlock'] = self.toPort.block.name + d['toPort'] = self.toPort.name + return d + Dict = property(getDict) + def myDelete(self): + scene = self.scene() + if scene: + self.setFromPort(None) + self.setToPort(None) + scene.removeItem(self) + def setFromPort(self, fromPort): + if self.fromPort: + self.fromPort.posCallbacks.remove(self.setBeginPos) + self.fromPort.connection = None + self.fromPort = fromPort + if self.fromPort: + self.fromPort.connection = self + self.updateLineStukken() + self.fromPort.posCallbacks.append(self.setBeginPos) + def setToPort(self, toPort): + if self.toPort: + self.toPort.posCallbacks.remove(self.setEndPos) + self.toPort.connection = None + self.toPort = toPort + if self.toPort: + self.setEndPos(toPort.scenePos()) + self.toPort.connection = self + self.toPort.posCallbacks.append(self.setEndPos) + def getPos1(self): + if self.fromPort: + return self.fromPort.scenePos() + def setBeginPos(self, pos1): self.updateLineStukken() + def setEndPos(self, endpos): + self.pos2 = endpos + self.updateLineStukken() + def itemChange(self, change, value): + if change == self.ItemSelectedHasChanged: + for via in self.vias: + via.setVisible(value) + return super(Connection, self).itemChange(change, value) + def shape(self): return self.myshape + def updateLineStukken(self): + """ + This algorithm determines the optimal routing of all signals. + TODO: implement nice automatic line router + """ + pos1 = self.getPos1() + pos2 = self.pos2 + if pos1 is None or pos2 is None: + return + scene = self.scene() + vias = [pos1 + QPointF(20, 0)] + self.vias + [pos2 + QPointF(-20, 0)] + if scene: + litem = QGraphicsLineItem() + litem.setFlags(self.ItemIsSelectable) + scene.addItem(litem) + for p1, p2 in zip(vias[:-1], vias[1:]): + line = QLineF(p1, p2) + litem.setLine(line) + citems = scene.collidingItems(litem) + citems = [i for i in citems if type(i) is BlockItem] + scene.removeItem(litem) + pts = [pos1] + vias + [pos2] + self.arrowhead.setPos(pos2) + self.arrowhead.setRotation(90) + p = buildPath(pts) + pps = QPainterPathStroker() + pps.setWidth(3) + p = pps.createStroke(p).simplified() + self.setPath(p) + """ Create a shape outline using the path stroker """ + s = super(Connection, self).shape() + pps = QPainterPathStroker() + pps.setWidth(10) + self.myshape = pps.createStroke(s).simplified() + +class PortItem(QGraphicsPathItem): + """ Represents a port to a subsystem """ + def __init__(self, name, block): + super(PortItem, self).__init__(block) + self.textItem = QGraphicsTextItem(self) + self.connection = None + self.block = block + self.setCursor(QCursor(Qt.CrossCursor)) + self.setPen(QPen(Qt.blue, 2, cap=Qt.RoundCap)) + self.name = name + self.posCallbacks = [] + self.setFlag(self.ItemSendsScenePositionChanges, True) + def getName(self): return self.textItem.toPlainText() + def setName(self, name): + self.textItem.setPlainText(name) + rect = self.textItem.boundingRect() + lw, lh = rect.width(), rect.height() + lx = 3 if type(self) is InputPort else -3 - lw + self.textItem.setPos(lx, -lh / 2) + name = property(getName, setName) + def getDict(self): return {'name': self.name} + Dict = property(getDict) + def itemChange(self, change, value): + if change == self.ItemScenePositionHasChanged: + for cb in self.posCallbacks: cb(value) + return value + return super(PortItem, self).itemChange(change, value) + +class OutputPort(PortItem): + def __init__(self, name, block, d=10.0): + super(OutputPort, self).__init__(name, block) + self.setPath(buildPath([QPointF(0.0, -d), QPointF(d, 0), QPointF(0.0, d)])) + def mousePressEvent(self, event): + self.scene().startConnection(self) + +class InputPort(PortItem): + def __init__(self, name, block, d=10.0): + super(InputPort, self).__init__(name, block) + self.setPath(buildPath([QPointF(-d, -d), QPointF(0, 0), QPointF(-d, d)])) + +class Handle(QGraphicsEllipseItem): + """ A handle that can be moved by the mouse """ + def __init__(self, parent=None): + dx = 13.0 + super(Handle, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent) + self.posChangeCallbacks = [] + self.setBrush(QBrush(Qt.white)) + self.setFlags(self.ItemSendsScenePositionChanges | self.ItemIsMovable) + self.setVisible(False) + self.setCursor(QCursor(Qt.SizeFDiagCursor)) + def mouseMoveEvent(self, event): + """ Move function without moving the other selected elements """ + p = self.mapToParent(event.pos()) + self.setPos(p) + def itemChange(self, change, value): + if change == self.ItemPositionChange: + for cb in self.posChangeCallbacks: + res = cb(value) + if res: value = res + return value + return super(Handle, self).itemChange(change, value) + +class ResizeSelectionHandle(Handle): + def __init__(self, position): + super(ResizeSelectionHandle, self).__init__() + self.position = position + if position in [Position.TOP_LEFT, Position.BOTTOM_RIGHT]: + self.setCursor(QCursor(Qt.SizeFDiagCursor)) + elif position in [Position.TOP_RIGHT, Position.BOTTOM_LEFT]: + self.setCursor(QCursor(Qt.SizeBDiagCursor)) + elif position in [Position.TOP, Position.BOTTOM]: + self.setCursor(QCursor(Qt.SizeVerCursor)) + elif position in [Position.LEFT, Position.RIGHT]: + self.setCursor(QCursor(Qt.SizeHorCursor)) + def mouseMoveEvent(self, event): + self.scene().sizerMoveEvent(self, event.scenePos()) + + +class BlockItem(QGraphicsRectItem): + """ Represents a block in the diagram """ + def __init__(self, name='Untitled', parent=None): + super(BlockItem, self).__init__(parent) + self.subModel = DiagramScene() + self.subModel.containingBlock = self + # Properties of the rectangle: + self.setPen(QPen(Qt.blue, 2)) + self.setBrush(QBrush(Qt.lightGray)) + self.setFlags(self.ItemIsSelectable | self.ItemIsMovable | self.ItemSendsScenePositionChanges) + self.setCursor(QCursor(Qt.PointingHandCursor)) + self.label = QGraphicsTextItem(name, self) + self.name = name + self.code = '' + # Create corner for resize: + button = QPushButton('+in') + button.clicked.connect(self.newInputPort) + self.buttonItemAddInput = QGraphicsProxyWidget(self) + self.buttonItemAddInput.setWidget(button) + self.buttonItemAddInput.setVisible(False) + button = QPushButton('+out') + button.clicked.connect(self.newOutputPort) + self.buttonItemAddOutput = QGraphicsProxyWidget(self) + self.buttonItemAddOutput.setWidget(button) + self.buttonItemAddOutput.setVisible(False) + # Inputs and outputs of the block: + self.inputs = [] + self.outputs = [] + def editParameters(self): + pd = ParameterDialog(self, self.window()) + pd.exec_() + def mouseDoubleClickEvent(self, event): + #self.editParameters() + scene = self.scene() + if scene: + for view in scene.views(): + view.diagram = self.subModel + view.zoomAll() + def newInputPort(self): + names = [i.name for i in self.inputs + self.outputs] + self.addInput(InputPort(uniqify('in', names), self)) + def newOutputPort(self): + names = [i.name for i in self.inputs + self.outputs] + self.addOutput(OutputPort(uniqify('out', names), self)) + def setName(self, name): self.label.setPlainText(name) + def getName(self): return self.label.toPlainText() + name = property(getName, setName) + def getDict(self): + d = {'x': self.scenePos().x(), 'y': self.scenePos().y()} + rect = self.rect() + d.update({'width': rect.width(), 'height': rect.height()}) + d.update({'name': self.name, 'code': self.code}) + d['inputs'] = [inp.Dict for inp in self.inputs] + d['outputs'] = [outp.Dict for outp in self.outputs] + d['submodel'] = self.subModel.Dict + return d + def setDict(self, d): + self.name = d['name'] + self.code = d['code'] + self.setPos(d['x'], d['y']) + self.changeSize(d['width'], d['height']) + for inp in d['inputs']: self.addInput(InputPort(inp['name'], self)) + for outp in d['outputs']: self.addOutput(OutputPort(outp['name'], self)) + self.subModel.Dict = d['submodel'] + Dict = property(getDict, setDict) + def gencode(self): + c = ['def {0}():'.format(self.name)] + if self.code: + c += indent(self.code.split('\n')) + else: + c += indent(['pass']) + return c + def addInput(self, i): + self.inputs.append(i) + self.updateSize() + def addOutput(self, o): + self.outputs.append(o) + self.updateSize() + def contextMenuEvent(self, event): + menu = QMenu() + pa = menu.addAction('Parameters') + pa.triggered.connect(self.editParameters) + menu.exec_(event.screenPos()) + def itemChange(self, change, value): + if change == self.ItemSelectedHasChanged: + for child in [self.buttonItemAddInput, self.buttonItemAddOutput]: + child.setVisible(value) + return super(BlockItem, self).itemChange(change, value) + def myDelete(self): + for p in self.inputs + self.outputs: + if p.connection: p.connection.myDelete() + self.scene().removeItem(self) + def updateSize(self): + rect = self.rect() + h, w = rect.height(), rect.width() + self.buttonItemAddInput.setPos(0, h + 4) + self.buttonItemAddOutput.setPos(w+10, h+4) + for inp, y in zip(self.inputs, equalSpace(len(self.inputs), h)): + inp.setPos(0.0, y) + for outp, y in zip(self.outputs, equalSpace(len(self.outputs), h)): + outp.setPos(w, y) + def setCenterAndSize(self, center, size): + self.changeSize(size.width(), size.height()) + p = QPointF(size.width(), size.height()) + self.setPos(center - p / 2) + def changeSize(self, w, h): + h = 20 if h < 20 else h + w = 40 if w < 40 else w + self.setRect(0.0, 0.0, w, h) + rect = self.label.boundingRect() + self.label.setPos((w - rect.width()) / 2, (h - rect.height()) / 2) + self.updateSize() + + +class DiagramScene(QGraphicsScene): + def __init__(self): + super(DiagramScene, self).__init__() + self.startedConnection = None + self.selectionHandles = [ResizeSelectionHandle(i) for i in range(8)] + for h in self.selectionHandles: + self.addItem(h) + h.setVisible(False) + self.selectionChanged.connect(self.handleSelectionChanged) + def repositionAndShowHandles(self): + r = self.selectionRect + self.selectionHandles[Position.TOP_LEFT].setPos(r.topLeft()) + self.selectionHandles[Position.TOP].setPos(r.center().x(), r.top()) + self.selectionHandles[Position.TOP_RIGHT].setPos(r.topRight()) + self.selectionHandles[Position.RIGHT].setPos(r.right(), r.center().y()) + self.selectionHandles[Position.BOTTOM_RIGHT].setPos(r.bottomRight()) + self.selectionHandles[Position.BOTTOM].setPos(r.center().x(), r.bottom()) + self.selectionHandles[Position.BOTTOM_LEFT].setPos(r.bottomLeft()) + self.selectionHandles[Position.LEFT].setPos(r.left(), r.center().y()) + for h in self.selectionHandles: + h.setVisible(True) + def handleSelectionChanged(self): + [h.setVisible(False) for h in self.selectionHandles] + items = self.selectedItems() + items = [i for i in items if type(i) is BlockItem] + if items: + r = QRectF() + for i in items: + r = r.united(i.boundingRect().translated(i.scenePos())) + self.selectionRect = r + self.repositionAndShowHandles() + def sizerMoveEvent(self, handle, pos): + if handle.position == Position.TOP_LEFT: self.selectionRect.setTopLeft(pos) + elif handle.position == Position.TOP: self.selectionRect.setTop(pos.y()) + elif handle.position == Position.TOP_RIGHT: self.selectionRect.setTopRight(pos) + elif handle.position == Position.RIGHT: self.selectionRect.setRight(pos.x()) + elif handle.position == Position.BOTTOM_RIGHT: self.selectionRect.setBottomRight(pos) + elif handle.position == Position.BOTTOM: self.selectionRect.setBottom(pos.y()) + elif handle.position == Position.BOTTOM_LEFT: self.selectionRect.setBottomLeft(pos) + elif handle.position == Position.LEFT: self.selectionRect.setLeft(pos.x()) + else: + print('invalid position') + self.repositionAndShowHandles() + items = self.selectedItems() + items = [i for i in items if type(i) is BlockItem] + if items: + item = items[0] + # TODO resize more items! + item.setCenterAndSize(self.selectionRect.center(), self.selectionRect.size()) + + blocks = property(lambda sel: [i for i in sel.items() if type(i) is BlockItem]) + connections = property(lambda sel: [i for i in sel.items() if type(i) is Connection]) + def setDict(self, d): + for block in d['blocks']: + b = BlockItem() + self.addItem(b) + b.Dict = block + for con in d['connections']: + fromPort = self.findPort(con['fromBlock'], con['fromPort']) + toPort = self.findPort(con['toBlock'], con['toPort']) + self.addItem(Connection(fromPort, toPort)) + def getDict(self): + return {'blocks': [b.Dict for b in self.blocks], 'connections': [c.Dict for c in self.connections]} + Dict = property(getDict, setDict) + def gencode(self): + c = [] + for b in self.blocks: + c += b.gencode() + for b in self.blocks: + c.append('{0}()'.format(b.name)) + return c + def findPort(self, blockname, portname): + block = self.findBlock(blockname) + if block: + for port in block.inputs + block.outputs: + if port.name == portname: return port + def findBlock(self, blockname): + for block in self.blocks: + if block.name == blockname: return block + def addNewBlock(self, pos, name): + blocknames = [item.name for item in self.blocks] + b1 = BlockItem(uniqify(name, blocknames)) + b1.setPos(pos) + self.addItem(b1) + def mouseMoveEvent(self, event): + if self.startedConnection: + pos = event.scenePos() + self.startedConnection.setEndPos(pos) + super(DiagramScene, self).mouseMoveEvent(event) + def mouseReleaseEvent(self, event): + if self.startedConnection: + for item in self.items(event.scenePos()): + if type(item) is InputPort and item.connection == None: + self.startedConnection.setToPort(item) + self.startedConnection = None + return + self.startedConnection.myDelete() + self.startedConnection = None + super(DiagramScene, self).mouseReleaseEvent(event) + def startConnection(self, port): + self.startedConnection = Connection(port, None) + pos = port.scenePos() + self.startedConnection.setEndPos(pos) + self.addItem(self.startedConnection) + def deleteItems(self): + for item in list(self.selectedItems()): item.myDelete()