Mercurial > lcfOS
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_() |