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