diff python/other/diagrameditor.py @ 290:7b38782ed496

File moves
author Windel Bouwman
date Sun, 24 Nov 2013 11:24:15 +0100
parents python/diagrameditor.py@6efbeb903777
children b77f3290ac79
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/other/diagrameditor.py	Sun Nov 24 11:24:15 2013 +0100
@@ -0,0 +1,316 @@
+#!/usr/bin/python
+
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+import sys, json, base64
+
+from diagramitems import Connection, ResizeSelectionHandle, Block, DiagramScene, CodeBlock
+from icons import newicon, saveicon, loadicon
+import diagramitems
+
+"""
+ Author: Windel Bouwman
+ Year: 2012
+ Description: This script implements a diagram editor.
+ run with python 3.x as:
+  $ python [thisfile.py]
+"""
+def indent(lines):
+   return ['   ' + line for line in lines]
+
+class ParameterDialog(QDialog):
+   def __init__(self, block, parent = None):
+      super(ParameterDialog, self).__init__(parent)
+      self.block = block
+      self.button = QPushButton('Ok', self)
+      self.nameEdit = QLineEdit(self.block.name)
+      self.codeEdit = QTextEdit(self)
+      self.codeEdit.setPlainText(self.block.code)
+      l = QFormLayout(self)
+      l.addRow('Name:', self.nameEdit)
+      l.addRow('Code:', self.codeEdit)
+      l.addWidget(self.button)
+      self.button.clicked.connect(self.OK)
+   def OK(self):
+      self.block.name = self.nameEdit.text()
+      self.block.code = self.codeEdit.toPlainText()
+      self.close()
+
+class EditorGraphicsView(QGraphicsView):
+   def __init__(self, parent=None):
+      QGraphicsView.__init__(self, parent)
+      self.setDragMode(QGraphicsView.RubberBandDrag)
+      self.delShort = QShortcut(QKeySequence.Delete, self)
+      self._model = None
+      self.treeView = QTreeView()
+      self.treeView.clicked.connect(self.itemActivated)
+   def itemActivated(self, idx):
+      b = idx.internalPointer()
+      s = b.scene()
+      s.clearSelection()
+      b.setSelected(True)
+   def setDiagram(self, d):
+      self.setScene(d)
+      self.delShort.activated.connect(d.deleteItems)
+   def getModel(self): 
+      return self._model
+   def setModel(self, m):
+      self._model = m
+      if m:
+         self.treeView.setModel(m)
+         self.diagram = m.rootDiagram
+         self.model.modelReset.connect(self.treeView.expandAll)
+   model = property(getModel, setModel)
+   diagram = property(lambda s: s.scene(), setDiagram)
+   def save(self):
+      if self.model:
+         if not self.model.filename:
+            self.model.filename = QFileDialog.getSaveFileName(self)
+         if self.model.filename:
+            with open(self.model.filename, 'w') as f:
+               f.write(json.dumps(self.model.Dict, indent=2))
+   def load(self):
+      filename = QFileDialog.getOpenFileName(self)
+      if filename:
+         self.model = loadModel(filename)
+   def newModel(self):
+      self.model = ModelHierarchyModel()
+   def goUp(self):
+      if hasattr(self.diagram, 'containingBlock'):
+         self.diagram = self.diagram.containingBlock.scene()
+         self.zoomAll()
+   def showCode(self):
+      if self.model:
+         c = self.model.gencode()
+         c = '\n'.join(c)
+         d = QDialog()
+         l = QFormLayout(d)
+         codeview = QTextEdit()
+         codeview.setPlainText(c)
+         l.addRow('code', codeview)
+         runButton = QPushButton('Run')
+         outputview = QTextEdit()
+         l.addRow('Output', outputview)
+         l.addWidget(runButton)
+         def print2(txt):
+            txt2 = outputview.toPlainText()
+            outputview.setPlainText(txt2 + '\n' + txt)
+         def runIt():
+            outputview.clear()
+            globs = {'print': print2}
+            exec(codeview.toPlainText(), globs)
+         runButton.clicked.connect(runIt)
+         d.exec_()
+   def zoomAll(self):
+      """ zoom to fit all items """
+      rect = self.diagram.itemsBoundingRect()
+      self.fitInView(rect, Qt.KeepAspectRatio)
+   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()
+         kind, name = name.split(':')
+         pos = self.mapToScene(event.pos())
+         s = self.scene()
+         if not s:
+            return
+         print(kind, 'name:', name)
+         kind = getattr(diagramitems, kind)
+         print(kind)
+         b = kind(s.uniqify(name))
+         b.setPos(pos)
+         s.addItem(b)
+
+class LibraryModel(QStandardItemModel):
+   mimeTypes = lambda self: ['component/name']
+   def mimeData(self, idxs):
+      mimedata = QMimeData()
+      for idx in idxs:
+         if idx.isValid():
+            txt = self.data(idx, Qt.DisplayRole)
+            mimedata.setData('component/name', txt)
+      return mimedata
+
+class ModelHierarchyModel(QAbstractItemModel):
+   def __init__(self):
+      super(ModelHierarchyModel, self).__init__()
+      self.rootDiagram = DiagramScene()
+      self.rootDiagram.structureChanged.connect(self.handlechange)
+      self.filename = None
+   def handlechange(self):
+      self.modelReset.emit()
+   def setDict(self, d):
+      self.rootDiagram.Dict = d
+      self.modelReset.emit()
+   def getDict(self):
+      return self.rootDiagram.Dict
+   Dict = property(getDict, setDict)
+   def gencode(self):
+      c = ['def topLevel():']
+      c += indent(self.rootDiagram.gencode())
+      c.append('print("Running model")')
+      c.append('topLevel()')
+      c.append('print("Done")')
+      return c
+   def index(self, row, column, parent=None):
+      if parent.isValid():
+         parent = parent.internalPointer().subModel
+      else:
+         parent = self.rootDiagram
+      blocks = sorted(parent.blocks, key=lambda b:  b.name)
+      block = blocks[row]
+      # Store the index to retrieve it later in the parent function.
+      # TODO: solve this in a better way.
+      block.index = self.createIndex(row, column, block)
+      return block.index
+   def parent(self, index):
+      if index.isValid():
+         block = index.internalPointer()
+         if block.scene() == self.rootDiagram:
+            return QModelIndex()
+         else:
+            print(block)
+            outerBlock = block.scene().containingBlock
+            return outerBlock.index
+      print('parent: No valid index')
+   def data(self, index, role):
+      if index.isValid() and role == Qt.DisplayRole:
+         b = index.internalPointer()
+         if index.column() == 0:
+            return b.name
+         elif index.column() == 1:
+            return str(type(b))
+   def headerData(self, section, orientation, role):
+      if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+         if section == 0:
+            return "Element"
+         elif section == 1:
+            return "Type"
+         else:
+            return "x"
+   def rowCount(self, parent):
+      if parent.column() > 0: 
+         return 0
+      if parent.isValid():
+         block = parent.internalPointer()
+         if hasattr(block, 'subModel'):
+            return len(block.subModel.blocks)
+         else:
+            return 0
+      else:
+         return len(self.rootDiagram.blocks)
+   def columnCount(self, parent):
+      return 2
+
+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:
+      for name in ['CodeBlock:codeBlock', 'DiagramBlock:submod', 'Block:blk']:
+         self.libraryModel.appendRow(QStandardItem(QIcon(pixmap), name))
+      self.setModel(self.libraryModel)
+      self.setViewMode(self.IconMode)
+      self.setDragDropMode(self.DragOnly)
+
+def warning(txt):
+   QMessageBox.warning(None, "Warning", txt)
+
+def loadModel(filename):
+   try:
+      m = ModelHierarchyModel()
+      with open(filename, 'r') as f: data = f.read()
+      m.filename = filename
+      m.Dict = json.loads(data)
+      return m
+   except KeyError:
+      warning('Corrupt model: {0}'.format(filename))
+   except ValueError:
+      warning('Corrupt model: {0}'.format(filename))
+   except FileNotFoundError:
+      warning('File [{0}] not found'.format(filename))
+
+class Main(QMainWindow):
+   def __init__(self):
+      super(Main, self).__init__(None)
+      self.editor = EditorGraphicsView()
+      self.setCentralWidget(self.editor)
+      self.setWindowTitle("Diagram editor")
+      def buildIcon(b64):
+         icon = base64.decodestring(b64)
+         pm = QPixmap()
+         pm.loadFromData(icon)
+         return QIcon(pm)
+      toolbar = self.addToolBar('Tools')
+      toolbar.setObjectName('Tools')
+      def act(name, shortcut, callback, icon=None):
+         a = QAction(icon, name, self) if icon else QAction(name, self)
+         a.setShortcuts(shortcut)
+         a.triggered.connect(callback)
+         toolbar.addAction(a)
+      act('New', QKeySequence.New, self.editor.newModel, buildIcon(newicon))
+      act('Save', QKeySequence.Save, self.editor.save, buildIcon(saveicon))
+      act('Load', QKeySequence.Open, self.editor.load, buildIcon(loadicon))
+      act('Full screen', QKeySequence("F11"), self.toggleFullScreen)
+      act('Fit in view', QKeySequence("F8"), self.editor.zoomAll)
+      act('Go up', QKeySequence(Qt.Key_Up), self.editor.goUp)
+      act('Model code', QKeySequence("F7"), self.editor.showCode)
+      def addDock(name, widget):
+         dock = QDockWidget(name, self)
+         dock.setObjectName(name)
+         dock.setWidget(widget)
+         self.addDockWidget(Qt.LeftDockWidgetArea, dock)
+      addDock('Library', LibraryWidget())
+      addDock('Model tree', self.editor.treeView)
+      self.settings = QSettings('windelsoft', 'diagrameditor')
+      self.loadSettings()
+   def toggleFullScreen(self):
+      self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
+      self.editor.zoomAll()
+   def loadSettings(self):
+      if self.settings.contains('mainwindowstate'):
+         self.restoreState(self.settings.value('mainwindowstate'))
+      if self.settings.contains('mainwindowgeometry'):
+         self.restoreGeometry(self.settings.value('mainwindowgeometry'))
+      if self.settings.contains('openedmodel'):
+         modelfile = self.settings.value('openedmodel')
+         self.editor.model = loadModel(modelfile)
+   def closeEvent(self, ev):
+      self.settings.setValue('mainwindowstate', self.saveState())
+      self.settings.setValue('mainwindowgeometry', self.saveGeometry())
+      if self.editor.model and self.editor.model.filename:
+         self.settings.setValue('openedmodel', self.editor.model.filename)
+         # TODO: ask for save of opened files
+      else:
+         self.settings.remove('openedmodel')
+      ev.accept()
+
+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()
+   app.exec_()
+