changeset 55:9414bad225a8

Added nice resizing
author windel
date Thu, 19 Apr 2012 18:16:34 +0200
parents d8163d2c3779
children 7a82f0d52e85
files applications/lab/diagrameditor.py
diffstat 1 files changed, 142 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/applications/lab/diagrameditor.py	Thu Apr 12 18:03:33 2012 +0200
+++ b/applications/lab/diagrameditor.py	Thu Apr 19 18:16:34 2012 +0200
@@ -13,6 +13,28 @@
   $ python [thisfile.py]
 """
 
+class ArrowHead(QGraphicsPathItem):
+   def __init__(self, parent):
+      super(ArrowHead, self).__init__(parent)
+      arrowPath = QPainterPath(QPointF(0.0, 0.0))
+      arrowPath.lineTo(-6.0, 10.0)
+      arrowPath.lineTo(6.0, 10.0)
+      arrowPath.lineTo(0.0, 0.0)
+      self.setPath(arrowPath)
+      pen = QPen(Qt.blue, 2)
+      self.setPen(pen)
+      self.setBrush(QBrush(pen.color()))
+
+class LinePart(QGraphicsLineItem):
+   def __init__(self, x1, y1, x2, y2):
+      super(LinePart, self).__init__(x1, y1, x2, y2)
+      self.setFlags(self.ItemIsSelectable)
+   def boundingRect(self):
+      rect = super(LinePart, self).boundingRect()
+      rect.adjust(-2, -2, 2, 2)
+      return rect
+
+#class Connection(QGraphicsItemGroup):
 class Connection(QGraphicsPathItem):
    """ Implementation of a connection between blocks """
    def __init__(self, fromPort, toPort):
@@ -22,21 +44,19 @@
       self.fromPort = None
       self.toPort = None
       #self.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
-      self.setFlags(self.ItemIsSelectable)
-      pen = QPen(Qt.blue, 2)
-      pen.setCapStyle(Qt.RoundCap)
-      self.setPen(pen)
-      arrowPath = QPainterPath(QPointF(0.0, 0.0))
-      arrowPath.lineTo(-6.0, 10.0)
-      arrowPath.lineTo(6.0, 10.0)
-      arrowPath.lineTo(0.0, 0.0)
-      self.arrowhead = QGraphicsPathItem(arrowPath, self)
-      self.arrowhead.setPen(pen)
-      self.arrowhead.setBrush(QBrush(pen.color()))
+      self.setFlag(self.ItemIsSelectable, True)
+      self.setFlag(self.ItemClipsToShape, True)
+      self.pen = QPen(Qt.blue, 2)
+      self.pen.setCapStyle(Qt.RoundCap)
+      self.setPen(self.pen)
+      self.arrowhead = ArrowHead(self)
+      #self.addToGroup(self.arrowhead)
+      #self.body = QGraphicsPathItem(self)
+      #self.body.setPen(self.pen)
+      #self.addToGroup(self.body)
+      self.lineItems = []
       self.setFromPort(fromPort)
       self.setToPort(toPort)
-   #def shape(self):
-   #   return self.path()
    def setFromPort(self, fromPort):
       if self.fromPort:
          self.fromPort.posCallbacks.remove(self.setBeginPos)
@@ -64,6 +84,10 @@
    def setEndPos(self, endpos):
       self.pos2 = endpos
       self.updateLineStukken()
+   def itemChange(self, change, value):
+      if change == self.ItemSelectedHasChanged:
+         pass # TODO, do something useful here.
+      return super(Connection, self).itemChange(change, value)
    def updateLineStukken(self):
       """
          This algorithm determines the optimal routing of all signals.
@@ -71,69 +95,44 @@
       """
       if self.pos1 is None or self.pos2 is None:
          return
+
       # TODO: do not get the scene here?
-      ds = editor.diagramScene
-      self.arrowhead.setPos(self.pos2)
-
       # TODO: create pieces of lines.
 
       # Determine the begin and end positions:
-      p1 = self.pos1
-      p4 = self.pos2
-      x1, y1 = self.pos1.x(), self.pos1.y()
-      x4, y4 = self.pos2.x(), self.pos2.y()
+      pts = editor.diagramScene.vias(self.pos1, self.pos2)
 
-      def stripHits(hits):
-         """ Helper function that removes object hits """
-         hits = [hit for hit in hits if type(hit) is BlockItem]
-         if self in hits:
-            hits.remove(self)
-         if self.fromPort in hits:
-            hits.remove(self.fromPort)
-         if self.toPort in hits:
-            hits.remove(self.toPort)
-         return hits
-
-      def viaPoints(pA, pB):
-         # Construct intermediate points:
+      self.arrowhead.setPos(self.pos2)
+      if pts[-1].x() < pts[-2].x():
+         self.arrowhead.setRotation(-90)
+      else:
+         self.arrowhead.setRotation(90)
 
-         pAB1 = QPointF(pA.x(), pB.y())
-         pAB2 = QPointF(pB.x(), pA.y())
-         path1 = QPainterPath(pA)
-         path1.lineTo(pAB1)
-         path2 = QPainterPath(pA)
-         path2.lineTo(pAB2)
-         if len(stripHits(ds.items(path1))) > len(stripHits(ds.items(path2))):
-            pAB = pAB2
-         else:
-            pAB = pAB1
-         return [pAB]
-
-      # Determine left or right:
-      dx = QPointF(20, 0)
-      if len(stripHits(ds.items(p1 + dx))) > len(stripHits(ds.items(p1 - dx))):
-         p2 = p1 - dx
-      else:
-         p2 = p1 + dx
+      usePath = True
+      if usePath:
+         path = QPainterPath(pts[0])
+         # Now move from p2 to p3 without hitting blocks:
+         for pt in pts[1:]:
+            path.lineTo(pt)
+         for pt in reversed(pts[0:-1]):
+            path.lineTo(pt)
+         path.closeSubpath()
 
-      if len(stripHits(ds.items(p4 + dx))) > len(stripHits(ds.items(p4 - dx))):
-         p3 = p4 - dx
-         self.arrowhead.setRotation(90)
+         #self.body.setPath(path)
+         self.setPath(path)
+         #outline = QGraphicsPathItem(self.shape())
+         #editor.diagramScene.addItem(outline)
       else:
-         p3 = p4 + dx
-         self.arrowhead.setRotation(-90)
-
-      path = QPainterPath(p1)
-      path.lineTo(p2)
-      # Now move from p2 to p3 without hitting blocks:
-      pts = viaPoints(p2, p3)
-      for pt in pts:
-         path.lineTo(pt)
-      path.lineTo(p3)
-      path.lineTo(p4)
-
-      hits = stripHits(ds.items(path))
-      self.setPath(path)
+         # Delete old line items:
+         for li in self.lineItems:
+            editor.diagramScene.removeItem(li)
+            #self.removeFromGroup(li)
+         self.lineItems = []
+         for a, b in zip(pts[0:-1], pts[1:]):
+            li = LinePart(a.x(),a.y(),b.x(),b.y())
+            #self.addToGroup(li)
+            editor.diagramScene.addItem(li)
+            self.lineItems.append(li)
 
 class ParameterDialog(QDialog):
    def __init__(self, block, parent = None):
@@ -196,6 +195,11 @@
       self.setName(name)
       self.posCallbacks = []
       self.setFlag(self.ItemSendsScenePositionChanges, True)
+   def boundingRect(self):
+      rect = super(PortItem, self).boundingRect()
+      dx = 8
+      rect.adjust(-dx, -dx, dx, dx)
+      return rect
    def setName(self, name):
       self.name = name
       self.textItem.setPlainText(name)
@@ -224,22 +228,23 @@
 class HandleItem(QGraphicsEllipseItem):
    """ A handle that can be moved by the mouse """
    def __init__(self, parent=None):
-      super(HandleItem, self).__init__(QRectF(-4.0,-4.0,8.0,8.0), parent)
+      dx = 12.0
+      super(HandleItem, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent)
       self.posChangeCallbacks = []
       self.setBrush(QBrush(Qt.white))
       self.setFlag(self.ItemSendsScenePositionChanges, True)
       self.setFlag(self.ItemIsMovable, True)
-      #self.setFlag(self.ItemIsSelectable, 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:
-         x, y = value.x(), value.y()
          for cb in self.posChangeCallbacks:
-            res = cb(x, y)
+            res = cb(value)
             if res:
-               x, y = res
-               value = QPointF(x, y)
+               value = res
          return value
       # Call superclass method:
       return super(HandleItem, self).itemChange(change, value)
@@ -267,7 +272,7 @@
       # Create corner for resize:
       self.sizer = HandleItem(self)
       self.sizer.posChangeCallbacks.append(self.changeSize) # Connect the callback
-      #self.sizer.setVisible(False)
+      self.sizer.setVisible(False)
 
       # Inputs and outputs of the block:
       self.inputs = []
@@ -290,6 +295,10 @@
    def addOutput(self, o):
       self.outputs.append(o)
       self.updateSize()
+   def boundingRect(self):
+      rect = super(BlockItem, self).boundingRect()
+      rect.adjust(-3, -3, 3, 3)
+      return rect
 
    def contextMenuEvent(self, event):
       menu = QMenu()
@@ -298,6 +307,10 @@
       pa = menu.addAction('Add port')
       pa.triggered.connect(self.addPort)
       menu.exec_(event.screenPos())
+   def itemChange(self, change, value):
+      if change == self.ItemSelectedHasChanged:
+         self.sizer.setVisible(value)
+      return super(BlockItem, self).itemChange(change, value)
 
    def updateSize(self):
       rect = self.rect()
@@ -319,8 +332,9 @@
             outp.setPos(w, y)
             y += dy
       
-   def changeSize(self, w, h):
+   def changeSize(self, p):
       """ Resize block function """
+      w, h = p.x(), p.y()
       # Limit the block size:
       if h < 20:
          h = 20
@@ -335,12 +349,19 @@
       self.label.setPos(lx, ly)
       # Update port positions:
       self.updateSize()
-      return w, h
+      return QPointF(w, h)
 
 class EditorGraphicsView(QGraphicsView):
    def __init__(self, scene, parent=None):
       QGraphicsView.__init__(self, scene, parent)
       self.setDragMode(QGraphicsView.RubberBandDrag)
+   def wheelEvent(self, event):
+      pos = event.pos()
+      posbefore = self.mapToScene(pos)
+      degrees = event.delta() / 8.0
+      sx = (100.0 + degrees) / 100.0
+      self.scale(sx, sx)
+      event.accept()
    def dragEnterEvent(self, event):
       if event.mimeData().hasFormat('component/name'):
          event.accept()
@@ -371,6 +392,9 @@
    """ Save and load and deletion of item"""
    def __init__(self, parent=None):
       super(DiagramScene, self).__init__(parent)
+      #circle = QGraphicsEllipseItem(-10,-10,20,20)
+      #self.addItem(circle)
+
    def saveDiagram(self, filename):
       items = self.items()
       blocks = [item for item in items if type(item) is BlockItem]
@@ -466,6 +490,44 @@
    def mouseReleaseEvent(self, mouseEvent):
       editor.sceneMouseReleaseEvent(mouseEvent)
       super(DiagramScene, self).mouseReleaseEvent(mouseEvent)
+   def vias(self, P1, P2):
+      """ Constructs a list of points that must be connected
+          to go from P1 to P2 """
+      def stripHits(hits):
+         """ Helper function that removes object hits """
+         hits = [hit for hit in hits if type(hit) is BlockItem]
+         return hits
+
+      def viaPoints(pA, pB):
+         # Construct intermediate points:
+
+         pAB1 = QPointF(pA.x(), pB.y())
+         pAB2 = QPointF(pB.x(), pA.y())
+         path1 = QPainterPath(pA)
+         path1.lineTo(pAB1)
+         path2 = QPainterPath(pA)
+         path2.lineTo(pAB2)
+         if len(stripHits(self.items(path1))) > len(stripHits(self.items(path2))):
+            pAB = pAB2
+         else:
+            pAB = pAB1
+         return [pAB]
+
+      # Determine left or right:
+      dx = QPointF(20, 0)
+      if len(stripHits(self.items(P1 + dx))) > len(stripHits(self.items(P1 - dx))):
+         p2 = P1 - dx
+      else:
+         p2 = P1 + dx
+
+      if len(stripHits(self.items(P2 + dx))) > len(stripHits(self.items(P2 - dx))):
+         p3 = P2 - dx
+      else:
+         p3 = P2 + dx
+      
+      # If pathitem:
+      pts = [P1, p2] + viaPoints(p2, p3) + [p3, P2]
+      return pts
    def deleteItem(self, item=None):
       if item:
          if type(item) is BlockItem:
@@ -544,6 +606,8 @@
       self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
    def startConnection(self, port):
       self.startedConnection = Connection(port, None)
+      pos = port.scenePos()
+      self.startedConnection.setEndPos(pos)
       self.diagramScene.addItem(self.startedConnection)
    def zoomAll(self):
       """ zoom to fit all items """
@@ -563,7 +627,6 @@
                self.startedConnection = None
                return
          self.diagramScene.deleteItem(self.startedConnection)
-         del self.startedConnection
          self.startedConnection = None
 
 if __name__ == '__main__':