Mercurial > lcfOS
comparison applications/lab/diagrameditor.py @ 44:cbf199e007c2
Added some demo applications
author | windel |
---|---|
date | Sat, 18 Feb 2012 16:42:23 +0100 |
parents | |
children | 8a52263d67c4 |
comparison
equal
deleted
inserted
replaced
43:e47bfef80baf | 44:cbf199e007c2 |
---|---|
1 #!/usr/bin/python | |
2 | |
3 from PyQt4 import QtGui, QtCore | |
4 from PyQt4.QtGui import * | |
5 from PyQt4.QtCore import * | |
6 import sys | |
7 | |
8 """ | |
9 This script implements a basic diagram editor. | |
10 """ | |
11 | |
12 class Connection: | |
13 """ | |
14 - fromPort | |
15 - list of line items in between | |
16 - toPort | |
17 """ | |
18 def __init__(self, fromPort, toPort): | |
19 self.fromPort = fromPort | |
20 self.pos1 = None | |
21 self.pos2 = None | |
22 self.p1dir = None | |
23 self.p2dir = None | |
24 self.setFromPort(fromPort) | |
25 self.toPort = toPort | |
26 # Create arrow item: | |
27 self.linePieces = [] | |
28 def setFromPort(self, fromPort): | |
29 self.fromPort = fromPort | |
30 if self.fromPort: | |
31 self.pos1 = fromPort.scenePos() | |
32 self.fromPort.posCallbacks.append(self.setBeginPos) | |
33 def setToPort(self, toPort): | |
34 self.toPort = toPort | |
35 if self.toPort: | |
36 self.pos2 = toPort.scenePos() | |
37 self.toPort.posCallbacks.append(self.setEndPos) | |
38 def setBeginPos(self, pos1): | |
39 self.pos1 = pos1 | |
40 if self.pos1 and self.pos2: | |
41 self.updateLineStukken() | |
42 def setEndPos(self, endpos): | |
43 self.pos2 = endpos | |
44 if self.pos1 and self.pos2: | |
45 self.updateLineStukken() | |
46 def updateLineStukken(self): | |
47 """ | |
48 This algorithm determines the optimal routing of all signals. | |
49 TODO: implement nice automatic line router | |
50 """ | |
51 # TODO: create pieces of lines. | |
52 | |
53 # Determine the current amount of linestukken: | |
54 x1, y1 = self.pos1.x(), self.pos1.y() | |
55 x2, y2 = self.pos2.x(), self.pos2.y() | |
56 | |
57 ds = editor.diagramScene | |
58 | |
59 if y1 == y2 or x1 == x2: | |
60 pass | |
61 else: | |
62 # We require two lijnstukken to make one corner! | |
63 while len(self.linePieces) < 2: | |
64 lp = LinePieceItem() | |
65 ds.addItem(lp) | |
66 self.linePieces.append(lp) | |
67 lp1 = self.linePieces[0] | |
68 lp2 = self.linePieces[1] | |
69 lp1.setLine(QLineF(x1, y1, x2, y1)) | |
70 lp2.setLine(QLineF(x2, y1, x2, y2)) | |
71 | |
72 def delete(self): | |
73 editor.diagramScene.removeItem(self.arrow) | |
74 # Remove position update callbacks: | |
75 | |
76 class ParameterDialog(QDialog): | |
77 def __init__(self, parent=None): | |
78 super(ParameterDialog, self).__init__(parent) | |
79 self.button = QPushButton('Ok', self) | |
80 l = QVBoxLayout(self) | |
81 l.addWidget(self.button) | |
82 self.button.clicked.connect(self.OK) | |
83 def OK(self): | |
84 self.close() | |
85 | |
86 class PortItem(QGraphicsEllipseItem): | |
87 """ Represents a port to a subsystem """ | |
88 def __init__(self, name, parent=None): | |
89 QGraphicsEllipseItem.__init__(self, QRectF(-6,-6,12.0,12.0), parent) | |
90 self.setCursor(QCursor(QtCore.Qt.CrossCursor)) | |
91 # Properties: | |
92 self.setBrush(QBrush(Qt.red)) | |
93 # Name: | |
94 self.name = name | |
95 self.posCallbacks = [] | |
96 self.setFlag(self.ItemSendsScenePositionChanges, True) | |
97 def itemChange(self, change, value): | |
98 if change == self.ItemScenePositionHasChanged: | |
99 value = value.toPointF() # Required! | |
100 for cb in self.posCallbacks: | |
101 cb(value) | |
102 return value | |
103 return super(PortItem, self).itemChange(change, value) | |
104 def mousePressEvent(self, event): | |
105 editor.startConnection(self) | |
106 | |
107 # Block part: | |
108 class HandleItem(QGraphicsEllipseItem): | |
109 """ A handle that can be moved by the mouse """ | |
110 def __init__(self, parent=None): | |
111 super(HandleItem, self).__init__(QRectF(-4.0,-4.0,8.0,8.0), parent) | |
112 self.posChangeCallbacks = [] | |
113 self.setBrush(QtGui.QBrush(Qt.white)) | |
114 self.setFlag(self.ItemIsMovable, True) | |
115 self.setFlag(self.ItemSendsScenePositionChanges, True) | |
116 self.setCursor(QtGui.QCursor(Qt.SizeFDiagCursor)) | |
117 | |
118 def itemChange(self, change, value): | |
119 if change == self.ItemPositionChange: | |
120 value = value.toPointF() | |
121 x, y = value.x(), value.y() | |
122 # TODO: make this a signal? | |
123 # This cannot be a signal because this is not a QObject | |
124 for cb in self.posChangeCallbacks: | |
125 res = cb(x, y) | |
126 if res: | |
127 x, y = res | |
128 value = QPointF(x, y) | |
129 return value | |
130 # Call superclass method: | |
131 return super(HandleItem, self).itemChange(change, value) | |
132 | |
133 class BlockItem(QGraphicsRectItem): | |
134 """ | |
135 Represents a block in the diagram | |
136 Has an x and y and width and height | |
137 width and height can only be adjusted with a tip in the lower right corner. | |
138 | |
139 - in and output ports | |
140 - parameters | |
141 - description | |
142 """ | |
143 def __init__(self, name='Untitled', parent=None): | |
144 super(BlockItem, self).__init__(parent) | |
145 w = 60.0 | |
146 h = 40.0 | |
147 # Properties of the rectangle: | |
148 self.setPen(QtGui.QPen(QtCore.Qt.blue, 2)) | |
149 self.setBrush(QtGui.QBrush(QtCore.Qt.lightGray)) | |
150 self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) | |
151 self.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) | |
152 # Label: | |
153 self.label = QGraphicsTextItem(name, self) | |
154 # Create corner for resize: | |
155 self.sizer = HandleItem(self) | |
156 self.sizer.setPos(w, h) | |
157 self.sizer.posChangeCallbacks.append(self.changeSize) # Connect the callback | |
158 #self.sizer.setVisible(False) | |
159 self.sizer.setFlag(self.sizer.ItemIsSelectable, True) | |
160 | |
161 # Inputs and outputs of the block: | |
162 self.inputs = [] | |
163 self.inputs.append( PortItem('a', self) ) | |
164 self.inputs.append( PortItem('b', self) ) | |
165 self.inputs.append( PortItem('c', self) ) | |
166 self.outputs = [] | |
167 self.outputs.append( PortItem('y', self) ) | |
168 # Update size: | |
169 self.changeSize(w, h) | |
170 def editParameters(self): | |
171 pd = ParameterDialog(self.window()) | |
172 pd.exec_() | |
173 | |
174 def contextMenuEvent(self, event): | |
175 menu = QMenu() | |
176 menu.addAction('Delete') | |
177 pa = menu.addAction('Parameters') | |
178 pa.triggered.connect(self.editParameters) | |
179 menu.exec_(event.screenPos()) | |
180 | |
181 def changeSize(self, w, h): | |
182 """ Resize block function """ | |
183 # Limit the block size: | |
184 if h < 20: | |
185 h = 20 | |
186 if w < 40: | |
187 w = 40 | |
188 self.setRect(0.0, 0.0, w, h) | |
189 # center label: | |
190 rect = self.label.boundingRect() | |
191 lw, lh = rect.width(), rect.height() | |
192 lx = (w - lw) / 2 | |
193 ly = (h - lh) / 2 | |
194 self.label.setPos(lx, ly) | |
195 # Update port positions: | |
196 if len(self.inputs) == 1: | |
197 self.inputs[0].setPos(-4, h / 2) | |
198 elif len(self.inputs) > 1: | |
199 y = 5 | |
200 dy = (h - 10) / (len(self.inputs) - 1) | |
201 for inp in self.inputs: | |
202 inp.setPos(-4, y) | |
203 y += dy | |
204 if len(self.outputs) == 1: | |
205 self.outputs[0].setPos(w+4, h / 2) | |
206 elif len(self.outputs) > 1: | |
207 y = 5 | |
208 dy = (h - 10) / (len(self.outputs) + 0) | |
209 for outp in self.outputs: | |
210 outp.setPos(w+4, y) | |
211 y += dy | |
212 return w, h | |
213 | |
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): | |
223 def __init__(self, scene, parent=None): | |
224 QGraphicsView.__init__(self, scene, parent) | |
225 def dragEnterEvent(self, event): | |
226 if event.mimeData().hasFormat('component/name'): | |
227 event.accept() | |
228 def dragMoveEvent(self, event): | |
229 if event.mimeData().hasFormat('component/name'): | |
230 event.accept() | |
231 def dropEvent(self, event): | |
232 if event.mimeData().hasFormat('component/name'): | |
233 name = str(event.mimeData().data('component/name')) | |
234 b1 = BlockItem(name) | |
235 b1.setPos(self.mapToScene(event.pos())) | |
236 self.scene().addItem(b1) | |
237 | |
238 class LibraryModel(QStandardItemModel): | |
239 def __init__(self, parent=None): | |
240 QStandardItemModel.__init__(self, parent) | |
241 def mimeTypes(self): | |
242 return ['component/name'] | |
243 def mimeData(self, idxs): | |
244 mimedata = QMimeData() | |
245 for idx in idxs: | |
246 if idx.isValid(): | |
247 txt = self.data(idx, Qt.DisplayRole).toByteArray() | |
248 mimedata.setData('component/name', txt) | |
249 return mimedata | |
250 | |
251 class DiagramScene(QGraphicsScene): | |
252 def __init__(self, parent=None): | |
253 super(DiagramScene, self).__init__(parent) | |
254 def mouseMoveEvent(self, mouseEvent): | |
255 editor.sceneMouseMoveEvent(mouseEvent) | |
256 super(DiagramScene, self).mouseMoveEvent(mouseEvent) | |
257 def mouseReleaseEvent(self, mouseEvent): | |
258 editor.sceneMouseReleaseEvent(mouseEvent) | |
259 super(DiagramScene, self).mouseReleaseEvent(mouseEvent) | |
260 | |
261 class DiagramEditor(QWidget): | |
262 def __init__(self, parent=None): | |
263 QtGui.QWidget.__init__(self, parent) | |
264 self.setWindowTitle("Diagram editor") | |
265 | |
266 # Widget layout and child widgets: | |
267 self.horizontalLayout = QtGui.QHBoxLayout(self) | |
268 self.libraryBrowserView = QtGui.QListView(self) | |
269 self.libraryModel = LibraryModel(self) | |
270 self.libraryModel.setColumnCount(1) | |
271 # Create an icon with an icon: | |
272 pixmap = QPixmap(60, 60) | |
273 pixmap.fill() | |
274 painter = QPainter(pixmap) | |
275 painter.fillRect(10, 10, 40, 40, Qt.blue) | |
276 painter.setBrush(Qt.red) | |
277 painter.drawEllipse(36, 2, 20, 20) | |
278 painter.setBrush(Qt.yellow) | |
279 painter.drawEllipse(20, 20, 20, 20) | |
280 painter.end() | |
281 | |
282 self.libItems = [] | |
283 self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Block') ) | |
284 self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Uber Unit') ) | |
285 self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Device') ) | |
286 for i in self.libItems: | |
287 self.libraryModel.appendRow(i) | |
288 self.libraryBrowserView.setModel(self.libraryModel) | |
289 self.libraryBrowserView.setViewMode(self.libraryBrowserView.IconMode) | |
290 self.libraryBrowserView.setDragDropMode(self.libraryBrowserView.DragOnly) | |
291 | |
292 self.diagramScene = DiagramScene(self) | |
293 self.diagramView = EditorGraphicsView(self.diagramScene, self) | |
294 self.horizontalLayout.addWidget(self.libraryBrowserView) | |
295 self.horizontalLayout.addWidget(self.diagramView) | |
296 | |
297 # Populate the diagram scene: | |
298 b1 = BlockItem('SubSystem1') | |
299 b1.setPos(50,100) | |
300 self.diagramScene.addItem(b1) | |
301 b2 = BlockItem('Unit2') | |
302 b2.setPos(-250,0) | |
303 self.diagramScene.addItem(b2) | |
304 | |
305 self.startedConnection = None | |
306 fullScreenShortcut = QShortcut(QKeySequence("F11"), self) | |
307 fullScreenShortcut.activated.connect(self.toggleFullScreen) | |
308 def toggleFullScreen(self): | |
309 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) | |
310 def startConnection(self, port): | |
311 self.startedConnection = Connection(port, None) | |
312 def sceneMouseMoveEvent(self, event): | |
313 if self.startedConnection: | |
314 pos = event.scenePos() | |
315 self.startedConnection.setEndPos(pos) | |
316 def sceneMouseReleaseEvent(self, event): | |
317 # Clear the actual connection: | |
318 if self.startedConnection: | |
319 pos = event.scenePos() | |
320 items = self.diagramScene.items(pos) | |
321 for item in items: | |
322 if type(item) is PortItem: | |
323 self.startedConnection.setToPort(item) | |
324 if self.startedConnection.toPort == None: | |
325 self.startedConnection.delete() | |
326 self.startedConnection = None | |
327 | |
328 if __name__ == '__main__': | |
329 app = QtGui.QApplication(sys.argv) | |
330 global editor | |
331 editor = DiagramEditor() | |
332 editor.show() | |
333 editor.resize(700, 800) | |
334 app.exec_() | |
335 |