Mercurial > lcfOS
view python/other/diagrameditor.py @ 410:6aa9743ed362 tip
Reflect change in c3 public modifier
author | Windel Bouwman |
---|---|
date | Mon, 23 Feb 2015 21:06:04 +0100 |
parents | bb4289c84907 |
children |
line wrap: on
line source
#!/usr/bin/python import sys import json import base64 import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'ide')) from qtwrapper import QtGui, QtCore, QtWidgets, pyqtSignal, get_icon from qtwrapper import abspath, Qt from diagramitems import Connection, ResizeSelectionHandle, Block, DiagramScene, CodeBlock 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(QtWidgets.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(QtWidgets.QGraphicsView): def __init__(self, parent=None): super().__init__(parent) self.setObjectName('Editor') self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag) self.delShort = QtWidgets.QShortcut(QtGui.QKeySequence.Delete, self) self._model = None self.treeView = QtWidgets.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 = QtWidgets.QFileDialog.getOpenFileName(self) if filename: self.model = loadModel(filename) def newModel(self): print('NEW') 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.angleDelta().y() / 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(QtGui.QStandardItemModel): def __init__(self, parent): super().__init__(parent) self.setObjectName('Library') mimeTypes = lambda self: ['component/name'] def mimeData(self, idxs): mimedata = QtCore.QMimeData() for idx in idxs: if idx.isValid(): txt = self.data(idx, Qt.DisplayRole) mimedata.setData('component/name', txt) return mimedata class ModelHierarchyModel(QtCore.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 QtCore.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(QtWidgets.QListView): def __init__(self): super().__init__() self.setObjectName('LibraryWidget') self.libraryModel = LibraryModel(self) self.libraryModel.setColumnCount(1) # Create an icon with an icon: pixmap = QtGui.QPixmap(60, 60) pixmap.fill() painter = QtGui.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(QtGui.QStandardItem(QtGui.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(QtWidgets.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 = QtWidgets.QAction(icon, name, self) if icon else QtWidgets.QAction(name, self) a.setShortcuts(shortcut) a.triggered.connect(callback) toolbar.addAction(a) act('New', QtGui.QKeySequence.New, self.editor.newModel) act('Save', QtGui.QKeySequence.Save, self.editor.save) act('Load', QtGui.QKeySequence.Open, self.editor.load) act('Full screen', QtGui.QKeySequence("F11"), self.toggleFullScreen) act('Fit in view', QtGui.QKeySequence("F8"), self.editor.zoomAll) act('Go up', QtGui.QKeySequence(Qt.Key_Up), self.editor.goUp) act('Model code', QtGui.QKeySequence("F7"), self.editor.showCode) def addDock(name, widget): dock = QtWidgets.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 = QtCore.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 = QtWidgets.QApplication(sys.argv) main = Main() main.show() app.exec_()