Mercurial > lcfOS
comparison python/apps/diagrameditor.py @ 63:32078200cdd6
Several move action
author | windel |
---|---|
date | Sun, 07 Oct 2012 17:04:10 +0200 |
parents | python/lab/diagrameditor.py@fd7d5069734e |
children | b01311fb3be7 |
comparison
equal
deleted
inserted
replaced
62:fd7d5069734e | 63:32078200cdd6 |
---|---|
1 #!/usr/bin/python | |
2 | |
3 from PyQt4.QtGui import * | |
4 from PyQt4.QtCore import * | |
5 import sys | |
6 import xml.dom.minidom as md | |
7 import xml | |
8 | |
9 """ | |
10 Author: Windel Bouwman | |
11 Year: 2012 | |
12 Description: This script implements a diagram editor. | |
13 run with python 3.x as: | |
14 $ python [thisfile.py] | |
15 """ | |
16 | |
17 class ArrowHead(QGraphicsPathItem): | |
18 def __init__(self, parent): | |
19 super(ArrowHead, self).__init__(parent) | |
20 arrowPath = QPainterPath(QPointF(0.0, 0.0)) | |
21 arrowPath.lineTo(-6.0, 10.0) | |
22 arrowPath.lineTo(6.0, 10.0) | |
23 arrowPath.lineTo(0.0, 0.0) | |
24 self.setPath(arrowPath) | |
25 pen = QPen(Qt.blue, 2) | |
26 self.setPen(pen) | |
27 self.setBrush(QBrush(pen.color())) | |
28 self.myshape = QPainterPath() | |
29 | |
30 class Connection(QGraphicsPathItem): | |
31 """ Implementation of a connection between blocks """ | |
32 def __init__(self, fromPort, toPort): | |
33 super(Connection, self).__init__() | |
34 self.pos1 = None | |
35 self.pos2 = None | |
36 self.fromPort = None | |
37 self.toPort = None | |
38 self.setFlag(self.ItemIsSelectable, True) | |
39 self.setFlag(self.ItemClipsToShape, True) | |
40 self.pen = QPen(Qt.blue, 2) | |
41 self.pen.setCapStyle(Qt.RoundCap) | |
42 self.setPen(self.pen) | |
43 self.arrowhead = ArrowHead(self) | |
44 self.vias = [] | |
45 self.setFromPort(fromPort) | |
46 self.setToPort(toPort) | |
47 def mouseDoubleClickEvent(self, event): | |
48 pos = event.scenePos() | |
49 pts = [self.pos1] + [v.pos() for v in self.vias] + [self.pos2] | |
50 idx = 0 | |
51 tidx = 0 | |
52 for p1, p2 in zip(pts[0:-1], pts[1:]): | |
53 l1 = QLineF(p1, p2) | |
54 l2 = QLineF(p1, pos) | |
55 l3 = QLineF(pos, p2) | |
56 d = l2.length() + l3.length() - l1.length() | |
57 if d < 5: | |
58 tidx = idx | |
59 idx += 1 | |
60 self.addHandle(pos, tidx) | |
61 | |
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 | |
75 def setFromPort(self, fromPort): | |
76 if self.fromPort: | |
77 self.fromPort.posCallbacks.remove(self.setBeginPos) | |
78 self.fromPort.connection = None | |
79 self.fromPort = fromPort | |
80 if self.fromPort: | |
81 self.fromPort.connection = self | |
82 self.setBeginPos(fromPort.scenePos()) | |
83 self.fromPort.posCallbacks.append(self.setBeginPos) | |
84 def setToPort(self, toPort): | |
85 if self.toPort: | |
86 self.toPort.posCallbacks.remove(self.setEndPos) | |
87 self.toPort.connection = None | |
88 self.toPort = toPort | |
89 if self.toPort: | |
90 self.setEndPos(toPort.scenePos()) | |
91 self.toPort.connection = self | |
92 self.toPort.posCallbacks.append(self.setEndPos) | |
93 def releasePorts(self): | |
94 self.setFromPort(None) | |
95 self.setToPort(None) | |
96 def setBeginPos(self, pos1): | |
97 self.pos1 = pos1 | |
98 self.updateLineStukken() | |
99 def setEndPos(self, endpos): | |
100 self.pos2 = endpos | |
101 self.updateLineStukken() | |
102 def itemChange(self, change, value): | |
103 if change == self.ItemSelectedHasChanged: | |
104 for via in self.vias: | |
105 via.setVisible(value) | |
106 return super(Connection, self).itemChange(change, value) | |
107 def shape(self): | |
108 return self.myshape | |
109 def updateLineStukken(self): | |
110 """ | |
111 This algorithm determines the optimal routing of all signals. | |
112 TODO: implement nice automatic line router | |
113 """ | |
114 if self.pos1 is None or self.pos2 is None: | |
115 return | |
116 pts = [self.pos1] + [v.pos() for v in self.vias] + [self.pos2] | |
117 self.arrowhead.setPos(self.pos2) | |
118 if pts[-1].x() < pts[-2].x(): | |
119 self.arrowhead.setRotation(-90) | |
120 else: | |
121 self.arrowhead.setRotation(90) | |
122 path = QPainterPath(pts[0]) | |
123 for pt in pts[1:]: | |
124 path.lineTo(pt) | |
125 self.setPath(path) | |
126 """ Create a shape outline using the path stroker """ | |
127 s = super(Connection, self).shape() | |
128 pps = QPainterPathStroker() | |
129 pps.setWidth(10) | |
130 self.myshape = pps.createStroke(s).simplified() | |
131 | |
132 class ParameterDialog(QDialog): | |
133 def __init__(self, block, parent = None): | |
134 super(ParameterDialog, self).__init__(parent) | |
135 self.block = block | |
136 self.button = QPushButton('Ok', self) | |
137 l = QGridLayout(self) | |
138 l.addWidget(QLabel('Name:', self), 0, 0) | |
139 self.nameEdit = QLineEdit(self.block.name) | |
140 l.addWidget(self.nameEdit, 0, 1) | |
141 l.addWidget(QLabel('Code:', self), 1, 0) | |
142 self.codeEdit = QTextEdit(self) | |
143 self.codeEdit.setPlainText(self.block.code) | |
144 l.addWidget(self.codeEdit, 1, 1) | |
145 l.addWidget(self.button, 2, 0, 1, 2) | |
146 self.button.clicked.connect(self.OK) | |
147 def OK(self): | |
148 self.block.setName(self.nameEdit.text()) | |
149 self.block.code = self.codeEdit.toPlainText() | |
150 self.close() | |
151 | |
152 class PortItem(QGraphicsPathItem): | |
153 """ Represents a port to a subsystem """ | |
154 def __init__(self, name, block, direction): | |
155 super(PortItem, self).__init__(block) | |
156 self.connection = None | |
157 path = QPainterPath() | |
158 d = 10.0 | |
159 if direction == 'input': | |
160 path.moveTo(-d, -d) | |
161 path.lineTo(0.0, 0.0) | |
162 path.lineTo(-d, d) | |
163 else: | |
164 path.moveTo(0.0, -d) | |
165 path.lineTo(d, 0.0) | |
166 path.lineTo(0.0, d) | |
167 self.setPath(path) | |
168 self.direction = direction | |
169 self.block = block | |
170 self.setCursor(QCursor(Qt.CrossCursor)) | |
171 pen = QPen(Qt.blue, 2) | |
172 pen.setCapStyle(Qt.RoundCap) | |
173 self.setPen(pen) | |
174 self.name = name | |
175 self.textItem = QGraphicsTextItem(name, self) | |
176 self.setName(name) | |
177 self.posCallbacks = [] | |
178 self.setFlag(self.ItemSendsScenePositionChanges, True) | |
179 def setName(self, name): | |
180 self.name = name | |
181 self.textItem.setPlainText(name) | |
182 rect = self.textItem.boundingRect() | |
183 lw, lh = rect.width(), rect.height() | |
184 if self.direction == 'input': | |
185 lx = 3 | |
186 else: | |
187 lx = -3 - lw | |
188 self.textItem.setPos(lx, -lh / 2) | |
189 def itemChange(self, change, value): | |
190 if change == self.ItemScenePositionHasChanged: | |
191 for cb in self.posCallbacks: | |
192 cb(value) | |
193 return value | |
194 return super(PortItem, self).itemChange(change, value) | |
195 def mousePressEvent(self, event): | |
196 if self.direction == 'output': | |
197 self.scene().startConnection(self) | |
198 | |
199 class OutputPort(PortItem): | |
200 # TODO: create a subclass OR make a member porttype | |
201 pass | |
202 | |
203 # Block part: | |
204 class HandleItem(QGraphicsEllipseItem): | |
205 """ A handle that can be moved by the mouse """ | |
206 def __init__(self, parent=None): | |
207 dx = 13.0 | |
208 super(HandleItem, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent) | |
209 self.posChangeCallbacks = [] | |
210 self.setBrush(QBrush(Qt.white)) | |
211 self.setFlag(self.ItemSendsScenePositionChanges, True) | |
212 self.setFlag(self.ItemIsMovable, True) | |
213 self.setVisible(False) | |
214 self.setCursor(QCursor(Qt.SizeFDiagCursor)) | |
215 def mouseMoveEvent(self, event): | |
216 """ Move function without moving the other selected elements """ | |
217 p = self.mapToParent(event.pos()) | |
218 self.setPos(p) | |
219 def mySetPos(self, p): | |
220 # TODO: use this instead of itemChange? | |
221 self.setPos(p) | |
222 def itemChange(self, change, value): | |
223 if change == self.ItemPositionChange: | |
224 for cb in self.posChangeCallbacks: | |
225 res = cb(value) | |
226 if res: | |
227 value = res | |
228 return value | |
229 # Call superclass method: | |
230 return super(HandleItem, self).itemChange(change, value) | |
231 | |
232 def uniqify(name, names): | |
233 newname = name | |
234 i = 1 | |
235 while newname in names: | |
236 newname = name + str(i) | |
237 i += 1 | |
238 return newname | |
239 | |
240 class BlockItem(QGraphicsRectItem): | |
241 """ | |
242 Represents a block in the diagram | |
243 Has an x and y and width and height | |
244 width and height can only be adjusted with a tip in the lower right corner. | |
245 | |
246 - in and output ports | |
247 - parameters | |
248 - description | |
249 """ | |
250 def __init__(self, name='Untitled', parent=None): | |
251 super(BlockItem, self).__init__(parent) | |
252 # Properties of the rectangle: | |
253 self.setPen(QPen(Qt.blue, 2)) | |
254 self.setBrush(QBrush(Qt.lightGray)) | |
255 self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) | |
256 self.setFlag(self.ItemSendsScenePositionChanges, True) | |
257 self.setCursor(QCursor(Qt.PointingHandCursor)) | |
258 self.label = QGraphicsTextItem(name, self) | |
259 self.name = name | |
260 self.code = '' | |
261 # Create corner for resize: | |
262 self.sizer = HandleItem(self) | |
263 self.sizer.posChangeCallbacks.append(self.changeSize) # Connect the callback | |
264 button = QPushButton('+in') | |
265 button.clicked.connect(self.newInputPort) | |
266 self.buttonItemAddInput = QGraphicsProxyWidget(self) | |
267 self.buttonItemAddInput.setWidget(button) | |
268 self.buttonItemAddInput.setVisible(False) | |
269 button = QPushButton('+out') | |
270 button.clicked.connect(self.newOutputPort) | |
271 self.buttonItemAddOutput = QGraphicsProxyWidget(self) | |
272 self.buttonItemAddOutput.setWidget(button) | |
273 self.buttonItemAddOutput.setVisible(False) | |
274 | |
275 # Inputs and outputs of the block: | |
276 self.inputs = [] | |
277 self.outputs = [] | |
278 # Update size: | |
279 self.sizer.mySetPos(QPointF(60, 40)) # This is a better resize function | |
280 def editParameters(self): | |
281 pd = ParameterDialog(self, self.window()) | |
282 pd.exec_() | |
283 def mouseDoubleClickEvent(self, event): | |
284 self.editParameters() | |
285 def newInputPort(self): | |
286 names = [i.name for i in self.inputs + self.outputs] | |
287 self.addInput(PortItem(uniqify('in', names), self, 'input')) | |
288 def newOutputPort(self): | |
289 names = [i.name for i in self.inputs + self.outputs] | |
290 self.addOutput(PortItem(uniqify('out', names), self, 'output')) | |
291 def setName(self, name): | |
292 self.name = name | |
293 self.label.setPlainText(name) | |
294 def addInput(self, i): | |
295 self.inputs.append(i) | |
296 self.updateSize() | |
297 def addOutput(self, o): | |
298 self.outputs.append(o) | |
299 self.updateSize() | |
300 | |
301 def contextMenuEvent(self, event): | |
302 menu = QMenu() | |
303 pa = menu.addAction('Parameters') | |
304 pa.triggered.connect(self.editParameters) | |
305 menu.exec_(event.screenPos()) | |
306 def itemChange(self, change, value): | |
307 if change == self.ItemSelectedHasChanged: | |
308 self.sizer.setVisible(value) | |
309 self.buttonItemAddInput.setVisible(value) | |
310 self.buttonItemAddOutput.setVisible(value) | |
311 return super(BlockItem, self).itemChange(change, value) | |
312 | |
313 def updateSize(self): | |
314 rect = self.rect() | |
315 h, w = rect.height(), rect.width() | |
316 self.buttonItemAddInput.setPos(0, h + 4) | |
317 self.buttonItemAddOutput.setPos(w+10, h+4) | |
318 if len(self.inputs) == 1: | |
319 self.inputs[0].setPos(0.0, h / 2) | |
320 elif len(self.inputs) > 1: | |
321 y = 15 | |
322 dy = (h - 30) / (len(self.inputs) - 1) | |
323 for inp in self.inputs: | |
324 inp.setPos(0.0, y) | |
325 y += dy | |
326 if len(self.outputs) == 1: | |
327 self.outputs[0].setPos(w, h / 2) | |
328 elif len(self.outputs) > 1: | |
329 y = 15 | |
330 dy = (h - 30) / (len(self.outputs) - 1) | |
331 for outp in self.outputs: | |
332 outp.setPos(w, y) | |
333 y += dy | |
334 | |
335 def changeSize(self, p): | |
336 """ Resize block function """ | |
337 w, h = p.x(), p.y() | |
338 # Limit the block size: | |
339 if h < 20: | |
340 h = 20 | |
341 if w < 40: | |
342 w = 40 | |
343 self.setRect(0.0, 0.0, w, h) | |
344 # center label: | |
345 rect = self.label.boundingRect() | |
346 lw, lh = rect.width(), rect.height() | |
347 lx = (w - lw) / 2 | |
348 ly = (h - lh) / 2 | |
349 self.label.setPos(lx, ly) | |
350 # Update port positions: | |
351 self.updateSize() | |
352 return QPointF(w, h) | |
353 | |
354 class EditorGraphicsView(QGraphicsView): | |
355 def __init__(self, scene, parent=None): | |
356 QGraphicsView.__init__(self, scene, parent) | |
357 self.setDragMode(QGraphicsView.RubberBandDrag) | |
358 def wheelEvent(self, event): | |
359 pos = event.pos() | |
360 posbefore = self.mapToScene(pos) | |
361 degrees = event.delta() / 8.0 | |
362 sx = (100.0 + degrees) / 100.0 | |
363 self.scale(sx, sx) | |
364 event.accept() | |
365 def dragEnterEvent(self, event): | |
366 if event.mimeData().hasFormat('component/name'): | |
367 event.accept() | |
368 def dragMoveEvent(self, event): | |
369 if event.mimeData().hasFormat('component/name'): | |
370 event.accept() | |
371 def dropEvent(self, event): | |
372 if event.mimeData().hasFormat('component/name'): | |
373 name = bytes(event.mimeData().data('component/name')).decode() | |
374 pos = self.mapToScene(event.pos()) | |
375 self.scene().addNewBlock(pos, name) | |
376 | |
377 class LibraryModel(QStandardItemModel): | |
378 def __init__(self, parent=None): | |
379 QStandardItemModel.__init__(self, parent) | |
380 def mimeTypes(self): | |
381 return ['component/name'] | |
382 def mimeData(self, idxs): | |
383 mimedata = QMimeData() | |
384 for idx in idxs: | |
385 if idx.isValid(): | |
386 txt = self.data(idx, Qt.DisplayRole) # python 3 | |
387 mimedata.setData('component/name', txt) | |
388 return mimedata | |
389 | |
390 class DiagramScene(QGraphicsScene): | |
391 """ Save and load and deletion of item""" | |
392 def __init__(self, parent=None): | |
393 super(DiagramScene, self).__init__(parent) | |
394 self.startedConnection = None | |
395 | |
396 def saveDiagram(self, filename): | |
397 items = self.items() | |
398 blocks = [item for item in items if type(item) is BlockItem] | |
399 connections = [item for item in items if type(item) is Connection] | |
400 | |
401 doc = md.Document() | |
402 modelElement = doc.createElement('system') | |
403 doc.appendChild(modelElement) | |
404 for block in blocks: | |
405 blockElement = doc.createElement("block") | |
406 x, y = block.scenePos().x(), block.scenePos().y() | |
407 rect = block.rect() | |
408 w, h = rect.width(), rect.height() | |
409 blockElement.setAttribute("name", block.name) | |
410 blockElement.setAttribute("x", str(int(x))) | |
411 blockElement.setAttribute("y", str(int(y))) | |
412 blockElement.setAttribute("width", str(int(w))) | |
413 blockElement.setAttribute("height", str(int(h))) | |
414 codeNode = doc.createCDATASection(block.code) | |
415 codeElement = doc.createElement('code') | |
416 codeElement.appendChild(codeNode) | |
417 blockElement.appendChild(codeElement) | |
418 for inp in block.inputs: | |
419 portElement = doc.createElement("input") | |
420 portElement.setAttribute("name", inp.name) | |
421 blockElement.appendChild(portElement) | |
422 for outp in block.outputs: | |
423 portElement = doc.createElement("output") | |
424 portElement.setAttribute("name", outp.name) | |
425 blockElement.appendChild(portElement) | |
426 modelElement.appendChild(blockElement) | |
427 for connection in connections: | |
428 connectionElement = doc.createElement("connection") | |
429 fromPort = connection.fromPort.name | |
430 toPort = connection.toPort.name | |
431 fromBlock = connection.fromPort.block.name | |
432 toBlock = connection.toPort.block.name | |
433 connectionElement.setAttribute("fromBlock", fromBlock) | |
434 connectionElement.setAttribute("fromPort", fromPort) | |
435 connectionElement.setAttribute("toBlock", toBlock) | |
436 connectionElement.setAttribute("toPort", toPort) | |
437 for via in connection.vias: | |
438 viaElement = doc.createElement('via') | |
439 viaElement.setAttribute('x', str(int(via.x()))) | |
440 viaElement.setAttribute('y', str(int(via.y()))) | |
441 connectionElement.appendChild(viaElement) | |
442 modelElement.appendChild(connectionElement) | |
443 with open(filename, 'w') as f: | |
444 f.write(doc.toprettyxml()) | |
445 | |
446 def loadDiagram(self, filename): | |
447 try: | |
448 doc = md.parse(filename) | |
449 except IOError as e: | |
450 print('{0} not found'.format(filename)) | |
451 return | |
452 except xml.parsers.expat.ExpatError as e: | |
453 print('{0}'.format(e)) | |
454 return | |
455 sysElements = doc.getElementsByTagName('system') | |
456 blockElements = doc.getElementsByTagName('block') | |
457 for sysElement in sysElements: | |
458 blockElements = sysElement.getElementsByTagName('block') | |
459 for blockElement in blockElements: | |
460 x = float(blockElement.getAttribute('x')) | |
461 y = float(blockElement.getAttribute('y')) | |
462 w = float(blockElement.getAttribute('width')) | |
463 h = float(blockElement.getAttribute('height')) | |
464 name = blockElement.getAttribute('name') | |
465 block = BlockItem(name) | |
466 self.addItem(block) | |
467 block.setPos(x, y) | |
468 block.sizer.setPos(w, h) | |
469 codeElements = blockElement.getElementsByTagName('code') | |
470 if codeElements: | |
471 cn = codeElements[0].childNodes | |
472 cdatas = [cd for cd in cn if type(cd) is md.CDATASection] | |
473 if len(cdatas) > 0: | |
474 block.code = cdatas[0].data | |
475 # Load ports: | |
476 portElements = blockElement.getElementsByTagName('input') | |
477 for portElement in portElements: | |
478 name = portElement.getAttribute('name') | |
479 inp = PortItem(name, block, 'input') | |
480 block.addInput(inp) | |
481 portElements = blockElement.getElementsByTagName('output') | |
482 for portElement in portElements: | |
483 name = portElement.getAttribute('name') | |
484 outp = PortItem(name, block, 'output') | |
485 block.addOutput(outp) | |
486 connectionElements = sysElement.getElementsByTagName('connection') | |
487 for connectionElement in connectionElements: | |
488 fromBlock = connectionElement.getAttribute('fromBlock') | |
489 fromPort = connectionElement.getAttribute('fromPort') | |
490 toBlock = connectionElement.getAttribute('toBlock') | |
491 toPort = connectionElement.getAttribute('toPort') | |
492 viaElements = connectionElement.getElementsByTagName('via') | |
493 fromPort = self.findPort(fromBlock, fromPort) | |
494 toPort = self.findPort(toBlock, toPort) | |
495 connection = Connection(fromPort, toPort) | |
496 for viaElement in viaElements: | |
497 x = int(viaElement.getAttribute('x')) | |
498 y = int(viaElement.getAttribute('y')) | |
499 connection.addHandle(QPointF(x, y)) | |
500 self.addItem(connection) | |
501 def findPort(self, blockname, portname): | |
502 items = self.items() | |
503 blocks = [item for item in items if type(item) is BlockItem] | |
504 for block in [b for b in blocks if b.name == blockname]: | |
505 for port in block.inputs + block.outputs: | |
506 if port.name == portname: | |
507 return port | |
508 def addNewBlock(self, pos, name): | |
509 blocknames = [item.name for item in self.items() if type(item) is BlockItem] | |
510 b1 = BlockItem(uniqify(name, blocknames)) | |
511 b1.setPos(pos) | |
512 self.addItem(b1) | |
513 def mouseMoveEvent(self, event): | |
514 if self.startedConnection: | |
515 pos = event.scenePos() | |
516 self.startedConnection.setEndPos(pos) | |
517 super(DiagramScene, self).mouseMoveEvent(event) | |
518 def mouseReleaseEvent(self, event): | |
519 if self.startedConnection: | |
520 items = self.items(event.scenePos()) | |
521 for item in items: | |
522 if type(item) is PortItem: | |
523 self.startedConnection.setToPort(item) | |
524 self.startedConnection = None | |
525 return | |
526 self.deleteItem(self.startedConnection) | |
527 self.startedConnection = None | |
528 super(DiagramScene, self).mouseReleaseEvent(event) | |
529 def startConnection(self, port): | |
530 self.startedConnection = Connection(port, None) | |
531 pos = port.scenePos() | |
532 self.startedConnection.setEndPos(pos) | |
533 self.addItem(self.startedConnection) | |
534 def deleteItem(self, item=None): | |
535 if item: | |
536 if type(item) is BlockItem: | |
537 for p in item.inputs + item.outputs: | |
538 if p.connection: | |
539 self.deleteItem(p.connection) | |
540 self.removeItem(item) | |
541 elif type(item) is Connection: | |
542 item.releasePorts() | |
543 self.removeItem(item) | |
544 else: | |
545 # No item was supplied, try to delete all currently selected items: | |
546 items = self.selectedItems() | |
547 connections = [item for item in items if type(item) is Connection] | |
548 blocks = [item for item in items if type(item) is BlockItem] | |
549 for item in connections + blocks: | |
550 self.deleteItem(item) | |
551 | |
552 class DiagramEditor(QWidget): | |
553 def __init__(self, parent=None): | |
554 QWidget.__init__(self, parent) | |
555 | |
556 # Widget layout and child widgets: | |
557 self.horizontalLayout = QHBoxLayout(self) | |
558 self.diagramScene = DiagramScene(self) | |
559 self.loadDiagram = self.diagramScene.loadDiagram | |
560 self.diagramView = EditorGraphicsView(self.diagramScene, self) | |
561 self.horizontalLayout.addWidget(self.diagramView) | |
562 | |
563 testShortcut = QShortcut(QKeySequence("F12"), self) | |
564 testShortcut.activated.connect(self.test) | |
565 delShort = QShortcut(QKeySequence.Delete, self) | |
566 delShort.activated.connect(self.diagramScene.deleteItem) | |
567 | |
568 def test(self): | |
569 self.diagramView.rotate(30) | |
570 self.zoomAll() | |
571 def save(self): | |
572 self.diagramScene.saveDiagram('diagram2.usd') | |
573 def load(self): | |
574 filename = QFileDialog.getOpenFileName(self) | |
575 self.diagramScene.loadDiagram(filename) | |
576 def zoomAll(self): | |
577 """ zoom to fit all items """ | |
578 rect = self.diagramScene.itemsBoundingRect() | |
579 self.diagramView.fitInView(rect, Qt.KeepAspectRatio) | |
580 | |
581 class LibraryWidget(QListView): | |
582 def __init__(self): | |
583 super(LibraryWidget, self).__init__(None) | |
584 self.libraryModel = LibraryModel(self) | |
585 self.libraryModel.setColumnCount(1) | |
586 # Create an icon with an icon: | |
587 pixmap = QPixmap(60, 60) | |
588 pixmap.fill() | |
589 painter = QPainter(pixmap) | |
590 painter.fillRect(10, 10, 40, 40, Qt.blue) | |
591 painter.setBrush(Qt.yellow) | |
592 painter.drawEllipse(20, 20, 20, 20) | |
593 painter.end() | |
594 | |
595 # Fill library: | |
596 self.libItems = [] | |
597 self.libItems.append( QStandardItem(QIcon(pixmap), 'Block') ) | |
598 self.libItems.append( QStandardItem(QIcon(pixmap), 'Uber Unit') ) | |
599 self.libItems.append( QStandardItem(QIcon(pixmap), 'Device') ) | |
600 for i in self.libItems: | |
601 self.libraryModel.appendRow(i) | |
602 self.setModel(self.libraryModel) | |
603 self.setViewMode(self.IconMode) | |
604 self.setDragDropMode(self.DragOnly) | |
605 | |
606 class Main(QMainWindow): | |
607 def __init__(self): | |
608 super(Main, self).__init__(None) | |
609 self.editor = DiagramEditor() | |
610 self.setCentralWidget(self.editor) | |
611 self.setWindowTitle("Diagram editor") | |
612 toolbar = self.addToolBar('Tools') | |
613 | |
614 saveAction = QAction('Save', self) | |
615 saveAction.setShortcuts(QKeySequence.Save) | |
616 saveAction.triggered.connect(self.editor.save) | |
617 toolbar.addAction(saveAction) | |
618 openAction = QAction('Open', self) | |
619 openAction.setShortcuts(QKeySequence.Open) | |
620 openAction.triggered.connect(self.editor.load) | |
621 toolbar.addAction(openAction) | |
622 fullScreenAction = QAction('Full screen', self) | |
623 fullScreenAction.setShortcuts(QKeySequence("F11")) | |
624 fullScreenAction.triggered.connect(self.toggleFullScreen) | |
625 toolbar.addAction(fullScreenAction) | |
626 zoomAction = QAction('Fit in view', self) | |
627 zoomAction.setShortcuts(QKeySequence('F8')) | |
628 zoomAction.triggered.connect(self.editor.zoomAll) | |
629 toolbar.addAction(zoomAction) | |
630 | |
631 self.library = LibraryWidget() | |
632 libraryDock = QDockWidget('Library', self) | |
633 libraryDock.setWidget(self.library) | |
634 self.addDockWidget(Qt.LeftDockWidgetArea, libraryDock) | |
635 | |
636 self.editor.loadDiagram('diagram2.usd') | |
637 | |
638 def toggleFullScreen(self): | |
639 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) | |
640 self.editor.zoomAll() | |
641 | |
642 if __name__ == '__main__': | |
643 if sys.version_info.major != 3: | |
644 print('Please use python 3.x') | |
645 sys.exit(1) | |
646 | |
647 app = QApplication(sys.argv) | |
648 main = Main() | |
649 main.show() | |
650 main.resize(700, 500) | |
651 main.editor.zoomAll() | |
652 app.exec_() | |
653 |