Mercurial > lcfOS
comparison python/other/diagrameditor.py @ 390:b77f3290ac79
Added diagram editor tests
author | Windel Bouwman |
---|---|
date | Fri, 16 May 2014 10:12:16 +0200 |
parents | 7b38782ed496 |
children | bb4289c84907 |
comparison
equal
deleted
inserted
replaced
388:e07c2a9abac1 | 390:b77f3290ac79 |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 from PyQt4.QtGui import * | 3 import sys |
4 from PyQt4.QtCore import * | 4 import json |
5 import sys, json, base64 | 5 import base64 |
6 import os | |
7 | |
8 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'ide')) | |
9 | |
10 from qtwrapper import QtGui, QtCore, QtWidgets, pyqtSignal, get_icon | |
11 from qtwrapper import abspath, Qt | |
6 | 12 |
7 from diagramitems import Connection, ResizeSelectionHandle, Block, DiagramScene, CodeBlock | 13 from diagramitems import Connection, ResizeSelectionHandle, Block, DiagramScene, CodeBlock |
8 from icons import newicon, saveicon, loadicon | |
9 import diagramitems | 14 import diagramitems |
10 | 15 |
11 """ | 16 """ |
12 Author: Windel Bouwman | 17 Author: Windel Bouwman |
13 Year: 2012 | 18 Year: 2012 |
14 Description: This script implements a diagram editor. | 19 Description: This script implements a diagram editor. |
15 run with python 3.x as: | 20 run with python 3.x as: |
16 $ python [thisfile.py] | 21 $ python [thisfile.py] |
17 """ | 22 """ |
23 | |
24 | |
18 def indent(lines): | 25 def indent(lines): |
19 return [' ' + line for line in lines] | 26 return [' ' + line for line in lines] |
20 | 27 |
21 class ParameterDialog(QDialog): | 28 |
29 class ParameterDialog(QtWidgets.QDialog): | |
22 def __init__(self, block, parent = None): | 30 def __init__(self, block, parent = None): |
23 super(ParameterDialog, self).__init__(parent) | 31 super(ParameterDialog, self).__init__(parent) |
24 self.block = block | 32 self.block = block |
25 self.button = QPushButton('Ok', self) | 33 self.button = QPushButton('Ok', self) |
26 self.nameEdit = QLineEdit(self.block.name) | 34 self.nameEdit = QLineEdit(self.block.name) |
34 def OK(self): | 42 def OK(self): |
35 self.block.name = self.nameEdit.text() | 43 self.block.name = self.nameEdit.text() |
36 self.block.code = self.codeEdit.toPlainText() | 44 self.block.code = self.codeEdit.toPlainText() |
37 self.close() | 45 self.close() |
38 | 46 |
39 class EditorGraphicsView(QGraphicsView): | 47 |
48 class EditorGraphicsView(QtWidgets.QGraphicsView): | |
40 def __init__(self, parent=None): | 49 def __init__(self, parent=None): |
41 QGraphicsView.__init__(self, parent) | 50 super().__init__(parent) |
42 self.setDragMode(QGraphicsView.RubberBandDrag) | 51 self.setObjectName('Editor') |
43 self.delShort = QShortcut(QKeySequence.Delete, self) | 52 self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag) |
53 self.delShort = QtWidgets.QShortcut(QtGui.QKeySequence.Delete, self) | |
44 self._model = None | 54 self._model = None |
45 self.treeView = QTreeView() | 55 self.treeView = QtWidgets.QTreeView() |
46 self.treeView.clicked.connect(self.itemActivated) | 56 self.treeView.clicked.connect(self.itemActivated) |
57 | |
47 def itemActivated(self, idx): | 58 def itemActivated(self, idx): |
48 b = idx.internalPointer() | 59 b = idx.internalPointer() |
49 s = b.scene() | 60 s = b.scene() |
50 s.clearSelection() | 61 s.clearSelection() |
51 b.setSelected(True) | 62 b.setSelected(True) |
63 | |
52 def setDiagram(self, d): | 64 def setDiagram(self, d): |
53 self.setScene(d) | 65 self.setScene(d) |
54 self.delShort.activated.connect(d.deleteItems) | 66 self.delShort.activated.connect(d.deleteItems) |
67 | |
55 def getModel(self): | 68 def getModel(self): |
56 return self._model | 69 return self._model |
70 | |
57 def setModel(self, m): | 71 def setModel(self, m): |
58 self._model = m | 72 self._model = m |
59 if m: | 73 if m: |
60 self.treeView.setModel(m) | 74 self.treeView.setModel(m) |
61 self.diagram = m.rootDiagram | 75 self.diagram = m.rootDiagram |
68 self.model.filename = QFileDialog.getSaveFileName(self) | 82 self.model.filename = QFileDialog.getSaveFileName(self) |
69 if self.model.filename: | 83 if self.model.filename: |
70 with open(self.model.filename, 'w') as f: | 84 with open(self.model.filename, 'w') as f: |
71 f.write(json.dumps(self.model.Dict, indent=2)) | 85 f.write(json.dumps(self.model.Dict, indent=2)) |
72 def load(self): | 86 def load(self): |
73 filename = QFileDialog.getOpenFileName(self) | 87 filename = QtWidgets.QFileDialog.getOpenFileName(self) |
74 if filename: | 88 if filename: |
75 self.model = loadModel(filename) | 89 self.model = loadModel(filename) |
90 | |
76 def newModel(self): | 91 def newModel(self): |
77 self.model = ModelHierarchyModel() | 92 print('NEW') |
93 self.model = ModelHierarchyModel() | |
94 | |
78 def goUp(self): | 95 def goUp(self): |
79 if hasattr(self.diagram, 'containingBlock'): | 96 if hasattr(self.diagram, 'containingBlock'): |
80 self.diagram = self.diagram.containingBlock.scene() | 97 self.diagram = self.diagram.containingBlock.scene() |
81 self.zoomAll() | 98 self.zoomAll() |
99 | |
82 def showCode(self): | 100 def showCode(self): |
83 if self.model: | 101 if self.model: |
84 c = self.model.gencode() | 102 c = self.model.gencode() |
85 c = '\n'.join(c) | 103 c = '\n'.join(c) |
86 d = QDialog() | 104 d = QDialog() |
99 outputview.clear() | 117 outputview.clear() |
100 globs = {'print': print2} | 118 globs = {'print': print2} |
101 exec(codeview.toPlainText(), globs) | 119 exec(codeview.toPlainText(), globs) |
102 runButton.clicked.connect(runIt) | 120 runButton.clicked.connect(runIt) |
103 d.exec_() | 121 d.exec_() |
122 | |
104 def zoomAll(self): | 123 def zoomAll(self): |
105 """ zoom to fit all items """ | 124 """ zoom to fit all items """ |
106 rect = self.diagram.itemsBoundingRect() | 125 rect = self.diagram.itemsBoundingRect() |
107 self.fitInView(rect, Qt.KeepAspectRatio) | 126 self.fitInView(rect, Qt.KeepAspectRatio) |
127 | |
108 def wheelEvent(self, event): | 128 def wheelEvent(self, event): |
109 pos = event.pos() | 129 pos = event.pos() |
110 posbefore = self.mapToScene(pos) | 130 posbefore = self.mapToScene(pos) |
111 degrees = event.delta() / 8.0 | 131 degrees = event.delta() / 8.0 |
112 sx = (100.0 + degrees) / 100.0 | 132 sx = (100.0 + degrees) / 100.0 |
113 self.scale(sx, sx) | 133 self.scale(sx, sx) |
114 event.accept() | 134 event.accept() |
135 | |
115 def dragEnterEvent(self, event): | 136 def dragEnterEvent(self, event): |
116 if event.mimeData().hasFormat('component/name'): | 137 if event.mimeData().hasFormat('component/name'): |
117 event.accept() | 138 event.accept() |
139 | |
118 def dragMoveEvent(self, event): | 140 def dragMoveEvent(self, event): |
119 if event.mimeData().hasFormat('component/name'): event.accept() | 141 if event.mimeData().hasFormat('component/name'): |
142 event.accept() | |
143 | |
120 def dropEvent(self, event): | 144 def dropEvent(self, event): |
121 if event.mimeData().hasFormat('component/name'): | 145 if event.mimeData().hasFormat('component/name'): |
122 name = bytes(event.mimeData().data('component/name')).decode() | 146 name = bytes(event.mimeData().data('component/name')).decode() |
123 kind, name = name.split(':') | 147 kind, name = name.split(':') |
124 pos = self.mapToScene(event.pos()) | 148 pos = self.mapToScene(event.pos()) |
130 print(kind) | 154 print(kind) |
131 b = kind(s.uniqify(name)) | 155 b = kind(s.uniqify(name)) |
132 b.setPos(pos) | 156 b.setPos(pos) |
133 s.addItem(b) | 157 s.addItem(b) |
134 | 158 |
135 class LibraryModel(QStandardItemModel): | 159 |
136 mimeTypes = lambda self: ['component/name'] | 160 class LibraryModel(QtGui.QStandardItemModel): |
137 def mimeData(self, idxs): | 161 def __init__(self, parent): |
138 mimedata = QMimeData() | 162 super().__init__(parent) |
139 for idx in idxs: | 163 self.setObjectName('Library') |
140 if idx.isValid(): | 164 |
141 txt = self.data(idx, Qt.DisplayRole) | 165 mimeTypes = lambda self: ['component/name'] |
142 mimedata.setData('component/name', txt) | 166 def mimeData(self, idxs): |
143 return mimedata | 167 mimedata = QtCore.QMimeData() |
144 | 168 for idx in idxs: |
145 class ModelHierarchyModel(QAbstractItemModel): | 169 if idx.isValid(): |
170 txt = self.data(idx, Qt.DisplayRole) | |
171 mimedata.setData('component/name', txt) | |
172 return mimedata | |
173 | |
174 | |
175 class ModelHierarchyModel(QtCore.QAbstractItemModel): | |
146 def __init__(self): | 176 def __init__(self): |
147 super(ModelHierarchyModel, self).__init__() | 177 super(ModelHierarchyModel, self).__init__() |
148 self.rootDiagram = DiagramScene() | 178 self.rootDiagram = DiagramScene() |
149 self.rootDiagram.structureChanged.connect(self.handlechange) | 179 self.rootDiagram.structureChanged.connect(self.handlechange) |
150 self.filename = None | 180 self.filename = None |
181 | |
151 def handlechange(self): | 182 def handlechange(self): |
152 self.modelReset.emit() | 183 self.modelReset.emit() |
184 | |
153 def setDict(self, d): | 185 def setDict(self, d): |
154 self.rootDiagram.Dict = d | 186 self.rootDiagram.Dict = d |
155 self.modelReset.emit() | 187 self.modelReset.emit() |
188 | |
156 def getDict(self): | 189 def getDict(self): |
157 return self.rootDiagram.Dict | 190 return self.rootDiagram.Dict |
191 | |
158 Dict = property(getDict, setDict) | 192 Dict = property(getDict, setDict) |
159 def gencode(self): | 193 def gencode(self): |
160 c = ['def topLevel():'] | 194 c = ['def topLevel():'] |
161 c += indent(self.rootDiagram.gencode()) | 195 c += indent(self.rootDiagram.gencode()) |
162 c.append('print("Running model")') | 196 c.append('print("Running model")') |
163 c.append('topLevel()') | 197 c.append('topLevel()') |
164 c.append('print("Done")') | 198 c.append('print("Done")') |
165 return c | 199 return c |
200 | |
166 def index(self, row, column, parent=None): | 201 def index(self, row, column, parent=None): |
167 if parent.isValid(): | 202 if parent.isValid(): |
168 parent = parent.internalPointer().subModel | 203 parent = parent.internalPointer().subModel |
169 else: | 204 else: |
170 parent = self.rootDiagram | 205 parent = self.rootDiagram |
172 block = blocks[row] | 207 block = blocks[row] |
173 # Store the index to retrieve it later in the parent function. | 208 # Store the index to retrieve it later in the parent function. |
174 # TODO: solve this in a better way. | 209 # TODO: solve this in a better way. |
175 block.index = self.createIndex(row, column, block) | 210 block.index = self.createIndex(row, column, block) |
176 return block.index | 211 return block.index |
212 | |
177 def parent(self, index): | 213 def parent(self, index): |
178 if index.isValid(): | 214 if index.isValid(): |
179 block = index.internalPointer() | 215 block = index.internalPointer() |
180 if block.scene() == self.rootDiagram: | 216 if block.scene() == self.rootDiagram: |
181 return QModelIndex() | 217 return QModelIndex() |
182 else: | 218 else: |
183 print(block) | 219 print(block) |
184 outerBlock = block.scene().containingBlock | 220 outerBlock = block.scene().containingBlock |
185 return outerBlock.index | 221 return outerBlock.index |
186 print('parent: No valid index') | 222 print('parent: No valid index') |
223 | |
187 def data(self, index, role): | 224 def data(self, index, role): |
188 if index.isValid() and role == Qt.DisplayRole: | 225 if index.isValid() and role == Qt.DisplayRole: |
189 b = index.internalPointer() | 226 b = index.internalPointer() |
190 if index.column() == 0: | 227 if index.column() == 0: |
191 return b.name | 228 return b.name |
192 elif index.column() == 1: | 229 elif index.column() == 1: |
193 return str(type(b)) | 230 return str(type(b)) |
231 | |
194 def headerData(self, section, orientation, role): | 232 def headerData(self, section, orientation, role): |
195 if orientation == Qt.Horizontal and role == Qt.DisplayRole: | 233 if orientation == Qt.Horizontal and role == Qt.DisplayRole: |
196 if section == 0: | 234 if section == 0: |
197 return "Element" | 235 return "Element" |
198 elif section == 1: | 236 elif section == 1: |
199 return "Type" | 237 return "Type" |
200 else: | 238 else: |
201 return "x" | 239 return "x" |
240 | |
202 def rowCount(self, parent): | 241 def rowCount(self, parent): |
203 if parent.column() > 0: | 242 if parent.column() > 0: |
204 return 0 | 243 return 0 |
205 if parent.isValid(): | 244 if parent.isValid(): |
206 block = parent.internalPointer() | 245 block = parent.internalPointer() |
208 return len(block.subModel.blocks) | 247 return len(block.subModel.blocks) |
209 else: | 248 else: |
210 return 0 | 249 return 0 |
211 else: | 250 else: |
212 return len(self.rootDiagram.blocks) | 251 return len(self.rootDiagram.blocks) |
252 | |
213 def columnCount(self, parent): | 253 def columnCount(self, parent): |
214 return 2 | 254 return 2 |
215 | 255 |
216 class LibraryWidget(QListView): | 256 |
217 def __init__(self): | 257 class LibraryWidget(QtWidgets.QListView): |
218 super(LibraryWidget, self).__init__(None) | 258 def __init__(self): |
259 super().__init__() | |
260 self.setObjectName('LibraryWidget') | |
219 self.libraryModel = LibraryModel(self) | 261 self.libraryModel = LibraryModel(self) |
220 self.libraryModel.setColumnCount(1) | 262 self.libraryModel.setColumnCount(1) |
221 # Create an icon with an icon: | 263 # Create an icon with an icon: |
222 pixmap = QPixmap(60, 60) | 264 pixmap = QtGui.QPixmap(60, 60) |
223 pixmap.fill() | 265 pixmap.fill() |
224 painter = QPainter(pixmap) | 266 painter = QtGui.QPainter(pixmap) |
225 painter.fillRect(10, 10, 40, 40, Qt.blue) | 267 painter.fillRect(10, 10, 40, 40, Qt.blue) |
226 painter.setBrush(Qt.yellow) | 268 painter.setBrush(Qt.yellow) |
227 painter.drawEllipse(20, 20, 20, 20) | 269 painter.drawEllipse(20, 20, 20, 20) |
228 painter.end() | 270 painter.end() |
229 # Fill library: | 271 # Fill library: |
230 for name in ['CodeBlock:codeBlock', 'DiagramBlock:submod', 'Block:blk']: | 272 for name in ['CodeBlock:codeBlock', 'DiagramBlock:submod', 'Block:blk']: |
231 self.libraryModel.appendRow(QStandardItem(QIcon(pixmap), name)) | 273 self.libraryModel.appendRow(QtGui.QStandardItem(QtGui.QIcon(pixmap), name)) |
232 self.setModel(self.libraryModel) | 274 self.setModel(self.libraryModel) |
233 self.setViewMode(self.IconMode) | 275 self.setViewMode(self.IconMode) |
234 self.setDragDropMode(self.DragOnly) | 276 self.setDragDropMode(self.DragOnly) |
235 | 277 |
278 | |
236 def warning(txt): | 279 def warning(txt): |
237 QMessageBox.warning(None, "Warning", txt) | 280 QMessageBox.warning(None, "Warning", txt) |
281 | |
238 | 282 |
239 def loadModel(filename): | 283 def loadModel(filename): |
240 try: | 284 try: |
241 m = ModelHierarchyModel() | 285 m = ModelHierarchyModel() |
242 with open(filename, 'r') as f: data = f.read() | 286 with open(filename, 'r') as f: data = f.read() |
248 except ValueError: | 292 except ValueError: |
249 warning('Corrupt model: {0}'.format(filename)) | 293 warning('Corrupt model: {0}'.format(filename)) |
250 except FileNotFoundError: | 294 except FileNotFoundError: |
251 warning('File [{0}] not found'.format(filename)) | 295 warning('File [{0}] not found'.format(filename)) |
252 | 296 |
253 class Main(QMainWindow): | 297 |
254 def __init__(self): | 298 class Main(QtWidgets.QMainWindow): |
299 def __init__(self): | |
255 super(Main, self).__init__(None) | 300 super(Main, self).__init__(None) |
256 self.editor = EditorGraphicsView() | 301 self.editor = EditorGraphicsView() |
257 self.setCentralWidget(self.editor) | 302 self.setCentralWidget(self.editor) |
258 self.setWindowTitle("Diagram editor") | 303 self.setWindowTitle("Diagram editor") |
259 def buildIcon(b64): | 304 def buildIcon(b64): |
262 pm.loadFromData(icon) | 307 pm.loadFromData(icon) |
263 return QIcon(pm) | 308 return QIcon(pm) |
264 toolbar = self.addToolBar('Tools') | 309 toolbar = self.addToolBar('Tools') |
265 toolbar.setObjectName('Tools') | 310 toolbar.setObjectName('Tools') |
266 def act(name, shortcut, callback, icon=None): | 311 def act(name, shortcut, callback, icon=None): |
267 a = QAction(icon, name, self) if icon else QAction(name, self) | 312 a = QtWidgets.QAction(icon, name, self) if icon else QtWidgets.QAction(name, self) |
268 a.setShortcuts(shortcut) | 313 a.setShortcuts(shortcut) |
269 a.triggered.connect(callback) | 314 a.triggered.connect(callback) |
270 toolbar.addAction(a) | 315 toolbar.addAction(a) |
271 act('New', QKeySequence.New, self.editor.newModel, buildIcon(newicon)) | 316 act('New', QtGui.QKeySequence.New, self.editor.newModel) |
272 act('Save', QKeySequence.Save, self.editor.save, buildIcon(saveicon)) | 317 act('Save', QtGui.QKeySequence.Save, self.editor.save) |
273 act('Load', QKeySequence.Open, self.editor.load, buildIcon(loadicon)) | 318 act('Load', QtGui.QKeySequence.Open, self.editor.load) |
274 act('Full screen', QKeySequence("F11"), self.toggleFullScreen) | 319 act('Full screen', QtGui.QKeySequence("F11"), self.toggleFullScreen) |
275 act('Fit in view', QKeySequence("F8"), self.editor.zoomAll) | 320 act('Fit in view', QtGui.QKeySequence("F8"), self.editor.zoomAll) |
276 act('Go up', QKeySequence(Qt.Key_Up), self.editor.goUp) | 321 act('Go up', QtGui.QKeySequence(Qt.Key_Up), self.editor.goUp) |
277 act('Model code', QKeySequence("F7"), self.editor.showCode) | 322 act('Model code', QtGui.QKeySequence("F7"), self.editor.showCode) |
278 def addDock(name, widget): | 323 def addDock(name, widget): |
279 dock = QDockWidget(name, self) | 324 dock = QtWidgets.QDockWidget(name, self) |
280 dock.setObjectName(name) | 325 dock.setObjectName(name) |
281 dock.setWidget(widget) | 326 dock.setWidget(widget) |
282 self.addDockWidget(Qt.LeftDockWidgetArea, dock) | 327 self.addDockWidget(Qt.LeftDockWidgetArea, dock) |
283 addDock('Library', LibraryWidget()) | 328 addDock('Library', LibraryWidget()) |
284 addDock('Model tree', self.editor.treeView) | 329 addDock('Model tree', self.editor.treeView) |
285 self.settings = QSettings('windelsoft', 'diagrameditor') | 330 self.settings = QtCore.QSettings('windelsoft', 'diagrameditor') |
286 self.loadSettings() | 331 self.loadSettings() |
287 def toggleFullScreen(self): | 332 |
288 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) | 333 def toggleFullScreen(self): |
289 self.editor.zoomAll() | 334 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) |
290 def loadSettings(self): | 335 self.editor.zoomAll() |
336 | |
337 def loadSettings(self): | |
291 if self.settings.contains('mainwindowstate'): | 338 if self.settings.contains('mainwindowstate'): |
292 self.restoreState(self.settings.value('mainwindowstate')) | 339 self.restoreState(self.settings.value('mainwindowstate')) |
293 if self.settings.contains('mainwindowgeometry'): | 340 if self.settings.contains('mainwindowgeometry'): |
294 self.restoreGeometry(self.settings.value('mainwindowgeometry')) | 341 self.restoreGeometry(self.settings.value('mainwindowgeometry')) |
295 if self.settings.contains('openedmodel'): | 342 if self.settings.contains('openedmodel'): |
296 modelfile = self.settings.value('openedmodel') | 343 modelfile = self.settings.value('openedmodel') |
297 self.editor.model = loadModel(modelfile) | 344 self.editor.model = loadModel(modelfile) |
298 def closeEvent(self, ev): | 345 |
346 def closeEvent(self, ev): | |
299 self.settings.setValue('mainwindowstate', self.saveState()) | 347 self.settings.setValue('mainwindowstate', self.saveState()) |
300 self.settings.setValue('mainwindowgeometry', self.saveGeometry()) | 348 self.settings.setValue('mainwindowgeometry', self.saveGeometry()) |
301 if self.editor.model and self.editor.model.filename: | 349 if self.editor.model and self.editor.model.filename: |
302 self.settings.setValue('openedmodel', self.editor.model.filename) | 350 self.settings.setValue('openedmodel', self.editor.model.filename) |
303 # TODO: ask for save of opened files | 351 # TODO: ask for save of opened files |
307 | 355 |
308 if __name__ == '__main__': | 356 if __name__ == '__main__': |
309 if sys.version_info.major != 3: | 357 if sys.version_info.major != 3: |
310 print('Please use python 3.x') | 358 print('Please use python 3.x') |
311 sys.exit(1) | 359 sys.exit(1) |
312 app = QApplication(sys.argv) | 360 app = QtWidgets.QApplication(sys.argv) |
313 main = Main() | 361 main = Main() |
314 main.show() | 362 main.show() |
315 app.exec_() | 363 app.exec_() |
316 | 364 |