comparison applications/lab/diagrameditor.py @ 46:7964065400b7

Added xml save function
author windel
date Thu, 15 Mar 2012 20:30:13 +0100
parents 8a52263d67c4
children c4ec95a588ea
comparison
equal deleted inserted replaced
45:8a52263d67c4 46:7964065400b7
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 from PyQt4 import QtGui, QtCore
4 from PyQt4.QtGui import * 3 from PyQt4.QtGui import *
5 from PyQt4.QtCore import * 4 from PyQt4.QtCore import *
6 import sys 5 import sys
6 from elementtree.SimpleXMLWriter import XMLWriter
7 7
8 """ 8 """
9 This script implements a basic diagram editor. 9 This script implements a basic diagram editor.
10 """ 10 """
11 11
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 self.fromPort = fromPort
20 self.pos1 = None 19 self.pos1 = None
21 self.pos2 = None 20 self.pos2 = None
22 self.p1dir = None 21 self.pathItem = QGraphicsPathItem()
23 self.p2dir = None 22 pen = QPen()
23 pen.setWidth(4)
24 pen.setColor(Qt.blue)
25 pen.setCapStyle(Qt.RoundCap)
26 self.pathItem.setPen(pen)
27 #self.pathItem.setFlags(self.pathItem.ItemIsSelectable)
28 editor.diagramScene.addItem(self.pathItem)
24 self.setFromPort(fromPort) 29 self.setFromPort(fromPort)
25 self.toPort = toPort 30 self.setToPort(toPort)
26 # Create arrow item:
27 self.linePieces = []
28 def setFromPort(self, fromPort): 31 def setFromPort(self, fromPort):
29 self.fromPort = fromPort 32 self.fromPort = fromPort
30 if self.fromPort: 33 if self.fromPort:
31 self.pos1 = fromPort.scenePos() 34 self.setBeginPos(fromPort.scenePos())
32 self.fromPort.posCallbacks.append(self.setBeginPos) 35 self.fromPort.posCallbacks.append(self.setBeginPos)
33 def setToPort(self, toPort): 36 def setToPort(self, toPort):
34 self.toPort = toPort 37 self.toPort = toPort
35 if self.toPort: 38 if self.toPort:
36 self.pos2 = toPort.scenePos() 39 self.setEndPos(toPort.scenePos())
37 self.toPort.posCallbacks.append(self.setEndPos) 40 self.toPort.posCallbacks.append(self.setEndPos)
38 def setBeginPos(self, pos1): 41 def setBeginPos(self, pos1):
39 self.pos1 = pos1 42 self.pos1 = pos1
40 if self.pos1 and self.pos2: 43 self.updateLineStukken()
41 self.updateLineStukken()
42 def setEndPos(self, endpos): 44 def setEndPos(self, endpos):
43 self.pos2 = endpos 45 self.pos2 = endpos
44 if self.pos1 and self.pos2: 46 self.updateLineStukken()
45 self.updateLineStukken()
46 def updateLineStukken(self): 47 def updateLineStukken(self):
47 """ 48 """
48 This algorithm determines the optimal routing of all signals. 49 This algorithm determines the optimal routing of all signals.
49 TODO: implement nice automatic line router 50 TODO: implement nice automatic line router
50 """ 51 """
52 if self.pos1 is None or self.pos2 is None:
53 return
54
55 ds = editor.diagramScene
56
51 # TODO: create pieces of lines. 57 # TODO: create pieces of lines.
52 58
53 # Determine the current amount of linestukken: 59 # Determine the begin and end positions:
60 p1 = self.pos1
61 p4 = self.pos2
54 x1, y1 = self.pos1.x(), self.pos1.y() 62 x1, y1 = self.pos1.x(), self.pos1.y()
55 x2, y2 = self.pos2.x(), self.pos2.y() 63 x4, y4 = self.pos2.x(), self.pos2.y()
56 64
57 ds = editor.diagramScene 65 def stripHits(hits):
58 66 """ Helper function that removes object hits """
59 if y1 == y2 or x1 == x2: 67 hits = [hit for hit in hits if type(hit) is BlockItem]
60 pass 68 if self.pathItem in hits:
69 hits.remove(self.pathItem)
70 if self.fromPort in hits:
71 hits.remove(self.fromPort)
72 if self.toPort in hits:
73 hits.remove(self.toPort)
74 return hits
75
76 def viaPoints(pA, pB):
77 # Construct intermediate points:
78
79 pAB1 = QPointF(pA.x(), pB.y())
80 pAB2 = QPointF(pB.x(), pA.y())
81 path1 = QPainterPath(pA)
82 path1.lineTo(pAB1)
83 path2 = QPainterPath(pA)
84 path2.lineTo(pAB2)
85 if len(stripHits(ds.items(path1))) > len(stripHits(ds.items(path2))):
86 pAB = pAB2
87 else:
88 pAB = pAB1
89 return [pAB]
90
91 # Determine left or right:
92 dx = QPointF(10, 0)
93 if len(stripHits(ds.items(p1 + dx))) > len(stripHits(ds.items(p1 - dx))):
94 p2 = p1 - dx
61 else: 95 else:
62 # We require two lijnstukken to make one corner! 96 p2 = p1 + dx
63 while len(self.linePieces) < 2: 97
64 lp = LinePieceItem() 98 if len(stripHits(ds.items(p4 + dx))) > len(stripHits(ds.items(p4 - dx))):
65 ds.addItem(lp) 99 p3 = p4 - dx
66 self.linePieces.append(lp) 100 else:
67 lp1 = self.linePieces[0] 101 p3 = p4 + dx
68 lp2 = self.linePieces[1] 102
69 lp1.setLine(QLineF(x1, y1, x2, y1)) 103 path = QPainterPath(p1)
70 lp2.setLine(QLineF(x2, y1, x2, y2)) 104 path.lineTo(p2)
105 # Now move from p2 to p3 without hitting blocks:
106 pts = viaPoints(p2, p3)
107 for pt in pts:
108 path.lineTo(pt)
109 path.lineTo(p3)
110 path.lineTo(p4)
111
112 hits = stripHits(ds.items(path))
113
114 #print('Items:', hits)
115 self.pathItem.setPath(path)
71 116
72 def delete(self): 117 def delete(self):
73 editor.diagramScene.removeItem(self.arrow) 118 editor.diagramScene.removeItem(self.pathItem)
74 # Remove position update callbacks: 119 # Remove position update callbacks:
75 120
76 class ParameterDialog(QDialog): 121 class ParameterDialog(QDialog):
77 def __init__(self, parent=None): 122 def __init__(self, parent = None):
78 super(ParameterDialog, self).__init__(parent) 123 super(ParameterDialog, self).__init__(parent)
79 self.button = QPushButton('Ok', self) 124 self.button = QPushButton('Ok', self)
80 l = QVBoxLayout(self) 125 l = QVBoxLayout(self)
81 l.addWidget(self.button) 126 l.addWidget(self.button)
82 self.button.clicked.connect(self.OK) 127 self.button.clicked.connect(self.OK)
85 130
86 class PortItem(QGraphicsEllipseItem): 131 class PortItem(QGraphicsEllipseItem):
87 """ Represents a port to a subsystem """ 132 """ Represents a port to a subsystem """
88 def __init__(self, name, parent=None): 133 def __init__(self, name, parent=None):
89 QGraphicsEllipseItem.__init__(self, QRectF(-6,-6,12.0,12.0), parent) 134 QGraphicsEllipseItem.__init__(self, QRectF(-6,-6,12.0,12.0), parent)
90 self.setCursor(QCursor(QtCore.Qt.CrossCursor)) 135 self.setCursor(QCursor(Qt.CrossCursor))
91 # Properties: 136 # Properties:
92 self.setBrush(QBrush(Qt.red)) 137 self.setBrush(QBrush(Qt.red))
93 # Name: 138 # Name:
94 self.name = name 139 self.name = name
95 self.posCallbacks = [] 140 self.posCallbacks = []
96 self.setFlag(self.ItemSendsScenePositionChanges, True) 141 self.setFlag(self.ItemSendsScenePositionChanges, True)
97 def itemChange(self, change, value): 142 def itemChange(self, change, value):
98 if change == self.ItemScenePositionHasChanged: 143 if change == self.ItemScenePositionHasChanged:
99 #value = value.toPointF() # Required in python2??!
100 for cb in self.posCallbacks: 144 for cb in self.posCallbacks:
101 cb(value) 145 cb(value)
102 return value 146 return value
103 return super(PortItem, self).itemChange(change, value) 147 return super(PortItem, self).itemChange(change, value)
104 def mousePressEvent(self, event): 148 def mousePressEvent(self, event):
108 class HandleItem(QGraphicsEllipseItem): 152 class HandleItem(QGraphicsEllipseItem):
109 """ A handle that can be moved by the mouse """ 153 """ A handle that can be moved by the mouse """
110 def __init__(self, parent=None): 154 def __init__(self, parent=None):
111 super(HandleItem, self).__init__(QRectF(-4.0,-4.0,8.0,8.0), parent) 155 super(HandleItem, self).__init__(QRectF(-4.0,-4.0,8.0,8.0), parent)
112 self.posChangeCallbacks = [] 156 self.posChangeCallbacks = []
113 self.setBrush(QtGui.QBrush(Qt.white)) 157 self.setBrush(QBrush(Qt.white))
114 self.setFlag(self.ItemIsMovable, True) 158 self.setFlags(self.ItemIsMovable | self.ItemSendsScenePositionChanges)
115 self.setFlag(self.ItemSendsScenePositionChanges, True) 159 self.setCursor(QCursor(Qt.SizeFDiagCursor))
116 self.setCursor(QtGui.QCursor(Qt.SizeFDiagCursor))
117 160
118 def itemChange(self, change, value): 161 def itemChange(self, change, value):
119 if change == self.ItemPositionChange: 162 if change == self.ItemPositionChange:
120 #value = value.toPointF() 163 #value = value.toPointF()
121 x, y = value.x(), value.y() 164 x, y = value.x(), value.y()
143 def __init__(self, name='Untitled', parent=None): 186 def __init__(self, name='Untitled', parent=None):
144 super(BlockItem, self).__init__(parent) 187 super(BlockItem, self).__init__(parent)
145 w = 60.0 188 w = 60.0
146 h = 40.0 189 h = 40.0
147 # Properties of the rectangle: 190 # Properties of the rectangle:
148 self.setPen(QtGui.QPen(QtCore.Qt.blue, 2)) 191 self.setPen(QPen(Qt.blue, 2))
149 self.setBrush(QtGui.QBrush(QtCore.Qt.lightGray)) 192 self.setBrush(QBrush(Qt.lightGray))
150 self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) 193 self.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
151 self.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) 194 self.setCursor(QCursor(Qt.PointingHandCursor))
152 # Label: 195 # Label:
153 self.label = QGraphicsTextItem(name, self) 196 self.label = QGraphicsTextItem(name, self)
154 # Create corner for resize: 197 # Create corner for resize:
155 self.sizer = HandleItem(self) 198 self.sizer = HandleItem(self)
156 self.sizer.setPos(w, h) 199 self.sizer.setPos(w, h)
171 pd = ParameterDialog(self.window()) 214 pd = ParameterDialog(self.window())
172 pd.exec_() 215 pd.exec_()
173 216
174 def contextMenuEvent(self, event): 217 def contextMenuEvent(self, event):
175 menu = QMenu() 218 menu = QMenu()
176 menu.addAction('Delete') 219 da = menu.addAction('Delete')
220 da.triggered.connect(self.delete)
177 pa = menu.addAction('Parameters') 221 pa = menu.addAction('Parameters')
178 pa.triggered.connect(self.editParameters) 222 pa.triggered.connect(self.editParameters)
179 menu.exec_(event.screenPos()) 223 menu.exec_(event.screenPos())
224
225 def delete(self):
226 editor.diagramScene.removeItem(self)
227 # TODO: remove connections
180 228
181 def changeSize(self, w, h): 229 def changeSize(self, w, h):
182 """ Resize block function """ 230 """ Resize block function """
183 # Limit the block size: 231 # Limit the block size:
184 if h < 20: 232 if h < 20:
209 for outp in self.outputs: 257 for outp in self.outputs:
210 outp.setPos(w+4, y) 258 outp.setPos(w+4, y)
211 y += dy 259 y += dy
212 return w, h 260 return w, h
213 261
214 class LinePieceItem(QGraphicsLineItem):
215 def __init__(self):
216 super(LinePieceItem, self).__init__(None)
217 self.setPen(QtGui.QPen(QtCore.Qt.red,2))
218 self.setFlag(self.ItemIsSelectable, True)
219 def x(self):
220 pass
221
222 class EditorGraphicsView(QGraphicsView): 262 class EditorGraphicsView(QGraphicsView):
223 def __init__(self, scene, parent=None): 263 def __init__(self, scene, parent=None):
224 QGraphicsView.__init__(self, scene, parent) 264 QGraphicsView.__init__(self, scene, parent)
225 def dragEnterEvent(self, event): 265 def dragEnterEvent(self, event):
226 if event.mimeData().hasFormat('component/name'): 266 if event.mimeData().hasFormat('component/name'):
259 editor.sceneMouseReleaseEvent(mouseEvent) 299 editor.sceneMouseReleaseEvent(mouseEvent)
260 super(DiagramScene, self).mouseReleaseEvent(mouseEvent) 300 super(DiagramScene, self).mouseReleaseEvent(mouseEvent)
261 301
262 class DiagramEditor(QWidget): 302 class DiagramEditor(QWidget):
263 def __init__(self, parent=None): 303 def __init__(self, parent=None):
264 QtGui.QWidget.__init__(self, parent) 304 QWidget.__init__(self, parent)
265 self.setWindowTitle("Diagram editor") 305 self.setWindowTitle("Diagram editor")
266 306
267 # Widget layout and child widgets: 307 # Widget layout and child widgets:
268 self.horizontalLayout = QtGui.QHBoxLayout(self) 308 self.horizontalLayout = QHBoxLayout(self)
269 self.libraryBrowserView = QtGui.QListView(self) 309 self.libraryBrowserView = QListView(self)
270 self.libraryBrowserView.setMaximumWidth(160) 310 self.libraryBrowserView.setMaximumWidth(160)
271 self.libraryModel = LibraryModel(self) 311 self.libraryModel = LibraryModel(self)
272 self.libraryModel.setColumnCount(1) 312 self.libraryModel.setColumnCount(1)
273 # Create an icon with an icon: 313 # Create an icon with an icon:
274 pixmap = QPixmap(60, 60) 314 pixmap = QPixmap(60, 60)
280 painter.setBrush(Qt.yellow) 320 painter.setBrush(Qt.yellow)
281 painter.drawEllipse(20, 20, 20, 20) 321 painter.drawEllipse(20, 20, 20, 20)
282 painter.end() 322 painter.end()
283 323
284 self.libItems = [] 324 self.libItems = []
285 self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Block') ) 325 self.libItems.append( QStandardItem(QIcon(pixmap), 'Block') )
286 self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Uber Unit') ) 326 self.libItems.append( QStandardItem(QIcon(pixmap), 'Uber Unit') )
287 self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Device') ) 327 self.libItems.append( QStandardItem(QIcon(pixmap), 'Device') )
288 for i in self.libItems: 328 for i in self.libItems:
289 self.libraryModel.appendRow(i) 329 self.libraryModel.appendRow(i)
290 self.libraryBrowserView.setModel(self.libraryModel) 330 self.libraryBrowserView.setModel(self.libraryModel)
291 self.libraryBrowserView.setViewMode(self.libraryBrowserView.IconMode) 331 self.libraryBrowserView.setViewMode(self.libraryBrowserView.IconMode)
292 self.libraryBrowserView.setDragDropMode(self.libraryBrowserView.DragOnly) 332 self.libraryBrowserView.setDragDropMode(self.libraryBrowserView.DragOnly)
294 self.diagramScene = DiagramScene(self) 334 self.diagramScene = DiagramScene(self)
295 self.diagramView = EditorGraphicsView(self.diagramScene, self) 335 self.diagramView = EditorGraphicsView(self.diagramScene, self)
296 self.horizontalLayout.addWidget(self.libraryBrowserView) 336 self.horizontalLayout.addWidget(self.libraryBrowserView)
297 self.horizontalLayout.addWidget(self.diagramView) 337 self.horizontalLayout.addWidget(self.diagramView)
298 338
299 # Populate the diagram scene:
300 b1 = BlockItem('SubSystem1')
301 b1.setPos(50,100)
302 self.diagramScene.addItem(b1)
303 b2 = BlockItem('Unit2')
304 b2.setPos(-250,0)
305 self.diagramScene.addItem(b2)
306
307 self.startedConnection = None 339 self.startedConnection = None
308 fullScreenShortcut = QShortcut(QKeySequence("F11"), self) 340 fullScreenShortcut = QShortcut(QKeySequence("F11"), self)
309 fullScreenShortcut.activated.connect(self.toggleFullScreen) 341 fullScreenShortcut.activated.connect(self.toggleFullScreen)
342 saveShortcut = QShortcut(QKeySequence(QKeySequence.Save), self)
343 saveShortcut.activated.connect(self.save)
344 self.loadDiagram('diagram.txt')
345
346 def save(self):
347 self.saveDiagram2('diagram2.txt')
348 def saveDiagram2(self, filename):
349 items = self.diagramScene.items()
350 blocks = [item for item in items if type(item) is BlockItem]
351 with open(filename, 'w') as f:
352 w = XMLWriter(f)
353 model = w.start('model')
354 for block in blocks:
355 w.element("block")
356
357 w.close(model)
358 def saveDiagram(self, filename):
359 items = self.diagramScene.items()
360 blocks = [item for item in items if type(item) is BlockItem]
361 with open(filename, 'w') as f:
362 for block in blocks:
363 rect = block.rect()
364 x, y, w, h = block.scenePos().x(), block.scenePos().y(), rect.width(), rect.height()
365 f.write('B({0};{1};{2};{3}) '.format(x,y,w,h)) # (x;y;w;h)
366
367 def loadDiagram(self, filename):
368 try:
369 with open(filename, 'r') as f:
370 txt = f.read()
371 except IOError as e:
372 print('{0} not found'.format(filename))
373 return
374 parts = txt.split(' ')
375 for part in parts:
376 if len(part) < 1:
377 continue
378 if part[0] == 'B':
379 part = part[2:-1]
380 x,y,w,h = [float(val) for val in part.split(';')]
381 block = BlockItem('Loaded') # TODO: store name.
382 self.diagramScene.addItem(block)
383 block.setPos(x, y)
384 block.sizer.setPos(w, h)
310 def toggleFullScreen(self): 385 def toggleFullScreen(self):
311 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) 386 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
312 def startConnection(self, port): 387 def startConnection(self, port):
313 self.startedConnection = Connection(port, None) 388 self.startedConnection = Connection(port, None)
314 def sceneMouseMoveEvent(self, event): 389 def sceneMouseMoveEvent(self, event):
316 pos = event.scenePos() 391 pos = event.scenePos()
317 self.startedConnection.setEndPos(pos) 392 self.startedConnection.setEndPos(pos)
318 def sceneMouseReleaseEvent(self, event): 393 def sceneMouseReleaseEvent(self, event):
319 # Clear the actual connection: 394 # Clear the actual connection:
320 if self.startedConnection: 395 if self.startedConnection:
321 pos = event.scenePos() 396 items = self.diagramScene.items(event.scenePos())
322 items = self.diagramScene.items(pos)
323 for item in items: 397 for item in items:
324 if type(item) is PortItem: 398 if type(item) is PortItem:
325 self.startedConnection.setToPort(item) 399 self.startedConnection.setToPort(item)
326 if self.startedConnection.toPort == None: 400 self.startedConnection = None
327 self.startedConnection.delete() 401 return
402 self.startedConnection.delete()
403 del self.startedConnection
328 self.startedConnection = None 404 self.startedConnection = None
329 405
330 if __name__ == '__main__': 406 if __name__ == '__main__':
331 if sys.version_info.major != 3: 407 if sys.version_info.major != 3:
332 print('Please use python 3.x') 408 print('Please use python 3.x')
333 sys.exit(1) 409 sys.exit(1)
334 410
335 app = QtGui.QApplication(sys.argv) 411 app = QApplication(sys.argv)
336 global editor 412 global editor
337 editor = DiagramEditor() 413 editor = DiagramEditor()
338 editor.show() 414 editor.show()
339 editor.resize(700, 800) 415 editor.resize(700, 800)
340 app.exec_() 416 app.exec_()