diff python/apps/diagrameditor.py @ 63:32078200cdd6

Several move action
author windel
date Sun, 07 Oct 2012 17:04:10 +0200
parents python/lab/diagrameditor.py@fd7d5069734e
children b01311fb3be7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/apps/diagrameditor.py	Sun Oct 07 17:04:10 2012 +0200
@@ -0,0 +1,653 @@
+#!/usr/bin/python
+
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+import sys
+import xml.dom.minidom as md
+import xml
+
+"""
+ Author: Windel Bouwman
+ Year: 2012
+ Description: This script implements a diagram editor.
+ run with python 3.x as:
+  $ 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()))
+      self.myshape = QPainterPath()
+
+class Connection(QGraphicsPathItem):
+   """ Implementation of a connection between blocks """
+   def __init__(self, fromPort, toPort):
+      super(Connection, self).__init__()
+      self.pos1 = None
+      self.pos2 = None
+      self.fromPort = None
+      self.toPort = None
+      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.vias = []
+      self.setFromPort(fromPort)
+      self.setToPort(toPort)
+   def mouseDoubleClickEvent(self, event):
+      pos = event.scenePos()
+      pts = [self.pos1] + [v.pos() for v in self.vias] + [self.pos2]
+      idx = 0
+      tidx = 0
+      for p1, p2 in zip(pts[0:-1], pts[1:]):
+         l1 = QLineF(p1, p2)
+         l2 = QLineF(p1, pos)
+         l3 = QLineF(pos, p2)
+         d = l2.length() + l3.length() - l1.length()
+         if d < 5:
+            tidx = idx
+         idx += 1
+      self.addHandle(pos, tidx)
+      
+   def addHandle(self, pos, idx=None):
+      hi = HandleItem(self)
+      if idx:
+         self.vias.insert(idx, hi)
+      else:
+         self.vias.append(hi)
+      def callback(p):
+         self.updateLineStukken()
+         return p
+      hi.posChangeCallbacks.append(callback)
+      hi.setPos(pos)
+      self.updateLineStukken()
+      
+   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.setBeginPos(fromPort.scenePos())
+         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 releasePorts(self):
+      self.setFromPort(None)
+      self.setToPort(None)
+   def setBeginPos(self, pos1):
+      self.pos1 = 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
+      """
+      if self.pos1 is None or self.pos2 is None:
+         return
+      pts = [self.pos1] + [v.pos() for v in self.vias] + [self.pos2]
+      self.arrowhead.setPos(self.pos2)
+      if pts[-1].x() < pts[-2].x():
+         self.arrowhead.setRotation(-90)
+      else:
+         self.arrowhead.setRotation(90)
+      path = QPainterPath(pts[0])
+      for pt in pts[1:]:
+         path.lineTo(pt)
+      self.setPath(path)
+      """ 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)
+      self.block = block
+      self.button = QPushButton('Ok', self)
+      l = QGridLayout(self)
+      l.addWidget(QLabel('Name:', self), 0, 0)
+      self.nameEdit = QLineEdit(self.block.name)
+      l.addWidget(self.nameEdit, 0, 1)
+      l.addWidget(QLabel('Code:', self), 1, 0)
+      self.codeEdit = QTextEdit(self)
+      self.codeEdit.setPlainText(self.block.code)
+      l.addWidget(self.codeEdit, 1, 1)
+      l.addWidget(self.button, 2, 0, 1, 2)
+      self.button.clicked.connect(self.OK)
+   def OK(self):
+      self.block.setName(self.nameEdit.text())
+      self.block.code = self.codeEdit.toPlainText()
+      self.close()
+
+class PortItem(QGraphicsPathItem):
+   """ Represents a port to a subsystem """
+   def __init__(self, name, block, direction):
+      super(PortItem, self).__init__(block)
+      self.connection = None
+      path = QPainterPath()
+      d = 10.0
+      if direction == 'input':
+         path.moveTo(-d, -d)
+         path.lineTo(0.0, 0.0)
+         path.lineTo(-d, d)
+      else:
+         path.moveTo(0.0, -d)
+         path.lineTo(d, 0.0)
+         path.lineTo(0.0, d)
+      self.setPath(path)
+      self.direction = direction
+      self.block = block
+      self.setCursor(QCursor(Qt.CrossCursor))
+      pen = QPen(Qt.blue, 2)
+      pen.setCapStyle(Qt.RoundCap)
+      self.setPen(pen)
+      self.name = name
+      self.textItem = QGraphicsTextItem(name, self)
+      self.setName(name)
+      self.posCallbacks = []
+      self.setFlag(self.ItemSendsScenePositionChanges, True)
+   def setName(self, name):
+      self.name = name
+      self.textItem.setPlainText(name)
+      rect = self.textItem.boundingRect()
+      lw, lh = rect.width(), rect.height()
+      if self.direction == 'input':
+         lx = 3
+      else:
+         lx = -3 - lw
+      self.textItem.setPos(lx, -lh / 2)
+   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)
+   def mousePressEvent(self, event):
+      if self.direction == 'output':
+         self.scene().startConnection(self)
+
+class OutputPort(PortItem):
+   # TODO: create a subclass OR make a member porttype
+   pass
+
+# Block part:
+class HandleItem(QGraphicsEllipseItem):
+   """ A handle that can be moved by the mouse """
+   def __init__(self, parent=None):
+      dx = 13.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.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 mySetPos(self, p):
+      # TODO: use this instead of itemChange?
+      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
+      # Call superclass method:
+      return super(HandleItem, self).itemChange(change, value)
+
+def uniqify(name, names):
+   newname = name
+   i = 1
+   while newname in names:
+      newname = name + str(i)
+      i += 1
+   return newname
+
+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)
+      # Properties of the rectangle:
+      self.setPen(QPen(Qt.blue, 2))
+      self.setBrush(QBrush(Qt.lightGray))
+      self.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
+      self.setFlag(self.ItemSendsScenePositionChanges, True)
+      self.setCursor(QCursor(Qt.PointingHandCursor))
+      self.label = QGraphicsTextItem(name, self)
+      self.name = name
+      self.code = ''
+      # Create corner for resize:
+      self.sizer = HandleItem(self)
+      self.sizer.posChangeCallbacks.append(self.changeSize) # Connect the callback
+      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 = []
+      # Update size:
+      self.sizer.mySetPos(QPointF(60, 40)) # This is a better resize function
+   def editParameters(self):
+      pd = ParameterDialog(self, self.window())
+      pd.exec_()
+   def mouseDoubleClickEvent(self, event):
+      self.editParameters()
+   def newInputPort(self):
+      names = [i.name for i in self.inputs + self.outputs]
+      self.addInput(PortItem(uniqify('in', names), self, 'input'))
+   def newOutputPort(self):
+      names = [i.name for i in self.inputs + self.outputs]
+      self.addOutput(PortItem(uniqify('out', names), self, 'output'))
+   def setName(self, name):
+      self.name = name
+      self.label.setPlainText(name)
+   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:
+         self.sizer.setVisible(value)
+         self.buttonItemAddInput.setVisible(value)
+         self.buttonItemAddOutput.setVisible(value)
+      return super(BlockItem, self).itemChange(change, value)
+
+   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)
+      if len(self.inputs) == 1:
+         self.inputs[0].setPos(0.0, h / 2)
+      elif len(self.inputs) > 1:
+         y = 15
+         dy = (h - 30) / (len(self.inputs) - 1)
+         for inp in self.inputs:
+            inp.setPos(0.0, y)
+            y += dy
+      if len(self.outputs) == 1:
+         self.outputs[0].setPos(w, h / 2)
+      elif len(self.outputs) > 1:
+         y = 15
+         dy = (h - 30) / (len(self.outputs) - 1)
+         for outp in self.outputs:
+            outp.setPos(w, y)
+            y += dy
+      
+   def changeSize(self, p):
+      """ Resize block function """
+      w, h = p.x(), p.y()
+      # 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:
+      self.updateSize()
+      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()
+   def dragMoveEvent(self, event):
+      if event.mimeData().hasFormat('component/name'):
+         event.accept()
+   def dropEvent(self, event):
+      if event.mimeData().hasFormat('component/name'):
+         name = bytes(event.mimeData().data('component/name')).decode()
+         pos = self.mapToScene(event.pos())
+         self.scene().addNewBlock(pos, name)
+
+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) # python 3
+            mimedata.setData('component/name', txt)
+      return mimedata
+
+class DiagramScene(QGraphicsScene):
+   """ Save and load and deletion of item"""
+   def __init__(self, parent=None):
+      super(DiagramScene, self).__init__(parent)
+      self.startedConnection = None
+
+   def saveDiagram(self, filename):
+      items = self.items()
+      blocks = [item for item in items if type(item) is BlockItem]
+      connections = [item for item in items if type(item) is Connection]
+
+      doc = md.Document()
+      modelElement = doc.createElement('system')
+      doc.appendChild(modelElement)
+      for block in blocks:
+         blockElement = doc.createElement("block")
+         x, y = block.scenePos().x(), block.scenePos().y()
+         rect = block.rect()
+         w, h = rect.width(), rect.height()
+         blockElement.setAttribute("name", block.name)
+         blockElement.setAttribute("x", str(int(x)))
+         blockElement.setAttribute("y", str(int(y)))
+         blockElement.setAttribute("width", str(int(w)))
+         blockElement.setAttribute("height", str(int(h)))
+         codeNode = doc.createCDATASection(block.code)
+         codeElement = doc.createElement('code')
+         codeElement.appendChild(codeNode)
+         blockElement.appendChild(codeElement)
+         for inp in block.inputs:
+            portElement = doc.createElement("input")
+            portElement.setAttribute("name", inp.name)
+            blockElement.appendChild(portElement)
+         for outp in block.outputs:
+            portElement = doc.createElement("output")
+            portElement.setAttribute("name", outp.name)
+            blockElement.appendChild(portElement)
+         modelElement.appendChild(blockElement)
+      for connection in connections:
+         connectionElement = doc.createElement("connection")
+         fromPort = connection.fromPort.name
+         toPort = connection.toPort.name
+         fromBlock = connection.fromPort.block.name
+         toBlock = connection.toPort.block.name
+         connectionElement.setAttribute("fromBlock", fromBlock)
+         connectionElement.setAttribute("fromPort", fromPort)
+         connectionElement.setAttribute("toBlock", toBlock)
+         connectionElement.setAttribute("toPort", toPort)
+         for via in connection.vias:
+            viaElement = doc.createElement('via')
+            viaElement.setAttribute('x', str(int(via.x())))
+            viaElement.setAttribute('y', str(int(via.y())))
+            connectionElement.appendChild(viaElement)
+         modelElement.appendChild(connectionElement)
+      with open(filename, 'w') as f:
+         f.write(doc.toprettyxml())
+         
+   def loadDiagram(self, filename):
+      try:
+         doc = md.parse(filename)
+      except IOError as e:
+         print('{0} not found'.format(filename))
+         return
+      except xml.parsers.expat.ExpatError as e:
+         print('{0}'.format(e))
+         return
+      sysElements = doc.getElementsByTagName('system')
+      blockElements = doc.getElementsByTagName('block')
+      for sysElement in sysElements:
+         blockElements = sysElement.getElementsByTagName('block')
+         for blockElement in blockElements:
+            x = float(blockElement.getAttribute('x'))
+            y = float(blockElement.getAttribute('y'))
+            w = float(blockElement.getAttribute('width'))
+            h = float(blockElement.getAttribute('height'))
+            name = blockElement.getAttribute('name')
+            block = BlockItem(name)
+            self.addItem(block)
+            block.setPos(x, y)
+            block.sizer.setPos(w, h)
+            codeElements = blockElement.getElementsByTagName('code')
+            if codeElements:
+               cn = codeElements[0].childNodes
+               cdatas = [cd for cd in cn if type(cd) is md.CDATASection]
+               if len(cdatas) > 0:
+                  block.code = cdatas[0].data
+            # Load ports:
+            portElements = blockElement.getElementsByTagName('input')
+            for portElement in portElements:
+               name = portElement.getAttribute('name')
+               inp = PortItem(name, block, 'input')
+               block.addInput(inp)
+            portElements = blockElement.getElementsByTagName('output')
+            for portElement in portElements:
+               name = portElement.getAttribute('name')
+               outp = PortItem(name, block, 'output')
+               block.addOutput(outp)
+         connectionElements = sysElement.getElementsByTagName('connection')
+         for connectionElement in connectionElements:
+            fromBlock = connectionElement.getAttribute('fromBlock')
+            fromPort = connectionElement.getAttribute('fromPort')
+            toBlock = connectionElement.getAttribute('toBlock')
+            toPort = connectionElement.getAttribute('toPort')
+            viaElements = connectionElement.getElementsByTagName('via')
+            fromPort = self.findPort(fromBlock, fromPort)
+            toPort = self.findPort(toBlock, toPort)
+            connection = Connection(fromPort, toPort)
+            for viaElement in viaElements:
+               x = int(viaElement.getAttribute('x'))
+               y = int(viaElement.getAttribute('y'))
+               connection.addHandle(QPointF(x, y))
+            self.addItem(connection)
+   def findPort(self, blockname, portname):
+      items = self.items()
+      blocks = [item for item in items if type(item) is BlockItem]
+      for block in [b for b in blocks if b.name == blockname]:
+         for port in block.inputs + block.outputs:
+            if port.name == portname:
+               return port
+   def addNewBlock(self, pos, name):
+      blocknames = [item.name for item in self.items() if type(item) is BlockItem]
+      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:
+         items = self.items(event.scenePos())
+         for item in items:
+            if type(item) is PortItem:
+               self.startedConnection.setToPort(item)
+               self.startedConnection = None
+               return
+         self.deleteItem(self.startedConnection)
+         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 deleteItem(self, item=None):
+      if item:
+         if type(item) is BlockItem:
+            for p in item.inputs + item.outputs:
+               if p.connection:
+                  self.deleteItem(p.connection)
+            self.removeItem(item)
+         elif type(item) is Connection:
+            item.releasePorts()
+            self.removeItem(item)
+      else:
+         # No item was supplied, try to delete all currently selected items:
+         items = self.selectedItems()
+         connections = [item for item in items if type(item) is Connection]
+         blocks = [item for item in items if type(item) is BlockItem]
+         for item in connections + blocks:
+            self.deleteItem(item)
+
+class DiagramEditor(QWidget):
+   def __init__(self, parent=None):
+      QWidget.__init__(self, parent)
+
+      # Widget layout and child widgets:
+      self.horizontalLayout = QHBoxLayout(self)
+      self.diagramScene = DiagramScene(self)
+      self.loadDiagram = self.diagramScene.loadDiagram
+      self.diagramView = EditorGraphicsView(self.diagramScene, self)
+      self.horizontalLayout.addWidget(self.diagramView)
+
+      testShortcut = QShortcut(QKeySequence("F12"), self)
+      testShortcut.activated.connect(self.test)
+      delShort = QShortcut(QKeySequence.Delete, self)
+      delShort.activated.connect(self.diagramScene.deleteItem)
+
+   def test(self):
+      self.diagramView.rotate(30)
+      self.zoomAll()
+   def save(self):
+      self.diagramScene.saveDiagram('diagram2.usd')
+   def load(self):
+      filename = QFileDialog.getOpenFileName(self)
+      self.diagramScene.loadDiagram(filename)
+   def zoomAll(self):
+      """ zoom to fit all items """
+      rect = self.diagramScene.itemsBoundingRect()
+      self.diagramView.fitInView(rect, Qt.KeepAspectRatio)
+
+class LibraryWidget(QListView):
+   def __init__(self):
+      super(LibraryWidget, self).__init__(None)
+      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.yellow)
+      painter.drawEllipse(20, 20, 20, 20)
+      painter.end()
+
+      # Fill library:
+      self.libItems = []
+      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.setModel(self.libraryModel)
+      self.setViewMode(self.IconMode)
+      self.setDragDropMode(self.DragOnly)
+
+class Main(QMainWindow):
+   def __init__(self):
+      super(Main, self).__init__(None)
+      self.editor = DiagramEditor()
+      self.setCentralWidget(self.editor)
+      self.setWindowTitle("Diagram editor")
+      toolbar = self.addToolBar('Tools')
+
+      saveAction = QAction('Save', self)
+      saveAction.setShortcuts(QKeySequence.Save)
+      saveAction.triggered.connect(self.editor.save)
+      toolbar.addAction(saveAction)
+      openAction = QAction('Open', self)
+      openAction.setShortcuts(QKeySequence.Open)
+      openAction.triggered.connect(self.editor.load)
+      toolbar.addAction(openAction)
+      fullScreenAction = QAction('Full screen', self)
+      fullScreenAction.setShortcuts(QKeySequence("F11"))
+      fullScreenAction.triggered.connect(self.toggleFullScreen)
+      toolbar.addAction(fullScreenAction)
+      zoomAction = QAction('Fit in view', self)
+      zoomAction.setShortcuts(QKeySequence('F8'))
+      zoomAction.triggered.connect(self.editor.zoomAll)
+      toolbar.addAction(zoomAction)
+
+      self.library = LibraryWidget() 
+      libraryDock = QDockWidget('Library', self)
+      libraryDock.setWidget(self.library)
+      self.addDockWidget(Qt.LeftDockWidgetArea, libraryDock)
+
+      self.editor.loadDiagram('diagram2.usd')
+
+   def toggleFullScreen(self):
+      self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
+      self.editor.zoomAll()
+
+if __name__ == '__main__':
+   if sys.version_info.major != 3:
+      print('Please use python 3.x')
+      sys.exit(1)
+
+   app = QApplication(sys.argv)
+   main = Main()
+   main.show()
+   main.resize(700, 500)
+   main.editor.zoomAll()
+   app.exec_()
+