Mercurial > lcfOS
comparison python/apps/diagrameditor.py @ 78:85bfa15c01f1
Changed to json
author | windel |
---|---|
date | Mon, 12 Nov 2012 20:25:41 +0100 |
parents | a5f3959bcab7 |
children | 44f075fe71eb |
comparison
equal
deleted
inserted
replaced
77:a5f3959bcab7 | 78:85bfa15c01f1 |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 from PyQt4.QtGui import * | 3 from PyQt4.QtGui import * |
4 from PyQt4.QtCore import * | 4 from PyQt4.QtCore import * |
5 import sys, xml | 5 import sys, json |
6 import xml.dom.minidom as md | |
7 | 6 |
8 """ | 7 """ |
9 Author: Windel Bouwman | 8 Author: Windel Bouwman |
10 Year: 2012 | 9 Year: 2012 |
11 Description: This script implements a diagram editor. | 10 Description: This script implements a diagram editor. |
30 while newname in names: newname, i = name + str(i), i + 1 | 29 while newname in names: newname, i = name + str(i), i + 1 |
31 return newname | 30 return newname |
32 | 31 |
33 class Connection(QGraphicsPathItem): | 32 class Connection(QGraphicsPathItem): |
34 """ Implementation of a connection between blocks """ | 33 """ Implementation of a connection between blocks """ |
35 def __init__(self, fromPort, toPort): | 34 def __init__(self, fromPort=None, toPort=None): |
36 super(Connection, self).__init__() | 35 super(Connection, self).__init__() |
37 self.pos2 = self.fromPort = self.toPort = None | 36 self.pos2 = self.fromPort = self.toPort = None |
38 self.setFlags(self.ItemIsSelectable | self.ItemClipsToShape) | 37 self.setFlags(self.ItemIsSelectable | self.ItemClipsToShape) |
39 pen = QPen(Qt.blue, 2, cap=Qt.RoundCap) | 38 pen = QPen(Qt.blue, 2, cap=Qt.RoundCap) |
40 self.setPen(pen) | 39 self.setPen(pen) |
43 self.arrowhead.setPen(pen) | 42 self.arrowhead.setPen(pen) |
44 self.arrowhead.setBrush(QBrush(pen.color())) | 43 self.arrowhead.setBrush(QBrush(pen.color())) |
45 self.vias = [] | 44 self.vias = [] |
46 self.setFromPort(fromPort) | 45 self.setFromPort(fromPort) |
47 self.setToPort(toPort) | 46 self.setToPort(toPort) |
48 def mouseDoubleClickEvent(self, event): | 47 def getDict(self): |
49 pos = event.scenePos() | 48 d = {} |
50 pts = [self.getPos1()] + [v.pos() for v in self.vias] + [self.pos2] | 49 d['fromBlock'] = self.fromPort.block.name |
51 idx = 0 | 50 d['fromPort'] = self.fromPort.name |
52 tidx = 0 | 51 d['toBlock'] = self.toPort.block.name |
53 for p1, p2 in zip(pts[0:-1], pts[1:]): | 52 d['toPort'] = self.toPort.name |
54 l1 = QLineF(p1, p2) | 53 return d |
55 l2 = QLineF(p1, pos) | 54 def setDict(self, d): |
56 l3 = QLineF(pos, p2) | 55 print(d) |
57 d = l2.length() + l3.length() - l1.length() | 56 Dict = property(getDict, setDict) |
58 if d < 5: | |
59 tidx = idx | |
60 idx += 1 | |
61 self.addHandle(pos, tidx) | |
62 def addHandle(self, pos, idx=None): | |
63 hi = HandleItem(self) | |
64 if idx: | |
65 self.vias.insert(idx, hi) | |
66 else: | |
67 self.vias.append(hi) | |
68 def callback(p): | |
69 self.updateLineStukken() | |
70 return p | |
71 hi.posChangeCallbacks.append(callback) | |
72 hi.setPos(pos) | |
73 self.updateLineStukken() | |
74 def myDelete(self): | 57 def myDelete(self): |
75 scene = self.scene() | 58 scene = self.scene() |
76 if scene: | 59 if scene: |
77 self.setFromPort(None) | 60 self.setFromPort(None) |
78 self.setToPort(None) | 61 self.setToPort(None) |
171 rect = self.textItem.boundingRect() | 154 rect = self.textItem.boundingRect() |
172 lw, lh = rect.width(), rect.height() | 155 lw, lh = rect.width(), rect.height() |
173 lx = 3 if self.direction == 'input' else -3 - lw | 156 lx = 3 if self.direction == 'input' else -3 - lw |
174 self.textItem.setPos(lx, -lh / 2) | 157 self.textItem.setPos(lx, -lh / 2) |
175 name = property(getName, setName) | 158 name = property(getName, setName) |
159 def getDict(self): | |
160 return {'name': self.name} | |
161 Dict = property(getDict) | |
176 def itemChange(self, change, value): | 162 def itemChange(self, change, value): |
177 if change == self.ItemScenePositionHasChanged: | 163 if change == self.ItemScenePositionHasChanged: |
178 for cb in self.posCallbacks: | 164 for cb in self.posCallbacks: |
179 cb(value) | 165 cb(value) |
180 return value | 166 return value |
185 | 171 |
186 class OutputPort(PortItem): | 172 class OutputPort(PortItem): |
187 # TODO: create a subclass OR make a member porttype | 173 # TODO: create a subclass OR make a member porttype |
188 pass | 174 pass |
189 | 175 |
190 # Block part: | |
191 class HandleItem(QGraphicsEllipseItem): | 176 class HandleItem(QGraphicsEllipseItem): |
192 """ A handle that can be moved by the mouse """ | 177 """ A handle that can be moved by the mouse """ |
193 def __init__(self, parent=None): | 178 def __init__(self, parent=None): |
194 dx = 13.0 | 179 dx = 13.0 |
195 super(HandleItem, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent) | 180 super(HandleItem, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent) |
207 for cb in self.posChangeCallbacks: | 192 for cb in self.posChangeCallbacks: |
208 res = cb(value) | 193 res = cb(value) |
209 if res: | 194 if res: |
210 value = res | 195 value = res |
211 return value | 196 return value |
212 # Call superclass method: | |
213 return super(HandleItem, self).itemChange(change, value) | 197 return super(HandleItem, self).itemChange(change, value) |
214 | 198 |
215 class BlockItem(QGraphicsRectItem): | 199 class BlockItem(QGraphicsRectItem): |
216 """ | 200 """ Represents a block in the diagram """ |
217 Represents a block in the diagram | |
218 Has an x and y and width and height | |
219 width and height can only be adjusted with a tip in the lower right corner. | |
220 | |
221 - in and output ports | |
222 - parameters | |
223 - description | |
224 """ | |
225 def __init__(self, name='Untitled', parent=None): | 201 def __init__(self, name='Untitled', parent=None): |
226 super(BlockItem, self).__init__(parent) | 202 super(BlockItem, self).__init__(parent) |
227 self.subModel = DiagramScene() | 203 self.subModel = DiagramScene() |
228 self.subModel.containingBlock = self | 204 self.subModel.containingBlock = self |
229 # Properties of the rectangle: | 205 # Properties of the rectangle: |
269 names = [i.name for i in self.inputs + self.outputs] | 245 names = [i.name for i in self.inputs + self.outputs] |
270 self.addOutput(PortItem(uniqify('out', names), self, 'output')) | 246 self.addOutput(PortItem(uniqify('out', names), self, 'output')) |
271 def setName(self, name): self.label.setPlainText(name) | 247 def setName(self, name): self.label.setPlainText(name) |
272 def getName(self): return self.label.toPlainText() | 248 def getName(self): return self.label.toPlainText() |
273 name = property(getName, setName) | 249 name = property(getName, setName) |
250 def getDict(self): | |
251 d = {'x': self.scenePos().x(), 'y': self.scenePos().y()} | |
252 rect = self.rect() | |
253 d.update({'width': rect.width(), 'height': rect.height()}) | |
254 d.update({'name': self.name, 'code': self.code}) | |
255 d['inputs'] = [inp.Dict for inp in self.inputs] | |
256 d['outputs'] = [outp.Dict for outp in self.outputs] | |
257 return d | |
258 def setDict(self, d): | |
259 self.name = d['name'] | |
260 self.code = d['code'] | |
261 self.setPos(d['x'], d['y']) | |
262 self.sizer.setPos(d['width'], d['height']) | |
263 for inp in d['inputs']: | |
264 self.addInput(PortItem(inp['name'], self, 'input')) | |
265 for outp in d['outputs']: | |
266 self.addOutput(PortItem(outp['name'], self, 'output')) | |
267 Dict = property(getDict, setDict) | |
274 def addInput(self, i): | 268 def addInput(self, i): |
275 self.inputs.append(i) | 269 self.inputs.append(i) |
276 self.updateSize() | 270 self.updateSize() |
277 def addOutput(self, o): | 271 def addOutput(self, o): |
278 self.outputs.append(o) | 272 self.outputs.append(o) |
313 return QPointF(w, h) | 307 return QPointF(w, h) |
314 | 308 |
315 class EditorGraphicsView(QGraphicsView): | 309 class EditorGraphicsView(QGraphicsView): |
316 def __init__(self, parent=None): | 310 def __init__(self, parent=None): |
317 QGraphicsView.__init__(self, parent) | 311 QGraphicsView.__init__(self, parent) |
318 self.dScene = DiagramScene(self) | |
319 self.setDragMode(QGraphicsView.RubberBandDrag) | 312 self.setDragMode(QGraphicsView.RubberBandDrag) |
320 delShort = QShortcut(QKeySequence.Delete, self) | 313 self.delShort = QShortcut(QKeySequence.Delete, self) |
321 delShort.activated.connect(self.dScene.deleteItems) | 314 def setModel(self, m): |
322 dScene = property(lambda s: s.scene(), lambda s, v: s.setScene(v)) | 315 self.setScene(m) |
316 self.delShort.activated.connect(m.deleteItems) | |
317 model = property(lambda s: s.scene(), setModel) | |
323 def save(self): | 318 def save(self): |
324 self.diagramScene.saveDiagram('diagram2.usd') | 319 filename = 'diagram4.usd' |
320 with open(filename, 'w') as f: | |
321 d = self.model.Dict | |
322 print(d) | |
323 f.write(json.dumps(d)) | |
325 def load(self): | 324 def load(self): |
326 filename = QFileDialog.getOpenFileName(self) | 325 filename = QFileDialog.getOpenFileName(self) |
327 self.diagramScene.loadDiagram(filename) | 326 self.model = loadModel(filename) |
328 def goDown(self): | 327 def goDown(self): |
329 print('Down!') | 328 print('Down!') |
330 block = self.dScene.selected | 329 block = self.dScene.selected |
331 def goUp(self): | 330 def goUp(self): |
332 print('Up!') | 331 print('Up!') |
333 if hasattr(self.dScene, 'containingBlock'): | 332 if hasattr(self.dScene, 'containingBlock'): |
334 self.dScene = self.dScene.containingBlock.scene() | 333 self.dScene = self.dScene.containingBlock.scene() |
335 self.zoomAll() | 334 self.zoomAll() |
336 def zoomAll(self): | 335 def zoomAll(self): |
337 """ zoom to fit all items """ | 336 """ zoom to fit all items """ |
338 rect = self.dScene.itemsBoundingRect() | 337 rect = self.model.itemsBoundingRect() |
339 self.fitInView(rect, Qt.KeepAspectRatio) | 338 self.fitInView(rect, Qt.KeepAspectRatio) |
340 def wheelEvent(self, event): | 339 def wheelEvent(self, event): |
341 pos = event.pos() | 340 pos = event.pos() |
342 posbefore = self.mapToScene(pos) | 341 posbefore = self.mapToScene(pos) |
343 degrees = event.delta() / 8.0 | 342 degrees = event.delta() / 8.0 |
364 if idx.isValid(): | 363 if idx.isValid(): |
365 txt = self.data(idx, Qt.DisplayRole) # python 3 | 364 txt = self.data(idx, Qt.DisplayRole) # python 3 |
366 mimedata.setData('component/name', txt) | 365 mimedata.setData('component/name', txt) |
367 return mimedata | 366 return mimedata |
368 | 367 |
368 class ModelHierarchyModel(QStandardItemModel): | |
369 def __init__(self): | |
370 super(ModelHierarchyModel, self).__init__() | |
371 self.items = [] | |
372 def flags(self, idx): | |
373 if idx.isValid(): | |
374 return Qt.ItemIsSelectable | Qt.ItemIsEnabled | |
375 def data(self): | |
376 pass | |
377 def headerData(self, section, orientation, role): | |
378 if orientation == Qt.Horizontal and role == Qt.DisplayRole: | |
379 return "Henkie" | |
380 def rowCount(self, parent): | |
381 pass | |
382 def columnCount(self, parent): | |
383 return 1 | |
384 | |
369 class DiagramScene(QGraphicsScene): | 385 class DiagramScene(QGraphicsScene): |
370 """ Save and load and deletion of item""" | 386 def __init__(self): |
371 def __init__(self, parent=None): | 387 super(DiagramScene, self).__init__() |
372 super(DiagramScene, self).__init__(parent) | |
373 self.startedConnection = None | 388 self.startedConnection = None |
374 blocks = property(lambda sel: [i for i in sel.items() if type(i) is BlockItem]) | 389 blocks = property(lambda sel: [i for i in sel.items() if type(i) is BlockItem]) |
375 connections = property(lambda sel: [i for i in sel.items() if type(i) is Connection]) | 390 connections = property(lambda sel: [i for i in sel.items() if type(i) is Connection]) |
376 def saveDiagram(self, filename): | 391 def setDict(self, d): |
377 blocks = self.blocks | 392 for block in d['blocks']: |
378 connections = self.connections | 393 b = BlockItem() |
379 doc = md.Document() | 394 self.addItem(b) |
380 modelElement = doc.createElement('system') | 395 b.Dict = block |
381 doc.appendChild(modelElement) | 396 for con in d['connections']: |
382 for block in blocks: | 397 fromPort = self.findPort(con['fromBlock'], con['fromPort']) |
383 blockElement = doc.createElement("block") | 398 toPort = self.findPort(con['toBlock'], con['toPort']) |
384 x, y = block.scenePos().x(), block.scenePos().y() | 399 c = Connection(fromPort, toPort) |
385 rect = block.rect() | 400 self.addItem(c) |
386 w, h = rect.width(), rect.height() | 401 def getDict(self): |
387 blockElement.setAttribute("name", block.name) | 402 d = {} |
388 blockElement.setAttribute("x", str(int(x))) | 403 d['blocks'] = [b.Dict for b in self.blocks] |
389 blockElement.setAttribute("y", str(int(y))) | 404 d['connections'] = [c.Dict for c in self.connections] |
390 blockElement.setAttribute("width", str(int(w))) | 405 return d |
391 blockElement.setAttribute("height", str(int(h))) | 406 Dict = property(getDict, setDict) |
392 codeNode = doc.createCDATASection(block.code) | |
393 codeElement = doc.createElement('code') | |
394 codeElement.appendChild(codeNode) | |
395 blockElement.appendChild(codeElement) | |
396 for inp in block.inputs: | |
397 portElement = doc.createElement("input") | |
398 portElement.setAttribute("name", inp.name) | |
399 blockElement.appendChild(portElement) | |
400 for outp in block.outputs: | |
401 portElement = doc.createElement("output") | |
402 portElement.setAttribute("name", outp.name) | |
403 blockElement.appendChild(portElement) | |
404 modelElement.appendChild(blockElement) | |
405 for connection in connections: | |
406 connectionElement = doc.createElement("connection") | |
407 fromPort = connection.fromPort.name | |
408 toPort = connection.toPort.name | |
409 fromBlock = connection.fromPort.block.name | |
410 toBlock = connection.toPort.block.name | |
411 connectionElement.setAttribute("fromBlock", fromBlock) | |
412 connectionElement.setAttribute("fromPort", fromPort) | |
413 connectionElement.setAttribute("toBlock", toBlock) | |
414 connectionElement.setAttribute("toPort", toPort) | |
415 for via in connection.vias: | |
416 viaElement = doc.createElement('via') | |
417 viaElement.setAttribute('x', str(int(via.x()))) | |
418 viaElement.setAttribute('y', str(int(via.y()))) | |
419 connectionElement.appendChild(viaElement) | |
420 modelElement.appendChild(connectionElement) | |
421 with open(filename, 'w') as f: | |
422 f.write(doc.toprettyxml()) | |
423 def loadDiagram(self, filename): | |
424 try: | |
425 doc = md.parse(filename) | |
426 except IOError as e: | |
427 print('{0} not found'.format(filename)) | |
428 return | |
429 except xml.parsers.expat.ExpatError as e: | |
430 print('{0}'.format(e)) | |
431 return | |
432 sysElements = doc.getElementsByTagName('system') | |
433 blockElements = doc.getElementsByTagName('block') | |
434 for sysElement in sysElements: | |
435 blockElements = sysElement.getElementsByTagName('block') | |
436 for blockElement in blockElements: | |
437 x = float(blockElement.getAttribute('x')) | |
438 y = float(blockElement.getAttribute('y')) | |
439 w = float(blockElement.getAttribute('width')) | |
440 h = float(blockElement.getAttribute('height')) | |
441 name = blockElement.getAttribute('name') | |
442 block = BlockItem(name) | |
443 self.addItem(block) | |
444 block.setPos(x, y) | |
445 block.sizer.setPos(w, h) | |
446 codeElements = blockElement.getElementsByTagName('code') | |
447 if codeElements: | |
448 cn = codeElements[0].childNodes | |
449 cdatas = [cd for cd in cn if type(cd) is md.CDATASection] | |
450 if len(cdatas) > 0: | |
451 block.code = cdatas[0].data | |
452 # Load ports: | |
453 portElements = blockElement.getElementsByTagName('input') | |
454 for portElement in portElements: | |
455 name = portElement.getAttribute('name') | |
456 inp = PortItem(name, block, 'input') | |
457 block.addInput(inp) | |
458 portElements = blockElement.getElementsByTagName('output') | |
459 for portElement in portElements: | |
460 name = portElement.getAttribute('name') | |
461 outp = PortItem(name, block, 'output') | |
462 block.addOutput(outp) | |
463 connectionElements = sysElement.getElementsByTagName('connection') | |
464 for connectionElement in connectionElements: | |
465 fromBlock = connectionElement.getAttribute('fromBlock') | |
466 fromPort = connectionElement.getAttribute('fromPort') | |
467 toBlock = connectionElement.getAttribute('toBlock') | |
468 toPort = connectionElement.getAttribute('toPort') | |
469 viaElements = connectionElement.getElementsByTagName('via') | |
470 fromPort = self.findPort(fromBlock, fromPort) | |
471 toPort = self.findPort(toBlock, toPort) | |
472 connection = Connection(fromPort, toPort) | |
473 for viaElement in viaElements: | |
474 x = int(viaElement.getAttribute('x')) | |
475 y = int(viaElement.getAttribute('y')) | |
476 connection.addHandle(QPointF(x, y)) | |
477 self.addItem(connection) | |
478 def findPort(self, blockname, portname): | 407 def findPort(self, blockname, portname): |
479 block = self.findBlock(blockname) | 408 block = self.findBlock(blockname) |
480 if block: | 409 if block: |
481 for port in block.inputs + block.outputs: | 410 for port in block.inputs + block.outputs: |
482 if port.name == portname: | 411 if port.name == portname: |
483 return port | 412 return port |
484 def findBlock(self, blockname): | 413 def findBlock(self, blockname): |
485 items = self.items() | 414 blocks = [item for item in self.items() if type(item) is BlockItem] |
486 blocks = [item for item in items if type(item) is BlockItem] | |
487 for block in blocks: | 415 for block in blocks: |
488 if block.name == blockname: | 416 if block.name == blockname: |
489 return block | 417 return block |
490 def addNewBlock(self, pos, name): | 418 def addNewBlock(self, pos, name): |
491 blocknames = [item.name for item in self.items() if type(item) is BlockItem] | 419 blocknames = [item.name for item in self.items() if type(item) is BlockItem] |
528 painter = QPainter(pixmap) | 456 painter = QPainter(pixmap) |
529 painter.fillRect(10, 10, 40, 40, Qt.blue) | 457 painter.fillRect(10, 10, 40, 40, Qt.blue) |
530 painter.setBrush(Qt.yellow) | 458 painter.setBrush(Qt.yellow) |
531 painter.drawEllipse(20, 20, 20, 20) | 459 painter.drawEllipse(20, 20, 20, 20) |
532 painter.end() | 460 painter.end() |
533 | |
534 # Fill library: | 461 # Fill library: |
535 self.libItems = [] | 462 for name in ['Block', 'Uber unit', 'Device']: |
536 self.libItems.append( QStandardItem(QIcon(pixmap), 'Block') ) | 463 self.libraryModel.appendRow(QStandardItem(QIcon(pixmap), name)) |
537 self.libItems.append( QStandardItem(QIcon(pixmap), 'Uber Unit') ) | |
538 self.libItems.append( QStandardItem(QIcon(pixmap), 'Device') ) | |
539 for i in self.libItems: | |
540 self.libraryModel.appendRow(i) | |
541 self.setModel(self.libraryModel) | 464 self.setModel(self.libraryModel) |
542 self.setViewMode(self.IconMode) | 465 self.setViewMode(self.IconMode) |
543 self.setDragDropMode(self.DragOnly) | 466 self.setDragDropMode(self.DragOnly) |
467 | |
468 class ModelTree(QTreeView): | |
469 def __init__(self): | |
470 super(ModelTree, self).__init__() | |
471 m = ModelHierarchyModel() | |
472 self.setModel(m) | |
473 | |
474 def loadModel(filename): | |
475 with open(filename, 'r') as f: | |
476 data = f.read() | |
477 d = json.loads(data) | |
478 print('Loaded json:', d) | |
479 s = DiagramScene() | |
480 s.Dict = d | |
481 return s | |
544 | 482 |
545 class Main(QMainWindow): | 483 class Main(QMainWindow): |
546 def __init__(self): | 484 def __init__(self): |
547 super(Main, self).__init__(None) | 485 super(Main, self).__init__(None) |
548 self.editor = EditorGraphicsView() | 486 self.editor = EditorGraphicsView() |
558 act('Load', QKeySequence.Open, self.editor.load) | 496 act('Load', QKeySequence.Open, self.editor.load) |
559 act('Full screen', QKeySequence("F11"), self.toggleFullScreen) | 497 act('Full screen', QKeySequence("F11"), self.toggleFullScreen) |
560 act('Fit in view', QKeySequence("F8"), self.editor.zoomAll) | 498 act('Fit in view', QKeySequence("F8"), self.editor.zoomAll) |
561 act('Go down', QKeySequence(Qt.Key_Down), self.editor.goDown) | 499 act('Go down', QKeySequence(Qt.Key_Down), self.editor.goDown) |
562 act('Go up', QKeySequence(Qt.Key_Up), self.editor.goUp) | 500 act('Go up', QKeySequence(Qt.Key_Up), self.editor.goUp) |
563 self.library = LibraryWidget() | 501 def addDock(name, widget): |
564 libraryDock = QDockWidget('Library', self) | 502 dock = QDockWidget(name, self) |
565 libraryDock.setWidget(self.library) | 503 dock.setWidget(widget) |
566 self.addDockWidget(Qt.LeftDockWidgetArea, libraryDock) | 504 self.addDockWidget(Qt.LeftDockWidgetArea, dock) |
567 self.editor.scene().loadDiagram('diagram2.usd') | 505 addDock('Library', LibraryWidget()) |
506 addDock('Model tree', ModelTree()) | |
568 def toggleFullScreen(self): | 507 def toggleFullScreen(self): |
569 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) | 508 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) |
570 self.editor.zoomAll() | 509 self.editor.zoomAll() |
571 | 510 |
572 if __name__ == '__main__': | 511 if __name__ == '__main__': |
575 sys.exit(1) | 514 sys.exit(1) |
576 app = QApplication(sys.argv) | 515 app = QApplication(sys.argv) |
577 main = Main() | 516 main = Main() |
578 main.show() | 517 main.show() |
579 main.resize(700, 500) | 518 main.resize(700, 500) |
519 main.editor.model = loadModel('diagram4.usd') | |
580 main.editor.zoomAll() | 520 main.editor.zoomAll() |
581 app.exec_() | 521 app.exec_() |
582 | 522 |