diff applications/lab/diagrameditor.py @ 44:cbf199e007c2

Added some demo applications
author windel
date Sat, 18 Feb 2012 16:42:23 +0100
parents
children 8a52263d67c4
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/applications/lab/diagrameditor.py	Sat Feb 18 16:42:23 2012 +0100
@@ -0,0 +1,335 @@
+#!/usr/bin/python
+
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+import sys
+
+"""
+  This script implements a basic diagram editor.
+"""
+
+class Connection:
+   """
+    - fromPort
+    - list of line items in between
+    - toPort
+   """
+   def __init__(self, fromPort, toPort):
+      self.fromPort = fromPort
+      self.pos1 = None
+      self.pos2 = None
+      self.p1dir = None
+      self.p2dir = None
+      self.setFromPort(fromPort)
+      self.toPort = toPort
+      # Create arrow item:
+      self.linePieces = []
+   def setFromPort(self, fromPort):
+      self.fromPort = fromPort
+      if self.fromPort:
+         self.pos1 = fromPort.scenePos()
+         self.fromPort.posCallbacks.append(self.setBeginPos)
+   def setToPort(self, toPort):
+      self.toPort = toPort
+      if self.toPort:
+         self.pos2 = toPort.scenePos()
+         self.toPort.posCallbacks.append(self.setEndPos)
+   def setBeginPos(self, pos1):
+      self.pos1 = pos1
+      if self.pos1 and self.pos2:
+         self.updateLineStukken()
+   def setEndPos(self, endpos):
+      self.pos2 = endpos
+      if self.pos1 and self.pos2:
+         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()
+
+      ds = editor.diagramScene
+
+      if y1 == y2 or x1 == x2:
+         pass
+      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))
+
+   def delete(self):
+      editor.diagramScene.removeItem(self.arrow)
+      # Remove position update callbacks:
+
+class ParameterDialog(QDialog):
+   def __init__(self, parent=None):
+      super(ParameterDialog, self).__init__(parent)
+      self.button = QPushButton('Ok', self)
+      l = QVBoxLayout(self)
+      l.addWidget(self.button)
+      self.button.clicked.connect(self.OK)
+   def OK(self):
+      self.close()
+
+class PortItem(QGraphicsEllipseItem):
+   """ 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))
+      # Properties:
+      self.setBrush(QBrush(Qt.red))
+      # Name:
+      self.name = name
+      self.posCallbacks = []
+      self.setFlag(self.ItemSendsScenePositionChanges, True)
+   def itemChange(self, change, value):
+      if change == self.ItemScenePositionHasChanged:
+         value = value.toPointF() # Required!
+         for cb in self.posCallbacks:
+            cb(value)
+         return value
+      return super(PortItem, self).itemChange(change, value)
+   def mousePressEvent(self, event):
+      editor.startConnection(self)
+
+# Block part:
+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)
+      self.posChangeCallbacks = []
+      self.setBrush(QtGui.QBrush(Qt.white))
+      self.setFlag(self.ItemIsMovable, True)
+      self.setFlag(self.ItemSendsScenePositionChanges, True)
+      self.setCursor(QtGui.QCursor(Qt.SizeFDiagCursor))
+
+   def itemChange(self, change, value):
+      if change == self.ItemPositionChange:
+         value = value.toPointF()
+         x, y = value.x(), value.y()
+         # TODO: make this a signal?
+         # This cannot be a signal because this is not a QObject
+         for cb in self.posChangeCallbacks:
+            res = cb(x, y)
+            if res:
+               x, y = res
+               value = QPointF(x, y)
+         return value
+      # Call superclass method:
+      return super(HandleItem, self).itemChange(change, value)
+
+class BlockItem(QGraphicsRectItem):
+   """ 
+      Represents a block in the diagram
+      Has an x and y and width and height
+      width and height can only be adjusted with a tip in the lower right corner.
+
+      - in and output ports
+      - parameters
+      - description
+   """
+   def __init__(self, name='Untitled', parent=None):
+      super(BlockItem, self).__init__(parent)
+      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.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
+      self.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
+      # Label:
+      self.label = QGraphicsTextItem(name, self)
+      # Create corner for resize:
+      self.sizer = HandleItem(self)
+      self.sizer.setPos(w, h)
+      self.sizer.posChangeCallbacks.append(self.changeSize) # Connect the callback
+      #self.sizer.setVisible(False)
+      self.sizer.setFlag(self.sizer.ItemIsSelectable, True)
+
+      # Inputs and outputs of the block:
+      self.inputs = []
+      self.inputs.append( PortItem('a', self) )
+      self.inputs.append( PortItem('b', self) )
+      self.inputs.append( PortItem('c', self) )
+      self.outputs = []
+      self.outputs.append( PortItem('y', self) )
+      # Update size:
+      self.changeSize(w, h)
+   def editParameters(self):
+      pd = ParameterDialog(self.window())
+      pd.exec_()
+
+   def contextMenuEvent(self, event):
+      menu = QMenu()
+      menu.addAction('Delete')
+      pa = menu.addAction('Parameters')
+      pa.triggered.connect(self.editParameters)
+      menu.exec_(event.screenPos())
+
+   def changeSize(self, w, h):
+      """ Resize block function """
+      # Limit the block size:
+      if h < 20:
+         h = 20
+      if w < 40:
+         w = 40
+      self.setRect(0.0, 0.0, w, h)
+      # center label:
+      rect = self.label.boundingRect()
+      lw, lh = rect.width(), rect.height()
+      lx = (w - lw) / 2
+      ly = (h - lh) / 2
+      self.label.setPos(lx, ly)
+      # Update port positions:
+      if len(self.inputs) == 1:
+         self.inputs[0].setPos(-4, h / 2)
+      elif len(self.inputs) > 1:
+         y = 5
+         dy = (h - 10) / (len(self.inputs) - 1)
+         for inp in self.inputs:
+            inp.setPos(-4, y)
+            y += dy
+      if len(self.outputs) == 1:
+         self.outputs[0].setPos(w+4, h / 2)
+      elif len(self.outputs) > 1:
+         y = 5
+         dy = (h - 10) / (len(self.outputs) + 0)
+         for outp in self.outputs:
+            outp.setPos(w+4, y)
+            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)
+   def dragEnterEvent(self, event):
+      if event.mimeData().hasFormat('component/name'):
+         event.accept()
+   def dragMoveEvent(self, event):
+      if event.mimeData().hasFormat('component/name'):
+         event.accept()
+   def dropEvent(self, event):
+      if event.mimeData().hasFormat('component/name'):
+         name = str(event.mimeData().data('component/name'))
+         b1 = BlockItem(name)
+         b1.setPos(self.mapToScene(event.pos()))
+         self.scene().addItem(b1)
+
+class LibraryModel(QStandardItemModel):
+   def __init__(self, parent=None):
+      QStandardItemModel.__init__(self, parent)
+   def mimeTypes(self):
+      return ['component/name']
+   def mimeData(self, idxs):
+      mimedata = QMimeData()
+      for idx in idxs:
+         if idx.isValid():
+            txt = self.data(idx, Qt.DisplayRole).toByteArray()
+            mimedata.setData('component/name', txt)
+      return mimedata
+
+class DiagramScene(QGraphicsScene):
+   def __init__(self, parent=None):
+      super(DiagramScene, self).__init__(parent)
+   def mouseMoveEvent(self, mouseEvent):
+      editor.sceneMouseMoveEvent(mouseEvent)
+      super(DiagramScene, self).mouseMoveEvent(mouseEvent)
+   def mouseReleaseEvent(self, mouseEvent):
+      editor.sceneMouseReleaseEvent(mouseEvent)
+      super(DiagramScene, self).mouseReleaseEvent(mouseEvent)
+
+class DiagramEditor(QWidget):
+   def __init__(self, parent=None):
+      QtGui.QWidget.__init__(self, parent)
+      self.setWindowTitle("Diagram editor")
+
+      # Widget layout and child widgets:
+      self.horizontalLayout = QtGui.QHBoxLayout(self)
+      self.libraryBrowserView = QtGui.QListView(self)
+      self.libraryModel = LibraryModel(self)
+      self.libraryModel.setColumnCount(1)
+      # Create an icon with an icon:
+      pixmap = QPixmap(60, 60)
+      pixmap.fill()
+      painter = QPainter(pixmap)
+      painter.fillRect(10, 10, 40, 40, Qt.blue)
+      painter.setBrush(Qt.red)
+      painter.drawEllipse(36, 2, 20, 20)
+      painter.setBrush(Qt.yellow)
+      painter.drawEllipse(20, 20, 20, 20)
+      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') )
+      for i in self.libItems:
+         self.libraryModel.appendRow(i)
+      self.libraryBrowserView.setModel(self.libraryModel)
+      self.libraryBrowserView.setViewMode(self.libraryBrowserView.IconMode)
+      self.libraryBrowserView.setDragDropMode(self.libraryBrowserView.DragOnly)
+
+      self.diagramScene = DiagramScene(self)
+      self.diagramView = EditorGraphicsView(self.diagramScene, self)
+      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)
+   def toggleFullScreen(self):
+      self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
+   def startConnection(self, port):
+      self.startedConnection = Connection(port, None)
+   def sceneMouseMoveEvent(self, event):
+      if self.startedConnection:
+         pos = event.scenePos()
+         self.startedConnection.setEndPos(pos)
+   def sceneMouseReleaseEvent(self, event):
+      # Clear the actual connection:
+      if self.startedConnection:
+         pos = event.scenePos()
+         items = self.diagramScene.items(pos)
+         for item in items:
+            if type(item) is PortItem:
+               self.startedConnection.setToPort(item)
+         if self.startedConnection.toPort == None:
+            self.startedConnection.delete()
+         self.startedConnection = None
+
+if __name__ == '__main__':
+   app = QtGui.QApplication(sys.argv)
+   global editor
+   editor = DiagramEditor()
+   editor.show()
+   editor.resize(700, 800)
+   app.exec_()
+