Mercurial > lcfOS
comparison applications/lab/diagrameditor.py @ 49:2b07ab2c0dc4
Added line saving
author | windel |
---|---|
date | Wed, 04 Apr 2012 07:30:21 +0200 |
parents | a309982ab4d5 |
children | 38ff8e178fe4 |
comparison
equal
deleted
inserted
replaced
48:a309982ab4d5 | 49:2b07ab2c0dc4 |
---|---|
7 | 7 |
8 """ | 8 """ |
9 This script implements a basic diagram editor. | 9 This script implements a basic diagram editor. |
10 """ | 10 """ |
11 | 11 |
12 class Connection: | 12 class Connection(QGraphicsPathItem): |
13 """ | 13 """ |
14 - fromPort | 14 - fromPort |
15 - list of line items in between | 15 - list of line items in between |
16 - toPort | 16 - toPort |
17 """ | 17 """ |
18 def __init__(self, fromPort, toPort): | 18 def __init__(self, fromPort, toPort): |
19 super(Connection, self).__init__() | |
19 self.pos1 = None | 20 self.pos1 = None |
20 self.pos2 = None | 21 self.pos2 = None |
21 self.pathItem = QGraphicsPathItem() | |
22 pen = QPen() | 22 pen = QPen() |
23 pen.setWidth(4) | 23 pen.setWidth(2) |
24 pen.setColor(Qt.blue) | 24 pen.setColor(Qt.blue) |
25 pen.setCapStyle(Qt.RoundCap) | 25 pen.setCapStyle(Qt.RoundCap) |
26 self.pathItem.setPen(pen) | 26 self.setPen(pen) |
27 #self.pathItem.setFlags(self.pathItem.ItemIsSelectable) | 27 editor.diagramScene.addItem(self) |
28 editor.diagramScene.addItem(self.pathItem) | |
29 self.setFromPort(fromPort) | 28 self.setFromPort(fromPort) |
30 self.setToPort(toPort) | 29 self.setToPort(toPort) |
31 def setFromPort(self, fromPort): | 30 def setFromPort(self, fromPort): |
32 self.fromPort = fromPort | 31 self.fromPort = fromPort |
33 if self.fromPort: | 32 if self.fromPort: |
63 x4, y4 = self.pos2.x(), self.pos2.y() | 62 x4, y4 = self.pos2.x(), self.pos2.y() |
64 | 63 |
65 def stripHits(hits): | 64 def stripHits(hits): |
66 """ Helper function that removes object hits """ | 65 """ Helper function that removes object hits """ |
67 hits = [hit for hit in hits if type(hit) is BlockItem] | 66 hits = [hit for hit in hits if type(hit) is BlockItem] |
68 if self.pathItem in hits: | 67 if self in hits: |
69 hits.remove(self.pathItem) | 68 hits.remove(self) |
70 if self.fromPort in hits: | 69 if self.fromPort in hits: |
71 hits.remove(self.fromPort) | 70 hits.remove(self.fromPort) |
72 if self.toPort in hits: | 71 if self.toPort in hits: |
73 hits.remove(self.toPort) | 72 hits.remove(self.toPort) |
74 return hits | 73 return hits |
110 path.lineTo(p4) | 109 path.lineTo(p4) |
111 | 110 |
112 hits = stripHits(ds.items(path)) | 111 hits = stripHits(ds.items(path)) |
113 | 112 |
114 #print('Items:', hits) | 113 #print('Items:', hits) |
115 self.pathItem.setPath(path) | 114 self.setPath(path) |
116 | 115 |
117 def delete(self): | 116 def delete(self): |
118 editor.diagramScene.removeItem(self.pathItem) | 117 editor.diagramScene.removeItem(self) |
119 # Remove position update callbacks: | 118 # Remove position update callbacks: |
120 | 119 |
121 class ParameterDialog(QDialog): | 120 class ParameterDialog(QDialog): |
122 def __init__(self, parent = None): | 121 def __init__(self, block, parent = None): |
123 super(ParameterDialog, self).__init__(parent) | 122 super(ParameterDialog, self).__init__(parent) |
123 self.block = block | |
124 self.button = QPushButton('Ok', self) | 124 self.button = QPushButton('Ok', self) |
125 l = QVBoxLayout(self) | 125 l = QGridLayout(self) |
126 l.addWidget(self.button) | 126 l.addWidget(QLabel('Name:', self), 0, 0) |
127 self.nameEdit = QLineEdit(self.block.label.toPlainText()) | |
128 l.addWidget(self.nameEdit, 0, 1) | |
129 l.addWidget(self.button, 1, 0) | |
127 self.button.clicked.connect(self.OK) | 130 self.button.clicked.connect(self.OK) |
128 def OK(self): | 131 def OK(self): |
132 self.block.label.setPlainText(self.nameEdit.text()) | |
129 self.close() | 133 self.close() |
130 | 134 |
131 class PortItem(QGraphicsEllipseItem): | 135 class PortItem(QGraphicsEllipseItem): |
132 """ Represents a port to a subsystem """ | 136 """ Represents a port to a subsystem """ |
133 def __init__(self, name, parent=None): | 137 def __init__(self, name, parent=None): |
201 #self.sizer.setVisible(False) | 205 #self.sizer.setVisible(False) |
202 self.sizer.setFlag(self.sizer.ItemIsSelectable, True) | 206 self.sizer.setFlag(self.sizer.ItemIsSelectable, True) |
203 | 207 |
204 # Inputs and outputs of the block: | 208 # Inputs and outputs of the block: |
205 self.inputs = [] | 209 self.inputs = [] |
206 self.inputs.append( PortItem('a', self) ) | 210 #self.inputs.append( PortItem('a', self) ) |
207 self.inputs.append( PortItem('b', self) ) | 211 #self.inputs.append( PortItem('b', self) ) |
208 self.inputs.append( PortItem('c', self) ) | 212 #self.inputs.append( PortItem('c', self) ) |
209 self.outputs = [] | 213 self.outputs = [] |
210 self.outputs.append( PortItem('y', self) ) | 214 #self.outputs.append( PortItem('y', self) ) |
211 # Update size: | 215 # Update size: |
212 self.changeSize(w, h) | 216 self.changeSize(w, h) |
213 def editParameters(self): | 217 def editParameters(self): |
214 pd = ParameterDialog(self.window()) | 218 pd = ParameterDialog(self, self.window()) |
215 pd.exec_() | 219 pd.exec_() |
216 def addInput(self, i): | 220 def addInput(self, i): |
217 self.inputs.append(i) | 221 self.inputs.append(i) |
222 self.updateSize() | |
218 def addOutput(self, o): | 223 def addOutput(self, o): |
219 self.outputs.append(o) | 224 self.outputs.append(o) |
225 self.updateSize() | |
220 | 226 |
221 def contextMenuEvent(self, event): | 227 def contextMenuEvent(self, event): |
222 menu = QMenu() | 228 menu = QMenu() |
223 da = menu.addAction('Delete') | 229 da = menu.addAction('Delete') |
224 da.triggered.connect(self.delete) | 230 da.triggered.connect(self.delete) |
228 | 234 |
229 def delete(self): | 235 def delete(self): |
230 editor.diagramScene.removeItem(self) | 236 editor.diagramScene.removeItem(self) |
231 # TODO: remove connections | 237 # TODO: remove connections |
232 | 238 |
233 def changeSize(self, w, h): | 239 def updateSize(self): |
234 """ Resize block function """ | 240 rect = self.rect() |
235 # Limit the block size: | 241 h, w = rect.height(), rect.width() |
236 if h < 20: | |
237 h = 20 | |
238 if w < 40: | |
239 w = 40 | |
240 self.setRect(0.0, 0.0, w, h) | |
241 # center label: | |
242 rect = self.label.boundingRect() | |
243 lw, lh = rect.width(), rect.height() | |
244 lx = (w - lw) / 2 | |
245 ly = (h - lh) / 2 | |
246 self.label.setPos(lx, ly) | |
247 # Update port positions: | |
248 if len(self.inputs) == 1: | 242 if len(self.inputs) == 1: |
249 self.inputs[0].setPos(-4, h / 2) | 243 self.inputs[0].setPos(-4, h / 2) |
250 elif len(self.inputs) > 1: | 244 elif len(self.inputs) > 1: |
251 y = 5 | 245 y = 5 |
252 dy = (h - 10) / (len(self.inputs) - 1) | 246 dy = (h - 10) / (len(self.inputs) - 1) |
259 y = 5 | 253 y = 5 |
260 dy = (h - 10) / (len(self.outputs) + 0) | 254 dy = (h - 10) / (len(self.outputs) + 0) |
261 for outp in self.outputs: | 255 for outp in self.outputs: |
262 outp.setPos(w+4, y) | 256 outp.setPos(w+4, y) |
263 y += dy | 257 y += dy |
258 | |
259 def changeSize(self, w, h): | |
260 """ Resize block function """ | |
261 # Limit the block size: | |
262 if h < 20: | |
263 h = 20 | |
264 if w < 40: | |
265 w = 40 | |
266 self.setRect(0.0, 0.0, w, h) | |
267 # center label: | |
268 rect = self.label.boundingRect() | |
269 lw, lh = rect.width(), rect.height() | |
270 lx = (w - lw) / 2 | |
271 ly = (h - lh) / 2 | |
272 self.label.setPos(lx, ly) | |
273 # Update port positions: | |
274 self.updateSize() | |
264 return w, h | 275 return w, h |
265 | 276 |
266 class EditorGraphicsView(QGraphicsView): | 277 class EditorGraphicsView(QGraphicsView): |
267 def __init__(self, scene, parent=None): | 278 def __init__(self, scene, parent=None): |
268 QGraphicsView.__init__(self, scene, parent) | 279 QGraphicsView.__init__(self, scene, parent) |
274 event.accept() | 285 event.accept() |
275 def dropEvent(self, event): | 286 def dropEvent(self, event): |
276 if event.mimeData().hasFormat('component/name'): | 287 if event.mimeData().hasFormat('component/name'): |
277 name = bytes(event.mimeData().data('component/name')).decode() | 288 name = bytes(event.mimeData().data('component/name')).decode() |
278 b1 = BlockItem(name) | 289 b1 = BlockItem(name) |
279 print(event.pos()) | |
280 b1.setPos(self.mapToScene(event.pos())) | 290 b1.setPos(self.mapToScene(event.pos())) |
281 print(self.mapToScene(event.pos())) | |
282 self.scene().addItem(b1) | 291 self.scene().addItem(b1) |
283 | 292 |
284 class LibraryModel(QStandardItemModel): | 293 class LibraryModel(QStandardItemModel): |
285 def __init__(self, parent=None): | 294 def __init__(self, parent=None): |
286 QStandardItemModel.__init__(self, parent) | 295 QStandardItemModel.__init__(self, parent) |
359 self.saveDiagram('diagram2.usd') | 368 self.saveDiagram('diagram2.usd') |
360 | 369 |
361 def saveDiagram(self, filename): | 370 def saveDiagram(self, filename): |
362 items = self.diagramScene.items() | 371 items = self.diagramScene.items() |
363 blocks = [item for item in items if type(item) is BlockItem] | 372 blocks = [item for item in items if type(item) is BlockItem] |
373 connections = [item for item in items if type(item) is Connection] | |
364 | 374 |
365 doc = md.Document() | 375 doc = md.Document() |
366 modelElement = doc.createElement('system') | 376 modelElement = doc.createElement('system') |
367 doc.appendChild(modelElement) | 377 doc.appendChild(modelElement) |
368 for block in blocks: | 378 for block in blocks: |
369 blockElement = doc.createElement("block") | 379 blockElement = doc.createElement("block") |
370 x, y = block.scenePos().x(), block.scenePos().y() | 380 x, y = block.scenePos().x(), block.scenePos().y() |
371 rect = block.rect() | 381 rect = block.rect() |
372 w, h = rect.width(), rect.height() | 382 w, h = rect.width(), rect.height() |
373 blockElement.setAttribute("name", block.label.toPlainText()) | 383 blockElement.setAttribute("name", block.label.toPlainText()) |
374 blockElement.setAttribute("x", str(x)) | 384 blockElement.setAttribute("x", str(int(x))) |
375 blockElement.setAttribute("y", str(y)) | 385 blockElement.setAttribute("y", str(int(y))) |
376 blockElement.setAttribute("width", str(w)) | 386 blockElement.setAttribute("width", str(int(w))) |
377 blockElement.setAttribute("height", str(h)) | 387 blockElement.setAttribute("height", str(int(h))) |
378 for inp in block.inputs: | 388 for inp in block.inputs: |
379 portElement = doc.createElement("input") | 389 portElement = doc.createElement("input") |
380 portElement.setAttribute("name", inp.name) | 390 portElement.setAttribute("name", inp.name) |
381 blockElement.appendChild(portElement) | 391 blockElement.appendChild(portElement) |
382 for outp in block.outputs: | 392 for outp in block.outputs: |
383 portElement = doc.createElement("output") | 393 portElement = doc.createElement("output") |
384 portElement.setAttribute("name", outp.name) | 394 portElement.setAttribute("name", outp.name) |
385 blockElement.appendChild(portElement) | 395 blockElement.appendChild(portElement) |
386 modelElement.appendChild(blockElement) | 396 modelElement.appendChild(blockElement) |
397 for connection in connections: | |
398 print(connection) | |
399 connectionElement = doc.createElement("connection") | |
400 fromPort = connection.fromPort.name | |
401 toPort = connection.toPort.name | |
402 fromBlock = "" | |
403 toBlock = "" | |
404 connectionElement.setAttribute("fromBlock", fromBlock) | |
405 connectionElement.setAttribute("fromPort", fromPort) | |
406 connectionElement.setAttribute("toBlock", toBlock) | |
407 connectionElement.setAttribute("toPort", toPort) | |
408 modelElement.appendChild(connectionElement) | |
387 with open(filename, 'w') as f: | 409 with open(filename, 'w') as f: |
388 f.write(doc.toprettyxml()) | 410 f.write(doc.toprettyxml()) |
389 | 411 |
390 def loadDiagram(self, filename): | 412 def loadDiagram(self, filename): |
391 try: | 413 try: |
406 block = BlockItem(name) | 428 block = BlockItem(name) |
407 self.diagramScene.addItem(block) | 429 self.diagramScene.addItem(block) |
408 block.setPos(x, y) | 430 block.setPos(x, y) |
409 block.sizer.setPos(w, h) | 431 block.sizer.setPos(w, h) |
410 # Load ports: | 432 # Load ports: |
411 portElements = blockElement.getElementsByTagName('port') | 433 portElements = blockElement.getElementsByTagName('input') |
412 for portElement in portElements: | 434 for portElement in portElements: |
413 print(portElement) | 435 name = portElement.getAttribute('name') |
436 inp = PortItem(name, block) | |
437 block.addInput(inp) | |
438 portElements = blockElement.getElementsByTagName('output') | |
439 for portElement in portElements: | |
440 name = portElement.getAttribute('name') | |
441 outp = PortItem(name, block) | |
442 block.addOutput(outp) | |
414 connectionElements = sysElement.getElementsByTagName('connection') | 443 connectionElements = sysElement.getElementsByTagName('connection') |
415 for connectionElement in connectionElements: | 444 for connectionElement in connectionElements: |
416 print(connectionElement) | 445 print(connectionElement) |
417 self.zoomAll() | 446 self.zoomAll() |
418 | 447 |
421 def startConnection(self, port): | 450 def startConnection(self, port): |
422 self.startedConnection = Connection(port, None) | 451 self.startedConnection = Connection(port, None) |
423 def zoomAll(self): | 452 def zoomAll(self): |
424 """ zoom to fit all items """ | 453 """ zoom to fit all items """ |
425 rect = self.diagramScene.itemsBoundingRect() | 454 rect = self.diagramScene.itemsBoundingRect() |
426 print(rect) | |
427 self.diagramView.fitInView(rect, Qt.KeepAspectRatio) | 455 self.diagramView.fitInView(rect, Qt.KeepAspectRatio) |
428 def sceneMouseMoveEvent(self, event): | 456 def sceneMouseMoveEvent(self, event): |
429 if self.startedConnection: | 457 if self.startedConnection: |
430 pos = event.scenePos() | 458 pos = event.scenePos() |
431 self.startedConnection.setEndPos(pos) | 459 self.startedConnection.setEndPos(pos) |