comparison python/other/diagramitems.py @ 292:534b94b40aa8

Fixup reorganize
author Windel Bouwman
date Wed, 27 Nov 2013 08:06:42 +0100
parents python/diagramitems.py@6efbeb903777
children b77f3290ac79
comparison
equal deleted inserted replaced
290:7b38782ed496 292:534b94b40aa8
1 """
2 Contains all blocks that can be used to build models.
3 """
4
5 from PyQt4.QtGui import *
6 from PyQt4.QtCore import *
7
8 def uniqify(name, names):
9 newname, i = name, 1
10 while newname in names: newname, i = name + str(i), i + 1
11 return newname
12
13 def enum(**enums):
14 return type('Enum', (), enums)
15
16 Position = enum(TOP=0, TOP_RIGHT=1, RIGHT=2, BOTTOM_RIGHT=3, BOTTOM=4, BOTTOM_LEFT=5, LEFT=6, TOP_LEFT=7)
17
18 def buildPath(pts):
19 path = QPainterPath(pts[0])
20 for pt in pts[1:]: path.lineTo(pt)
21 return path
22
23 def equalSpace(n, l, offset=15):
24 if n == 1:
25 return [l / 2]
26 elif n > 1:
27 return [offset + (l - offset*2)/(n - 1)*i for i in range(n)]
28 return []
29
30 class Connection(QGraphicsPathItem):
31 """ A connection between blocks """
32 def __init__(self, fromPort=None, toPort=None):
33 super(Connection, self).__init__()
34 self.pos2 = self.fromPort = self.toPort = None
35 self.setFlags(self.ItemIsSelectable | self.ItemClipsToShape)
36 pen = QPen(Qt.blue, 2, cap=Qt.RoundCap)
37 self.setPen(pen)
38 self.arrowhead = QGraphicsPathItem(self)
39 self.arrowhead.setPath(buildPath([QPointF(0.0, 0.0), QPointF(-6.0, 10.0), QPointF(6.0, 10.0), QPointF(0.0, 0.0)]))
40 self.arrowhead.setPen(pen)
41 self.arrowhead.setBrush(QBrush(pen.color()))
42 self.vias = []
43 self.setFromPort(fromPort)
44 self.setToPort(toPort)
45 def getDict(self):
46 d = {}
47 d['fromBlock'] = self.fromPort.block.name
48 d['fromPort'] = self.fromPort.name
49 d['toBlock'] = self.toPort.block.name
50 d['toPort'] = self.toPort.name
51 return d
52 Dict = property(getDict)
53 def myDelete(self):
54 scene = self.scene()
55 if scene:
56 self.setFromPort(None)
57 self.setToPort(None)
58 scene.removeItem(self)
59 def setFromPort(self, fromPort):
60 if self.fromPort:
61 self.fromPort.posCallbacks.remove(self.setBeginPos)
62 self.fromPort.connection = None
63 self.fromPort = fromPort
64 if self.fromPort:
65 self.fromPort.connection = self
66 self.updateLineStukken()
67 self.fromPort.posCallbacks.append(self.setBeginPos)
68 def setToPort(self, toPort):
69 if self.toPort:
70 self.toPort.posCallbacks.remove(self.setEndPos)
71 self.toPort.connection = None
72 self.toPort = toPort
73 if self.toPort:
74 self.setEndPos(toPort.scenePos())
75 self.toPort.connection = self
76 self.toPort.posCallbacks.append(self.setEndPos)
77 def getPos1(self):
78 if self.fromPort:
79 return self.fromPort.scenePos()
80 def setBeginPos(self, pos1): self.updateLineStukken()
81 def setEndPos(self, endpos):
82 self.pos2 = endpos
83 self.updateLineStukken()
84 def itemChange(self, change, value):
85 if change == self.ItemSelectedHasChanged:
86 for via in self.vias:
87 via.setVisible(value)
88 return super(Connection, self).itemChange(change, value)
89 def shape(self): return self.myshape
90 def updateLineStukken(self):
91 """
92 This algorithm determines the optimal routing of all signals.
93 TODO: implement nice automatic line router
94 """
95 pos1 = self.getPos1()
96 pos2 = self.pos2
97 if pos1 is None or pos2 is None:
98 return
99 scene = self.scene()
100 vias = [pos1 + QPointF(20, 0)] + self.vias + [pos2 + QPointF(-20, 0)]
101 if scene:
102 litem = QGraphicsLineItem()
103 litem.setFlags(self.ItemIsSelectable)
104 scene.addItem(litem)
105 for p1, p2 in zip(vias[:-1], vias[1:]):
106 line = QLineF(p1, p2)
107 litem.setLine(line)
108 citems = scene.collidingItems(litem)
109 citems = [i for i in citems if type(i) is Block]
110 scene.removeItem(litem)
111 pts = [pos1] + vias + [pos2]
112 self.arrowhead.setPos(pos2)
113 self.arrowhead.setRotation(90)
114 p = buildPath(pts)
115 self.setPath(p)
116 """ Create a shape outline using the path stroker """
117 s = super(Connection, self).shape()
118 pps = QPainterPathStroker()
119 pps.setWidth(10)
120 self.myshape = pps.createStroke(s).simplified()
121
122 class PortItem(QGraphicsPathItem):
123 """ Represents a port to a subsystem """
124 def __init__(self, name, block):
125 super(PortItem, self).__init__(block)
126 self.textItem = QGraphicsTextItem(self)
127 self.connection = None
128 self.block = block
129 self.setCursor(QCursor(Qt.CrossCursor))
130 self.setPen(QPen(Qt.blue, 2, cap=Qt.RoundCap))
131 self.name = name
132 self.posCallbacks = []
133 self.setFlag(self.ItemSendsScenePositionChanges, True)
134 def getName(self): return self.textItem.toPlainText()
135 def setName(self, name):
136 self.textItem.setPlainText(name)
137 rect = self.textItem.boundingRect()
138 lw, lh = rect.width(), rect.height()
139 lx = 3 if type(self) is InputPort else -3 - lw
140 self.textItem.setPos(lx, -lh / 2)
141 name = property(getName, setName)
142 def getDict(self):
143 return {'name': self.name}
144 Dict = property(getDict)
145 def itemChange(self, change, value):
146 if change == self.ItemScenePositionHasChanged:
147 for cb in self.posCallbacks: cb(value)
148 return value
149 return super(PortItem, self).itemChange(change, value)
150
151 class OutputPort(PortItem):
152 def __init__(self, name, block, d=10.0):
153 super(OutputPort, self).__init__(name, block)
154 self.setPath(buildPath([QPointF(0.0, -d), QPointF(d, 0), QPointF(0.0, d)]))
155 def mousePressEvent(self, event):
156 self.scene().startConnection(self)
157
158 class InputPort(PortItem):
159 def __init__(self, name, block, d=10.0):
160 super(InputPort, self).__init__(name, block)
161 self.setPath(buildPath([QPointF(-d, -d), QPointF(0, 0), QPointF(-d, d)]))
162
163 class Handle(QGraphicsEllipseItem):
164 """ A handle that can be moved by the mouse """
165 def __init__(self, dx=10.0, parent=None):
166 super(Handle, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent)
167 self.setBrush(QBrush(Qt.white))
168 self.setFlags(self.ItemIsMovable)
169 self.setZValue(1)
170 self.setVisible(False)
171 self.setCursor(QCursor(Qt.SizeFDiagCursor))
172 def mouseMoveEvent(self, event):
173 """ Move function without moving the other selected elements """
174 p = self.mapToParent(event.pos())
175 self.setPos(p)
176
177 class ResizeSelectionHandle(Handle):
178 def __init__(self, position, block):
179 super(ResizeSelectionHandle, self).__init__(dx=12, parent=block)
180 self.position = position
181 self.block = block
182 if position in [Position.TOP_LEFT, Position.BOTTOM_RIGHT]:
183 self.setCursor(QCursor(Qt.SizeFDiagCursor))
184 elif position in [Position.TOP_RIGHT, Position.BOTTOM_LEFT]:
185 self.setCursor(QCursor(Qt.SizeBDiagCursor))
186 elif position in [Position.TOP, Position.BOTTOM]:
187 self.setCursor(QCursor(Qt.SizeVerCursor))
188 elif position in [Position.LEFT, Position.RIGHT]:
189 self.setCursor(QCursor(Qt.SizeHorCursor))
190 def mouseMoveEvent(self, event):
191 self.block.sizerMoveEvent(self, event.scenePos())
192
193 class Block(QGraphicsRectItem):
194 """ Represents a block in the diagram. """
195 def __init__(self, name='Untitled', parent=None):
196 super(Block, self).__init__(parent)
197 self.selectionHandles = [ResizeSelectionHandle(i, self) for i in range(8)]
198 # Properties of the rectangle:
199 self.setPen(QPen(Qt.blue, 2))
200 self.setBrush(QBrush(Qt.lightGray))
201 self.setFlags(self.ItemIsSelectable | self.ItemIsMovable | self.ItemSendsScenePositionChanges)
202 self.setCursor(QCursor(Qt.PointingHandCursor))
203 self.setAcceptHoverEvents(True)
204 self.label = QGraphicsTextItem(name, self)
205 self.name = name
206 # Create corner for resize:
207 button = QPushButton('+in')
208 button.clicked.connect(self.newInputPort)
209 self.buttonItemAddInput = QGraphicsProxyWidget(self)
210 self.buttonItemAddInput.setWidget(button)
211 self.buttonItemAddInput.setVisible(False)
212 button = QPushButton('+out')
213 button.clicked.connect(self.newOutputPort)
214 self.buttonItemAddOutput = QGraphicsProxyWidget(self)
215 self.buttonItemAddOutput.setWidget(button)
216 self.buttonItemAddOutput.setVisible(False)
217 # Inputs and outputs of the block:
218 self.inputs = []
219 self.outputs = []
220 self.changeSize(2,2)
221 def editParameters(self):
222 pd = ParameterDialog(self, self.window())
223 pd.exec_()
224 def newInputPort(self):
225 names = [i.name for i in self.inputs + self.outputs]
226 self.addInput(InputPort(uniqify('in', names), self))
227 def newOutputPort(self):
228 names = [i.name for i in self.inputs + self.outputs]
229 self.addOutput(OutputPort(uniqify('out', names), self))
230 def setName(self, name): self.label.setPlainText(name)
231 def getName(self): return self.label.toPlainText()
232 name = property(getName, setName)
233 def getDict(self):
234 d = {'x': self.scenePos().x(), 'y': self.scenePos().y()}
235 rect = self.rect()
236 d.update({'width': rect.width(), 'height': rect.height()})
237 d['name'] = self.name
238 d['inputs'] = [inp.Dict for inp in self.inputs]
239 d['outputs'] = [outp.Dict for outp in self.outputs]
240 return d
241 def setDict(self, d):
242 self.name = d['name']
243 self.setPos(d['x'], d['y'])
244 self.changeSize(d['width'], d['height'])
245 for inp in d['inputs']:
246 self.addInput(InputPort(inp['name'], self))
247 for outp in d['outputs']:
248 self.addOutput(OutputPort(outp['name'], self))
249 Dict = property(getDict, setDict)
250 def addInput(self, i):
251 self.inputs.append(i)
252 self.updateSize()
253 def addOutput(self, o):
254 self.outputs.append(o)
255 self.updateSize()
256 def contextMenuEvent(self, event):
257 menu = QMenu()
258 pa = menu.addAction('Parameters')
259 pa.triggered.connect(self.editParameters)
260 menu.exec_(event.screenPos())
261 def itemChange(self, change, value):
262 if change == self.ItemSelectedHasChanged:
263 for child in [self.buttonItemAddInput, self.buttonItemAddOutput]:
264 child.setVisible(value)
265 if value:
266 self.repositionAndShowHandles()
267 else:
268 [h.setVisible(False) for h in self.selectionHandles]
269
270 return super(Block, self).itemChange(change, value)
271 def hoverEnterEvent(self, event):
272 if not self.isSelected():
273 self.repositionAndShowHandles()
274 super(Block, self).hoverEnterEvent(event)
275 def hoverLeaveEvent(self, event):
276 if not self.isSelected():
277 [h.setVisible(False) for h in self.selectionHandles]
278 super(Block, self).hoverLeaveEvent(event)
279 def myDelete(self):
280 for p in self.inputs + self.outputs:
281 if p.connection: p.connection.myDelete()
282 self.scene().removeItem(self)
283 def repositionAndShowHandles(self):
284 r = self.rect()
285 self.selectionHandles[Position.TOP_LEFT].setPos(r.topLeft())
286 self.selectionHandles[Position.TOP].setPos(r.center().x(), r.top())
287 self.selectionHandles[Position.TOP_RIGHT].setPos(r.topRight())
288 self.selectionHandles[Position.RIGHT].setPos(r.right(), r.center().y())
289 self.selectionHandles[Position.BOTTOM_RIGHT].setPos(r.bottomRight())
290 self.selectionHandles[Position.BOTTOM].setPos(r.center().x(), r.bottom())
291 self.selectionHandles[Position.BOTTOM_LEFT].setPos(r.bottomLeft())
292 self.selectionHandles[Position.LEFT].setPos(r.left(), r.center().y())
293 for h in self.selectionHandles:
294 h.setVisible(True)
295 def sizerMoveEvent(self, handle, pos):
296 r = self.rect().translated(self.pos())
297 if handle.position == Position.TOP_LEFT: r.setTopLeft(pos)
298 elif handle.position == Position.TOP: r.setTop(pos.y())
299 elif handle.position == Position.TOP_RIGHT: r.setTopRight(pos)
300 elif handle.position == Position.RIGHT: r.setRight(pos.x())
301 elif handle.position == Position.BOTTOM_RIGHT: r.setBottomRight(pos)
302 elif handle.position == Position.BOTTOM: r.setBottom(pos.y())
303 elif handle.position == Position.BOTTOM_LEFT: r.setBottomLeft(pos)
304 elif handle.position == Position.LEFT: r.setLeft(pos.x())
305 else:
306 print('invalid position')
307 self.setCenterAndSize(r.center(), r.size())
308 self.repositionAndShowHandles()
309 def updateSize(self):
310 rect = self.rect()
311 h, w = rect.height(), rect.width()
312 self.buttonItemAddInput.setPos(0, h + 4)
313 self.buttonItemAddOutput.setPos(w+10, h+4)
314 for inp, y in zip(self.inputs, equalSpace(len(self.inputs), h)):
315 inp.setPos(0.0, y)
316 for outp, y in zip(self.outputs, equalSpace(len(self.outputs), h)):
317 outp.setPos(w, y)
318 def setCenterAndSize(self, center, size):
319 self.changeSize(size.width(), size.height())
320 p = QPointF(size.width(), size.height())
321 self.setPos(center - p / 2)
322 def changeSize(self, w, h):
323 minw = 150
324 minh = 50
325 h = minh if h < minh else h
326 w = minw if w < minw else w
327 self.setRect(0.0, 0.0, w, h)
328 rect = self.label.boundingRect()
329 self.label.setPos((w - rect.width()) / 2, (h - rect.height()) / 2)
330 self.updateSize()
331
332 class CodeBlock(Block):
333 def __init__(self, name='Untitled', parent=None):
334 super(CodeBlock, self).__init__(name, parent)
335 self.code = ''
336 def setDict(self, d):
337 super(CodeBlock, self).setDict(d)
338 self.code = d['code']
339 def getDict(self):
340 d = super(CodeBlock, self).getDict()
341 d['code'] = self.code
342 return d
343 def gencode(self):
344 c = ['def {0}():'.format(self.name)]
345 if self.code:
346 c += indent(self.code.split('\n'))
347 else:
348 c += indent(['pass'])
349 return c
350
351 class DiagramBlock(Block):
352 def __init__(self, name='Untitled', parent=None):
353 super(DiagramBlock, self).__init__(name, parent)
354 self.subModel = DiagramScene()
355 self.subModel.containingBlock = self
356 def setDict(self, d):
357 self.subModel.Dict = d['submodel']
358 def mouseDoubleClickEvent(self, event):
359 # descent into child diagram
360 #self.editParameters()
361 print('descent')
362 scene = self.scene()
363 if scene:
364 for view in scene.views():
365 view.diagram = self.subModel
366 view.zoomAll()
367
368 class DiagramScene(QGraphicsScene):
369 """ A diagram scene consisting of blocks and connections """
370 structureChanged = pyqtSignal()
371 def __init__(self):
372 super(DiagramScene, self).__init__()
373 self.startedConnection = None
374
375 blocks = property(lambda sel: [i for i in sel.items() if isinstance(i, Block)])
376 connections = property(lambda sel: [i for i in sel.items() if type(i) is Connection])
377 def addItem(self, item):
378 super(DiagramScene, self).addItem(item)
379 if isinstance(item, Block):
380 self.structureChanged.emit()
381 def removeItem(self, item):
382 super(DiagramScene, self).removeItem(item)
383 if isinstance(item, Block):
384 self.structureChanged.emit()
385 def setDict(self, d):
386 for block in d['blocks']:
387 b = Block()
388 self.addItem(b)
389 b.Dict = block
390 for con in d['connections']:
391 fromPort = self.findPort(con['fromBlock'], con['fromPort'])
392 toPort = self.findPort(con['toBlock'], con['toPort'])
393 self.addItem(Connection(fromPort, toPort))
394 def getDict(self):
395 return {'blocks': [b.Dict for b in self.blocks], 'connections': [c.Dict for c in self.connections]}
396 Dict = property(getDict, setDict)
397 def gencode(self):
398 c = []
399 for b in self.blocks:
400 c += b.gencode()
401 for b in self.blocks:
402 c.append('{0}()'.format(b.name))
403 return c
404 def findPort(self, blockname, portname):
405 block = self.findBlock(blockname)
406 if block:
407 for port in block.inputs + block.outputs:
408 if port.name == portname: return port
409 def findBlock(self, blockname):
410 for block in self.blocks:
411 if block.name == blockname: return block
412 def uniqify(self, name):
413 blocknames = [item.name for item in self.blocks]
414 return uniqify(name, blocknames)
415 def mouseMoveEvent(self, event):
416 if self.startedConnection:
417 pos = event.scenePos()
418 self.startedConnection.setEndPos(pos)
419 super(DiagramScene, self).mouseMoveEvent(event)
420 def mouseReleaseEvent(self, event):
421 if self.startedConnection:
422 for item in self.items(event.scenePos()):
423 if type(item) is InputPort and item.connection == None:
424 self.startedConnection.setToPort(item)
425 self.startedConnection = None
426 return
427 self.startedConnection.myDelete()
428 self.startedConnection = None
429 super(DiagramScene, self).mouseReleaseEvent(event)
430 def startConnection(self, port):
431 self.startedConnection = Connection(port, None)
432 pos = port.scenePos()
433 self.startedConnection.setEndPos(pos)
434 self.addItem(self.startedConnection)
435 def deleteItems(self):
436 for item in list(self.selectedItems()): item.myDelete()
437