Mercurial > lcfOS
comparison applications/lab/diagrameditor.py @ 56:7a82f0d52e85
Cleanup of line selection
author | windel |
---|---|
date | Sun, 22 Apr 2012 13:32:09 +0200 |
parents | 9414bad225a8 |
children | 143ab220aa27 |
comparison
equal
deleted
inserted
replaced
55:9414bad225a8 | 56:7a82f0d52e85 |
---|---|
3 from PyQt4.QtGui import * | 3 from PyQt4.QtGui import * |
4 from PyQt4.QtCore import * | 4 from PyQt4.QtCore import * |
5 import sys | 5 import sys |
6 import xml.dom.minidom as md | 6 import xml.dom.minidom as md |
7 | 7 |
8 """ | 8 """ |
9 Author: Windel Bouwman | 9 Author: Windel Bouwman |
10 Year: 2012 | 10 Year: 2012 |
11 Description: This script implements a diagram editor. | 11 Description: This script implements a diagram editor. |
12 run with python 3.x as: | 12 run with python 3.x as: |
13 $ python [thisfile.py] | 13 $ python [thisfile.py] |
23 self.setPath(arrowPath) | 23 self.setPath(arrowPath) |
24 pen = QPen(Qt.blue, 2) | 24 pen = QPen(Qt.blue, 2) |
25 self.setPen(pen) | 25 self.setPen(pen) |
26 self.setBrush(QBrush(pen.color())) | 26 self.setBrush(QBrush(pen.color())) |
27 | 27 |
28 class LinePart(QGraphicsLineItem): | |
29 def __init__(self, x1, y1, x2, y2): | |
30 super(LinePart, self).__init__(x1, y1, x2, y2) | |
31 self.setFlags(self.ItemIsSelectable) | |
32 def boundingRect(self): | |
33 rect = super(LinePart, self).boundingRect() | |
34 rect.adjust(-2, -2, 2, 2) | |
35 return rect | |
36 | |
37 #class Connection(QGraphicsItemGroup): | |
38 class Connection(QGraphicsPathItem): | 28 class Connection(QGraphicsPathItem): |
39 """ Implementation of a connection between blocks """ | 29 """ Implementation of a connection between blocks """ |
40 def __init__(self, fromPort, toPort): | 30 def __init__(self, fromPort, toPort): |
41 super(Connection, self).__init__() | 31 super(Connection, self).__init__() |
42 self.pos1 = None | 32 self.pos1 = None |
43 self.pos2 = None | 33 self.pos2 = None |
44 self.fromPort = None | 34 self.fromPort = None |
45 self.toPort = None | 35 self.toPort = None |
46 #self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) | |
47 self.setFlag(self.ItemIsSelectable, True) | 36 self.setFlag(self.ItemIsSelectable, True) |
48 self.setFlag(self.ItemClipsToShape, True) | 37 self.setFlag(self.ItemClipsToShape, True) |
49 self.pen = QPen(Qt.blue, 2) | 38 self.pen = QPen(Qt.blue, 2) |
50 self.pen.setCapStyle(Qt.RoundCap) | 39 self.pen.setCapStyle(Qt.RoundCap) |
51 self.setPen(self.pen) | 40 self.setPen(self.pen) |
52 self.arrowhead = ArrowHead(self) | 41 self.arrowhead = ArrowHead(self) |
53 #self.addToGroup(self.arrowhead) | |
54 #self.body = QGraphicsPathItem(self) | |
55 #self.body.setPen(self.pen) | |
56 #self.addToGroup(self.body) | |
57 self.lineItems = [] | |
58 self.setFromPort(fromPort) | 42 self.setFromPort(fromPort) |
59 self.setToPort(toPort) | 43 self.setToPort(toPort) |
60 def setFromPort(self, fromPort): | 44 def setFromPort(self, fromPort): |
61 if self.fromPort: | 45 if self.fromPort: |
62 self.fromPort.posCallbacks.remove(self.setBeginPos) | 46 self.fromPort.posCallbacks.remove(self.setBeginPos) |
86 self.updateLineStukken() | 70 self.updateLineStukken() |
87 def itemChange(self, change, value): | 71 def itemChange(self, change, value): |
88 if change == self.ItemSelectedHasChanged: | 72 if change == self.ItemSelectedHasChanged: |
89 pass # TODO, do something useful here. | 73 pass # TODO, do something useful here. |
90 return super(Connection, self).itemChange(change, value) | 74 return super(Connection, self).itemChange(change, value) |
75 def shape(self): | |
76 """ Create a shape outline using the path stroker """ | |
77 s = super(Connection, self).shape() | |
78 pps = QPainterPathStroker() | |
79 pps.setWidth(10) | |
80 return pps.createStroke(s).simplified() | |
91 def updateLineStukken(self): | 81 def updateLineStukken(self): |
92 """ | 82 """ |
93 This algorithm determines the optimal routing of all signals. | 83 This algorithm determines the optimal routing of all signals. |
94 TODO: implement nice automatic line router | 84 TODO: implement nice automatic line router |
95 """ | 85 """ |
96 if self.pos1 is None or self.pos2 is None: | 86 if self.pos1 is None or self.pos2 is None: |
97 return | 87 return |
98 | 88 |
99 # TODO: do not get the scene here? | 89 # TODO: do not get the scene here? |
100 # TODO: create pieces of lines. | |
101 | 90 |
102 # Determine the begin and end positions: | 91 # Determine the begin and end positions: |
103 pts = editor.diagramScene.vias(self.pos1, self.pos2) | 92 pts = editor.diagramScene.vias(self.pos1, self.pos2) |
104 | 93 |
105 self.arrowhead.setPos(self.pos2) | 94 self.arrowhead.setPos(self.pos2) |
106 if pts[-1].x() < pts[-2].x(): | 95 if pts[-1].x() < pts[-2].x(): |
107 self.arrowhead.setRotation(-90) | 96 self.arrowhead.setRotation(-90) |
108 else: | 97 else: |
109 self.arrowhead.setRotation(90) | 98 self.arrowhead.setRotation(90) |
110 | 99 |
111 usePath = True | 100 path = QPainterPath(pts[0]) |
112 if usePath: | 101 for pt in pts[1:]: |
113 path = QPainterPath(pts[0]) | 102 path.lineTo(pt) |
114 # Now move from p2 to p3 without hitting blocks: | 103 self.setPath(path) |
115 for pt in pts[1:]: | |
116 path.lineTo(pt) | |
117 for pt in reversed(pts[0:-1]): | |
118 path.lineTo(pt) | |
119 path.closeSubpath() | |
120 | |
121 #self.body.setPath(path) | |
122 self.setPath(path) | |
123 #outline = QGraphicsPathItem(self.shape()) | |
124 #editor.diagramScene.addItem(outline) | |
125 else: | |
126 # Delete old line items: | |
127 for li in self.lineItems: | |
128 editor.diagramScene.removeItem(li) | |
129 #self.removeFromGroup(li) | |
130 self.lineItems = [] | |
131 for a, b in zip(pts[0:-1], pts[1:]): | |
132 li = LinePart(a.x(),a.y(),b.x(),b.y()) | |
133 #self.addToGroup(li) | |
134 editor.diagramScene.addItem(li) | |
135 self.lineItems.append(li) | |
136 | 104 |
137 class ParameterDialog(QDialog): | 105 class ParameterDialog(QDialog): |
138 def __init__(self, block, parent = None): | 106 def __init__(self, block, parent = None): |
139 super(ParameterDialog, self).__init__(parent) | 107 super(ParameterDialog, self).__init__(parent) |
140 self.block = block | 108 self.block = block |
146 l.addWidget(self.button, 1, 0) | 114 l.addWidget(self.button, 1, 0) |
147 self.button.clicked.connect(self.OK) | 115 self.button.clicked.connect(self.OK) |
148 def OK(self): | 116 def OK(self): |
149 self.block.setName(self.nameEdit.text()) | 117 self.block.setName(self.nameEdit.text()) |
150 self.close() | 118 self.close() |
119 | |
151 # TODO: merge dialogs? | 120 # TODO: merge dialogs? |
152 | 121 |
153 class AddPortDialog(QDialog): | 122 class AddPortDialog(QDialog): |
154 def __init__(self, block, parent = None): | 123 def __init__(self, block, parent = None): |
155 super(AddPortDialog, self).__init__(parent) | 124 super(AddPortDialog, self).__init__(parent) |
169 | 138 |
170 class PortItem(QGraphicsPathItem): | 139 class PortItem(QGraphicsPathItem): |
171 """ Represents a port to a subsystem """ | 140 """ Represents a port to a subsystem """ |
172 def __init__(self, name, block, direction): | 141 def __init__(self, name, block, direction): |
173 super(PortItem, self).__init__(block) | 142 super(PortItem, self).__init__(block) |
174 #QRectF(-6,-6,12.0,12.0) | |
175 self.connection = None | 143 self.connection = None |
176 path = QPainterPath() | 144 path = QPainterPath() |
145 d = 10.0 | |
177 if direction == 'input': | 146 if direction == 'input': |
178 d = 5.0 | |
179 path.moveTo(-d, -d) | 147 path.moveTo(-d, -d) |
180 path.lineTo(0.0, 0.0) | 148 path.lineTo(0.0, 0.0) |
181 path.lineTo(-d, d) | 149 path.lineTo(-d, d) |
182 else: | 150 else: |
183 d = 5.0 | |
184 path.moveTo(0.0, -d) | 151 path.moveTo(0.0, -d) |
185 path.lineTo(d, 0.0) | 152 path.lineTo(d, 0.0) |
186 path.lineTo(0.0, d) | 153 path.lineTo(0.0, d) |
187 self.setPath(path) | 154 self.setPath(path) |
188 self.direction = direction | 155 self.direction = direction |
189 self.block = block | 156 self.block = block |
190 self.setCursor(QCursor(Qt.CrossCursor)) | 157 self.setCursor(QCursor(Qt.CrossCursor)) |
191 #self.setBrush(QBrush(Qt.red)) | 158 pen = QPen(Qt.blue, 2) |
192 self.setPen(QPen(Qt.blue, 2)) | 159 pen.setCapStyle(Qt.RoundCap) |
160 self.setPen(pen) | |
193 self.name = name | 161 self.name = name |
194 self.textItem = QGraphicsTextItem(name, self) | 162 self.textItem = QGraphicsTextItem(name, self) |
195 self.setName(name) | 163 self.setName(name) |
196 self.posCallbacks = [] | 164 self.posCallbacks = [] |
197 self.setFlag(self.ItemSendsScenePositionChanges, True) | 165 self.setFlag(self.ItemSendsScenePositionChanges, True) |
216 cb(value) | 184 cb(value) |
217 return value | 185 return value |
218 return super(PortItem, self).itemChange(change, value) | 186 return super(PortItem, self).itemChange(change, value) |
219 def mousePressEvent(self, event): | 187 def mousePressEvent(self, event): |
220 if self.direction == 'output': | 188 if self.direction == 'output': |
221 editor.startConnection(self) | 189 editor.diagramScene.startConnection(self) |
222 | 190 |
223 class OutputPort(PortItem): | 191 class OutputPort(PortItem): |
224 # TODO: create a subclass OR make a member porttype | 192 # TODO: create a subclass OR make a member porttype |
225 pass | 193 pass |
226 | 194 |
227 # Block part: | 195 # Block part: |
228 class HandleItem(QGraphicsEllipseItem): | 196 class HandleItem(QGraphicsEllipseItem): |
229 """ A handle that can be moved by the mouse """ | 197 """ A handle that can be moved by the mouse """ |
230 def __init__(self, parent=None): | 198 def __init__(self, parent=None): |
231 dx = 12.0 | 199 dx = 9.0 |
232 super(HandleItem, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent) | 200 super(HandleItem, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent) |
233 self.posChangeCallbacks = [] | 201 self.posChangeCallbacks = [] |
234 self.setBrush(QBrush(Qt.white)) | 202 self.setBrush(QBrush(Qt.white)) |
235 self.setFlag(self.ItemSendsScenePositionChanges, True) | 203 self.setFlag(self.ItemSendsScenePositionChanges, True) |
236 self.setFlag(self.ItemIsMovable, True) | 204 self.setFlag(self.ItemIsMovable, True) |
390 | 358 |
391 class DiagramScene(QGraphicsScene): | 359 class DiagramScene(QGraphicsScene): |
392 """ Save and load and deletion of item""" | 360 """ Save and load and deletion of item""" |
393 def __init__(self, parent=None): | 361 def __init__(self, parent=None): |
394 super(DiagramScene, self).__init__(parent) | 362 super(DiagramScene, self).__init__(parent) |
395 #circle = QGraphicsEllipseItem(-10,-10,20,20) | 363 self.startedConnection = None |
396 #self.addItem(circle) | |
397 | 364 |
398 def saveDiagram(self, filename): | 365 def saveDiagram(self, filename): |
399 items = self.items() | 366 items = self.items() |
400 blocks = [item for item in items if type(item) is BlockItem] | 367 blocks = [item for item in items if type(item) is BlockItem] |
401 connections = [item for item in items if type(item) is Connection] | 368 connections = [item for item in items if type(item) is Connection] |
482 blocks = [item for item in items if type(item) is BlockItem] | 449 blocks = [item for item in items if type(item) is BlockItem] |
483 for block in [b for b in blocks if b.name == blockname]: | 450 for block in [b for b in blocks if b.name == blockname]: |
484 for port in block.inputs + block.outputs: | 451 for port in block.inputs + block.outputs: |
485 if port.name == portname: | 452 if port.name == portname: |
486 return port | 453 return port |
487 def mouseMoveEvent(self, mouseEvent): | 454 def mouseMoveEvent(self, event): |
488 editor.sceneMouseMoveEvent(mouseEvent) | 455 if self.startedConnection: |
489 super(DiagramScene, self).mouseMoveEvent(mouseEvent) | 456 pos = event.scenePos() |
490 def mouseReleaseEvent(self, mouseEvent): | 457 items = self.items(pos) |
491 editor.sceneMouseReleaseEvent(mouseEvent) | 458 for item in items: |
492 super(DiagramScene, self).mouseReleaseEvent(mouseEvent) | 459 if type(item) is PortItem: |
460 pen = item.pen() | |
461 pen.setColor(Qt.red) | |
462 item.setPen(pen) | |
463 self.startedConnection.setEndPos(pos) | |
464 super(DiagramScene, self).mouseMoveEvent(event) | |
465 def mouseReleaseEvent(self, event): | |
466 if self.startedConnection: | |
467 items = self.items(event.scenePos()) | |
468 for item in items: | |
469 if type(item) is PortItem: | |
470 self.startedConnection.setToPort(item) | |
471 self.startedConnection = None | |
472 return | |
473 self.deleteItem(self.startedConnection) | |
474 self.startedConnection = None | |
475 super(DiagramScene, self).mouseReleaseEvent(event) | |
476 def startConnection(self, port): | |
477 self.startedConnection = Connection(port, None) | |
478 pos = port.scenePos() | |
479 self.startedConnection.setEndPos(pos) | |
480 self.addItem(self.startedConnection) | |
493 def vias(self, P1, P2): | 481 def vias(self, P1, P2): |
494 """ Constructs a list of points that must be connected | 482 """ Constructs a list of points that must be connected |
495 to go from P1 to P2 """ | 483 to go from P1 to P2 """ |
496 def stripHits(hits): | 484 def stripHits(hits): |
497 """ Helper function that removes object hits """ | 485 """ Helper function that removes object hits """ |
583 self.loadDiagram = self.diagramScene.loadDiagram | 571 self.loadDiagram = self.diagramScene.loadDiagram |
584 self.diagramView = EditorGraphicsView(self.diagramScene, self) | 572 self.diagramView = EditorGraphicsView(self.diagramScene, self) |
585 self.horizontalLayout.addWidget(self.libraryBrowserView) | 573 self.horizontalLayout.addWidget(self.libraryBrowserView) |
586 self.horizontalLayout.addWidget(self.diagramView) | 574 self.horizontalLayout.addWidget(self.diagramView) |
587 | 575 |
588 self.startedConnection = None | |
589 fullScreenShortcut = QShortcut(QKeySequence("F11"), self) | 576 fullScreenShortcut = QShortcut(QKeySequence("F11"), self) |
590 fullScreenShortcut.activated.connect(self.toggleFullScreen) | 577 fullScreenShortcut.activated.connect(self.toggleFullScreen) |
591 saveShortcut = QShortcut(QKeySequence(QKeySequence.Save), self) | 578 saveShortcut = QShortcut(QKeySequence(QKeySequence.Save), self) |
592 saveShortcut.activated.connect(self.save) | 579 saveShortcut.activated.connect(self.save) |
593 testShortcut = QShortcut(QKeySequence("F12"), self) | 580 testShortcut = QShortcut(QKeySequence("F12"), self) |
602 self.zoomAll() | 589 self.zoomAll() |
603 def save(self): | 590 def save(self): |
604 self.diagramScene.saveDiagram('diagram2.usd') | 591 self.diagramScene.saveDiagram('diagram2.usd') |
605 def toggleFullScreen(self): | 592 def toggleFullScreen(self): |
606 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) | 593 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) |
607 def startConnection(self, port): | |
608 self.startedConnection = Connection(port, None) | |
609 pos = port.scenePos() | |
610 self.startedConnection.setEndPos(pos) | |
611 self.diagramScene.addItem(self.startedConnection) | |
612 def zoomAll(self): | 594 def zoomAll(self): |
613 """ zoom to fit all items """ | 595 """ zoom to fit all items """ |
614 rect = self.diagramScene.itemsBoundingRect() | 596 rect = self.diagramScene.itemsBoundingRect() |
615 self.diagramView.fitInView(rect, Qt.KeepAspectRatio) | 597 self.diagramView.fitInView(rect, Qt.KeepAspectRatio) |
616 def sceneMouseMoveEvent(self, event): | |
617 if self.startedConnection: | |
618 pos = event.scenePos() | |
619 self.startedConnection.setEndPos(pos) | |
620 def sceneMouseReleaseEvent(self, event): | |
621 # Clear the actual connection: | |
622 if self.startedConnection: | |
623 items = self.diagramScene.items(event.scenePos()) | |
624 for item in items: | |
625 if type(item) is PortItem: | |
626 self.startedConnection.setToPort(item) | |
627 self.startedConnection = None | |
628 return | |
629 self.diagramScene.deleteItem(self.startedConnection) | |
630 self.startedConnection = None | |
631 | 598 |
632 if __name__ == '__main__': | 599 if __name__ == '__main__': |
633 if sys.version_info.major != 3: | 600 if sys.version_info.major != 3: |
634 print('Please use python 3.x') | 601 print('Please use python 3.x') |
635 sys.exit(1) | 602 sys.exit(1) |