Mercurial > lcfOS
view python/diagrameditor.py @ 221:848c4b15fd0b
pointers
author | Windel Bouwman |
---|---|
date | Mon, 08 Jul 2013 22:21:44 +0200 |
parents | 6efbeb903777 |
children |
line wrap: on
line source
#!/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_()