Mercurial > lcfOS
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_() +