changeset 88:f3fe557be5ed

Split off of items to reduce file size
author windel
date Tue, 27 Nov 2012 18:00:13 +0100
parents 367006d423ae
children 4b1892054744
files python/apps/diagrameditor.py python/apps/diagramitems.py
diffstat 2 files changed, 415 insertions(+), 408 deletions(-) [+]
line wrap: on
line diff
--- 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):
--- /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()