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)