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