changeset 46:7964065400b7

Added xml save function
author windel
date Thu, 15 Mar 2012 20:30:13 +0100
parents 8a52263d67c4
children c4ec95a588ea
files applications/lab/diagrameditor.py
diffstat 1 files changed, 144 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/applications/lab/diagrameditor.py	Sat Feb 18 17:09:21 2012 +0100
+++ b/applications/lab/diagrameditor.py	Thu Mar 15 20:30:13 2012 +0100
@@ -1,9 +1,9 @@
 #!/usr/bin/python
 
-from PyQt4 import QtGui, QtCore
 from PyQt4.QtGui import *
 from PyQt4.QtCore import *
 import sys
+from elementtree.SimpleXMLWriter import XMLWriter
 
 """
   This script implements a basic diagram editor.
@@ -16,65 +16,110 @@
     - toPort
    """
    def __init__(self, fromPort, toPort):
-      self.fromPort = fromPort
       self.pos1 = None
       self.pos2 = None
-      self.p1dir = None
-      self.p2dir = None
+      self.pathItem = QGraphicsPathItem()
+      pen = QPen()
+      pen.setWidth(4)
+      pen.setColor(Qt.blue)
+      pen.setCapStyle(Qt.RoundCap)
+      self.pathItem.setPen(pen)
+      #self.pathItem.setFlags(self.pathItem.ItemIsSelectable)
+      editor.diagramScene.addItem(self.pathItem)
       self.setFromPort(fromPort)
-      self.toPort = toPort
-      # Create arrow item:
-      self.linePieces = []
+      self.setToPort(toPort)
    def setFromPort(self, fromPort):
       self.fromPort = fromPort
       if self.fromPort:
-         self.pos1 = fromPort.scenePos()
+         self.setBeginPos(fromPort.scenePos())
          self.fromPort.posCallbacks.append(self.setBeginPos)
    def setToPort(self, toPort):
       self.toPort = toPort
       if self.toPort:
-         self.pos2 = toPort.scenePos()
+         self.setEndPos(toPort.scenePos())
          self.toPort.posCallbacks.append(self.setEndPos)
    def setBeginPos(self, pos1):
       self.pos1 = pos1
-      if self.pos1 and self.pos2:
-         self.updateLineStukken()
+      self.updateLineStukken()
    def setEndPos(self, endpos):
       self.pos2 = endpos
-      if self.pos1 and self.pos2:
-         self.updateLineStukken()
+      self.updateLineStukken()
    def updateLineStukken(self):
       """
          This algorithm determines the optimal routing of all signals.
          TODO: implement nice automatic line router
       """
-      # TODO: create pieces of lines.
-
-      # Determine the current amount of linestukken:
-      x1, y1 = self.pos1.x(), self.pos1.y()
-      x2, y2 = self.pos2.x(), self.pos2.y()
+      if self.pos1 is None or self.pos2 is None:
+         return
 
       ds = editor.diagramScene
 
-      if y1 == y2 or x1 == x2:
-         pass
+      # 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()
+
+      def stripHits(hits):
+         """ Helper function that removes object hits """
+         hits = [hit for hit in hits if type(hit) is BlockItem]
+         if self.pathItem in hits:
+            hits.remove(self.pathItem)
+         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:
+
+         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(10, 0)
+      if len(stripHits(ds.items(p1 + dx))) > len(stripHits(ds.items(p1 - dx))):
+         p2 = p1 - dx
       else:
-         # We require two lijnstukken to make one corner!
-         while len(self.linePieces) < 2:
-            lp = LinePieceItem()
-            ds.addItem(lp)
-            self.linePieces.append(lp)
-         lp1 = self.linePieces[0]
-         lp2 = self.linePieces[1]
-         lp1.setLine(QLineF(x1, y1, x2, y1))
-         lp2.setLine(QLineF(x2, y1, x2, y2))
+         p2 = p1 + dx
+
+      if len(stripHits(ds.items(p4 + dx))) > len(stripHits(ds.items(p4 - dx))):
+         p3 = p4 - dx
+      else:
+         p3 = p4 + dx
+
+      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))
+
+      #print('Items:', hits) 
+      self.pathItem.setPath(path)
 
    def delete(self):
-      editor.diagramScene.removeItem(self.arrow)
+      editor.diagramScene.removeItem(self.pathItem)
       # Remove position update callbacks:
 
 class ParameterDialog(QDialog):
-   def __init__(self, parent=None):
+   def __init__(self, parent = None):
       super(ParameterDialog, self).__init__(parent)
       self.button = QPushButton('Ok', self)
       l = QVBoxLayout(self)
@@ -87,7 +132,7 @@
    """ Represents a port to a subsystem """
    def __init__(self, name, parent=None):
       QGraphicsEllipseItem.__init__(self, QRectF(-6,-6,12.0,12.0), parent)
-      self.setCursor(QCursor(QtCore.Qt.CrossCursor))
+      self.setCursor(QCursor(Qt.CrossCursor))
       # Properties:
       self.setBrush(QBrush(Qt.red))
       # Name:
@@ -96,7 +141,6 @@
       self.setFlag(self.ItemSendsScenePositionChanges, True)
    def itemChange(self, change, value):
       if change == self.ItemScenePositionHasChanged:
-         #value = value.toPointF() # Required in python2??!
          for cb in self.posCallbacks:
             cb(value)
          return value
@@ -110,10 +154,9 @@
    def __init__(self, parent=None):
       super(HandleItem, self).__init__(QRectF(-4.0,-4.0,8.0,8.0), parent)
       self.posChangeCallbacks = []
-      self.setBrush(QtGui.QBrush(Qt.white))
-      self.setFlag(self.ItemIsMovable, True)
-      self.setFlag(self.ItemSendsScenePositionChanges, True)
-      self.setCursor(QtGui.QCursor(Qt.SizeFDiagCursor))
+      self.setBrush(QBrush(Qt.white))
+      self.setFlags(self.ItemIsMovable | self.ItemSendsScenePositionChanges)
+      self.setCursor(QCursor(Qt.SizeFDiagCursor))
 
    def itemChange(self, change, value):
       if change == self.ItemPositionChange:
@@ -145,10 +188,10 @@
       w = 60.0
       h = 40.0
       # Properties of the rectangle:
-      self.setPen(QtGui.QPen(QtCore.Qt.blue, 2))
-      self.setBrush(QtGui.QBrush(QtCore.Qt.lightGray))
+      self.setPen(QPen(Qt.blue, 2))
+      self.setBrush(QBrush(Qt.lightGray))
       self.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
-      self.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
+      self.setCursor(QCursor(Qt.PointingHandCursor))
       # Label:
       self.label = QGraphicsTextItem(name, self)
       # Create corner for resize:
@@ -173,11 +216,16 @@
 
    def contextMenuEvent(self, event):
       menu = QMenu()
-      menu.addAction('Delete')
+      da = menu.addAction('Delete')
+      da.triggered.connect(self.delete)
       pa = menu.addAction('Parameters')
       pa.triggered.connect(self.editParameters)
       menu.exec_(event.screenPos())
 
+   def delete(self):
+      editor.diagramScene.removeItem(self)
+      # TODO: remove connections
+
    def changeSize(self, w, h):
       """ Resize block function """
       # Limit the block size:
@@ -211,14 +259,6 @@
             y += dy
       return w, h
 
-class LinePieceItem(QGraphicsLineItem):
-   def __init__(self):
-      super(LinePieceItem, self).__init__(None)
-      self.setPen(QtGui.QPen(QtCore.Qt.red,2))
-      self.setFlag(self.ItemIsSelectable, True)
-   def x(self):
-      pass
-
 class EditorGraphicsView(QGraphicsView):
    def __init__(self, scene, parent=None):
       QGraphicsView.__init__(self, scene, parent)
@@ -261,12 +301,12 @@
 
 class DiagramEditor(QWidget):
    def __init__(self, parent=None):
-      QtGui.QWidget.__init__(self, parent)
+      QWidget.__init__(self, parent)
       self.setWindowTitle("Diagram editor")
 
       # Widget layout and child widgets:
-      self.horizontalLayout = QtGui.QHBoxLayout(self)
-      self.libraryBrowserView = QtGui.QListView(self)
+      self.horizontalLayout = QHBoxLayout(self)
+      self.libraryBrowserView = QListView(self)
       self.libraryBrowserView.setMaximumWidth(160)
       self.libraryModel = LibraryModel(self)
       self.libraryModel.setColumnCount(1)
@@ -282,9 +322,9 @@
       painter.end()
 
       self.libItems = []
-      self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Block') )
-      self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Uber Unit') )
-      self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Device') )
+      self.libItems.append( QStandardItem(QIcon(pixmap), 'Block') )
+      self.libItems.append( QStandardItem(QIcon(pixmap), 'Uber Unit') )
+      self.libItems.append( QStandardItem(QIcon(pixmap), 'Device') )
       for i in self.libItems:
          self.libraryModel.appendRow(i)
       self.libraryBrowserView.setModel(self.libraryModel)
@@ -296,17 +336,52 @@
       self.horizontalLayout.addWidget(self.libraryBrowserView)
       self.horizontalLayout.addWidget(self.diagramView)
 
-      # Populate the diagram scene:
-      b1 = BlockItem('SubSystem1')
-      b1.setPos(50,100)
-      self.diagramScene.addItem(b1)
-      b2 = BlockItem('Unit2')
-      b2.setPos(-250,0)
-      self.diagramScene.addItem(b2)
-
       self.startedConnection = None
       fullScreenShortcut = QShortcut(QKeySequence("F11"), self)
       fullScreenShortcut.activated.connect(self.toggleFullScreen)
+      saveShortcut = QShortcut(QKeySequence(QKeySequence.Save), self)
+      saveShortcut.activated.connect(self.save)
+      self.loadDiagram('diagram.txt')
+
+   def save(self):
+      self.saveDiagram2('diagram2.txt')
+   def saveDiagram2(self, filename):
+      items = self.diagramScene.items()
+      blocks = [item for item in items if type(item) is BlockItem]
+      with open(filename, 'w') as f:
+         w = XMLWriter(f)
+         model = w.start('model')
+         for block in blocks:
+            w.element("block")
+
+         w.close(model)
+   def saveDiagram(self, filename):
+      items = self.diagramScene.items()
+      blocks = [item for item in items if type(item) is BlockItem]
+      with open(filename, 'w') as f:
+         for block in blocks:
+            rect = block.rect()
+            x, y, w, h = block.scenePos().x(), block.scenePos().y(), rect.width(), rect.height()
+            f.write('B({0};{1};{2};{3}) '.format(x,y,w,h)) # (x;y;w;h)
+         
+   def loadDiagram(self, filename):
+      try:
+         with open(filename, 'r') as f:
+            txt = f.read()
+      except IOError as e:
+         print('{0} not found'.format(filename))
+         return
+      parts = txt.split(' ')
+      for part in parts:
+         if len(part) < 1:
+            continue
+         if part[0] == 'B':
+            part = part[2:-1]
+            x,y,w,h = [float(val) for val in part.split(';')]
+            block = BlockItem('Loaded') # TODO: store name.
+            self.diagramScene.addItem(block)
+            block.setPos(x, y)
+            block.sizer.setPos(w, h)
    def toggleFullScreen(self):
       self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
    def startConnection(self, port):
@@ -318,13 +393,14 @@
    def sceneMouseReleaseEvent(self, event):
       # Clear the actual connection:
       if self.startedConnection:
-         pos = event.scenePos()
-         items = self.diagramScene.items(pos)
+         items = self.diagramScene.items(event.scenePos())
          for item in items:
             if type(item) is PortItem:
                self.startedConnection.setToPort(item)
-         if self.startedConnection.toPort == None:
-            self.startedConnection.delete()
+               self.startedConnection = None
+               return
+         self.startedConnection.delete()
+         del self.startedConnection
          self.startedConnection = None
 
 if __name__ == '__main__':
@@ -332,7 +408,7 @@
       print('Please use python 3.x')
       sys.exit(1)
 
-   app = QtGui.QApplication(sys.argv)
+   app = QApplication(sys.argv)
    global editor
    editor = DiagramEditor()
    editor.show()