Graphics/UMLGraphicsView.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a subclass of E4GraphicsView for our diagrams.
8 """
9
10 import sys
11
12 from PyQt4.QtCore import *
13 from PyQt4.QtGui import *
14
15 from E4Graphics.E4GraphicsView import E4GraphicsView
16
17 from UMLItem import UMLItem
18 from UMLSceneSizeDialog import UMLSceneSizeDialog
19 from ZoomDialog import ZoomDialog
20
21 import UI.Config
22 import UI.PixmapCache
23
24 import Preferences
25
26 class UMLGraphicsView(E4GraphicsView):
27 """
28 Class implementing a specialized E4GraphicsView for our diagrams.
29
30 @signal relayout() emitted to indicate a relayout of the diagram
31 is requested
32 """
33 def __init__(self, scene, diagramName = "Unnamed", parent = None, name = None):
34 """
35 Constructor
36
37 @param scene reference to the scene object (QGraphicsScene)
38 @param diagramName name of the diagram (string)
39 @param parent parent widget of the view (QWidget)
40 @param name name of the view widget (string)
41 """
42 E4GraphicsView.__init__(self, scene, parent)
43 if name:
44 self.setObjectName(name)
45
46 self.diagramName = diagramName
47
48 self.border = 10
49 self.deltaSize = 100.0
50
51 self.__initActions()
52
53 self.connect(scene, SIGNAL("changed(const QList<QRectF> &)"), self.__sceneChanged)
54
55 def __initActions(self):
56 """
57 Private method to initialize the view actions.
58 """
59 self.deleteShapeAct = \
60 QAction(UI.PixmapCache.getIcon("deleteShape.png"),
61 self.trUtf8("Delete shapes"), self)
62 self.connect(self.deleteShapeAct, SIGNAL("triggered()"), self.__deleteShape)
63
64 self.saveAct = \
65 QAction(UI.PixmapCache.getIcon("fileSave.png"),
66 self.trUtf8("Save as PNG"), self)
67 self.connect(self.saveAct, SIGNAL("triggered()"), self.__saveImage)
68
69 self.printAct = \
70 QAction(UI.PixmapCache.getIcon("print.png"),
71 self.trUtf8("Print"), self)
72 self.connect(self.printAct, SIGNAL("triggered()"), self.__printDiagram)
73
74 self.printPreviewAct = \
75 QAction(UI.PixmapCache.getIcon("printPreview.png"),
76 self.trUtf8("Print Preview"), self)
77 self.connect(self.printPreviewAct, SIGNAL("triggered()"),
78 self.__printPreviewDiagram)
79
80 self.zoomInAct = \
81 QAction(UI.PixmapCache.getIcon("zoomIn.png"),
82 self.trUtf8("Zoom in"), self)
83 self.connect(self.zoomInAct, SIGNAL("triggered()"), self.zoomIn)
84
85 self.zoomOutAct = \
86 QAction(UI.PixmapCache.getIcon("zoomOut.png"),
87 self.trUtf8("Zoom out"), self)
88 self.connect(self.zoomOutAct, SIGNAL("triggered()"), self.zoomOut)
89
90 self.zoomAct = \
91 QAction(UI.PixmapCache.getIcon("zoomTo.png"),
92 self.trUtf8("Zoom..."), self)
93 self.connect(self.zoomAct, SIGNAL("triggered()"), self.__zoom)
94
95 self.zoomResetAct = \
96 QAction(UI.PixmapCache.getIcon("zoomReset.png"),
97 self.trUtf8("Zoom reset"), self)
98 self.connect(self.zoomResetAct, SIGNAL("triggered()"), self.zoomReset)
99
100 self.incWidthAct = \
101 QAction(UI.PixmapCache.getIcon("sceneWidthInc.png"),
102 self.trUtf8("Increase width by {0} points").format(self.deltaSize),
103 self)
104 self.connect(self.incWidthAct, SIGNAL("triggered()"), self.__incWidth)
105
106 self.incHeightAct = \
107 QAction(UI.PixmapCache.getIcon("sceneHeightInc.png"),
108 self.trUtf8("Increase height by {0} points").format(self.deltaSize),
109 self)
110 self.connect(self.incHeightAct, SIGNAL("triggered()"), self.__incHeight)
111
112 self.decWidthAct = \
113 QAction(UI.PixmapCache.getIcon("sceneWidthDec.png"),
114 self.trUtf8("Decrease width by {0} points").format(self.deltaSize),
115 self)
116 self.connect(self.decWidthAct, SIGNAL("triggered()"), self.__decWidth)
117
118 self.decHeightAct = \
119 QAction(UI.PixmapCache.getIcon("sceneHeightDec.png"),
120 self.trUtf8("Decrease height by {0} points").format(self.deltaSize),
121 self)
122 self.connect(self.decHeightAct, SIGNAL("triggered()"), self.__decHeight)
123
124 self.setSizeAct = \
125 QAction(UI.PixmapCache.getIcon("sceneSize.png"),
126 self.trUtf8("Set size"), self)
127 self.connect(self.setSizeAct, SIGNAL("triggered()"), self.__setSize)
128
129 self.relayoutAct = \
130 QAction(UI.PixmapCache.getIcon("reload.png"),
131 self.trUtf8("Re-Layout"), self)
132 self.connect(self.relayoutAct, SIGNAL("triggered()"), self.__relayout)
133
134 self.alignLeftAct = \
135 QAction(UI.PixmapCache.getIcon("shapesAlignLeft"),
136 self.trUtf8("Align Left"), self)
137 self.connect(self.alignLeftAct, SIGNAL("triggered()"),
138 lambda align=Qt.AlignLeft: self.__alignShapes(align))
139
140 self.alignHCenterAct = \
141 QAction(UI.PixmapCache.getIcon("shapesAlignHCenter"),
142 self.trUtf8("Align Center Horizontal"), self)
143 self.connect(self.alignHCenterAct, SIGNAL("triggered()"),
144 lambda align=Qt.AlignHCenter: self.__alignShapes(align))
145
146 self.alignRightAct = \
147 QAction(UI.PixmapCache.getIcon("shapesAlignRight"),
148 self.trUtf8("Align Right"), self)
149 self.connect(self.alignRightAct, SIGNAL("triggered()"),
150 lambda align=Qt.AlignRight: self.__alignShapes(align))
151
152 self.alignTopAct = \
153 QAction(UI.PixmapCache.getIcon("shapesAlignTop"),
154 self.trUtf8("Align Top"), self)
155 self.connect(self.alignTopAct, SIGNAL("triggered()"),
156 lambda align=Qt.AlignTop: self.__alignShapes(align))
157
158 self.alignVCenterAct = \
159 QAction(UI.PixmapCache.getIcon("shapesAlignVCenter"),
160 self.trUtf8("Align Center Vertical"), self)
161 self.connect(self.alignVCenterAct, SIGNAL("triggered()"),
162 lambda align=Qt.AlignVCenter: self.__alignShapes(align))
163
164 self.alignBottomAct = \
165 QAction(UI.PixmapCache.getIcon("shapesAlignBottom"),
166 self.trUtf8("Align Bottom"), self)
167 self.connect(self.alignBottomAct, SIGNAL("triggered()"),
168 lambda align=Qt.AlignBottom: self.__alignShapes(align))
169
170 def __checkSizeActions(self):
171 """
172 Private slot to set the enabled state of the size actions.
173 """
174 diagramSize = self._getDiagramSize(10)
175 sceneRect = self.scene().sceneRect()
176 if (sceneRect.width() - self.deltaSize) <= diagramSize.width():
177 self.decWidthAct.setEnabled(False)
178 else:
179 self.decWidthAct.setEnabled(True)
180 if (sceneRect.height() - self.deltaSize) <= diagramSize.height():
181 self.decHeightAct.setEnabled(False)
182 else:
183 self.decHeightAct.setEnabled(True)
184
185 def __sceneChanged(self, areas):
186 """
187 Private slot called when the scene changes.
188
189 @param areas list of rectangles that contain changes (list of QRectF)
190 """
191 if len(self.scene().selectedItems()) > 0:
192 self.deleteShapeAct.setEnabled(True)
193 else:
194 self.deleteShapeAct.setEnabled(False)
195
196 def initToolBar(self):
197 """
198 Public method to populate a toolbar with our actions.
199
200 @return the populated toolBar (QToolBar)
201 """
202 toolBar = QToolBar(self.trUtf8("Graphics"), self)
203 toolBar.setIconSize(UI.Config.ToolBarIconSize)
204 toolBar.addAction(self.deleteShapeAct)
205 toolBar.addSeparator()
206 toolBar.addAction(self.saveAct)
207 toolBar.addSeparator()
208 toolBar.addAction(self.printPreviewAct)
209 toolBar.addAction(self.printAct)
210 toolBar.addSeparator()
211 toolBar.addAction(self.zoomInAct)
212 toolBar.addAction(self.zoomOutAct)
213 toolBar.addAction(self.zoomAct)
214 toolBar.addAction(self.zoomResetAct)
215 toolBar.addSeparator()
216 toolBar.addAction(self.alignLeftAct)
217 toolBar.addAction(self.alignHCenterAct)
218 toolBar.addAction(self.alignRightAct)
219 toolBar.addAction(self.alignTopAct)
220 toolBar.addAction(self.alignVCenterAct)
221 toolBar.addAction(self.alignBottomAct)
222 toolBar.addSeparator()
223 toolBar.addAction(self.incWidthAct)
224 toolBar.addAction(self.incHeightAct)
225 toolBar.addAction(self.decWidthAct)
226 toolBar.addAction(self.decHeightAct)
227 toolBar.addAction(self.setSizeAct)
228 toolBar.addSeparator()
229 toolBar.addAction(self.relayoutAct)
230
231 return toolBar
232
233 def filteredItems(self, items):
234 """
235 Public method to filter a list of items.
236
237 @param items list of items as returned by the scene object
238 (QGraphicsItem)
239 @return list of interesting collision items (QGraphicsItem)
240 """
241 return [itm for itm in items if isinstance(itm, UMLItem)]
242
243 def selectItems(self, items):
244 """
245 Public method to select the given items.
246
247 @param items list of items to be selected (list of QGraphicsItemItem)
248 """
249 # step 1: deselect all items
250 self.unselectItems()
251
252 # step 2: select all given items
253 for itm in items:
254 if isinstance(itm, UMLWidget):
255 itm.setSelected(True)
256
257 def selectItem(self, item):
258 """
259 Public method to select an item.
260
261 @param item item to be selected (QGraphicsItemItem)
262 """
263 if isinstance(item, UMLWidget):
264 item.setSelected(not item.isSelected())
265
266 def __deleteShape(self):
267 """
268 Private method to delete the selected shapes from the display.
269 """
270 for item in self.scene().selectedItems():
271 item.removeAssociations()
272 item.setSelected(False)
273 self.scene().removeItem(item)
274 del item
275
276 def __incWidth(self):
277 """
278 Private method to handle the increase width context menu entry.
279 """
280 self.resizeScene(self.deltaSize, True)
281 self.__checkSizeActions()
282
283 def __incHeight(self):
284 """
285 Private method to handle the increase height context menu entry.
286 """
287 self.resizeScene(self.deltaSize, False)
288 self.__checkSizeActions()
289
290 def __decWidth(self):
291 """
292 Private method to handle the decrease width context menu entry.
293 """
294 self.resizeScene(-self.deltaSize, True)
295 self.__checkSizeActions()
296
297 def __decHeight(self):
298 """
299 Private method to handle the decrease height context menu entry.
300 """
301 self.resizeScene(-self.deltaSize, False)
302 self.__checkSizeActions()
303
304 def __setSize(self):
305 """
306 Private method to handle the set size context menu entry.
307 """
308 rect = self._getDiagramRect(10)
309 sceneRect = self.scene().sceneRect()
310 dlg = UMLSceneSizeDialog(sceneRect.width(), sceneRect.height(),
311 rect.width(), rect.height(), self)
312 if dlg.exec_() == QDialog.Accepted:
313 width, height = dlg.getData()
314 self.setSceneSize(width, height)
315 self.__checkSizeActions()
316
317 def __saveImage(self):
318 """
319 Private method to handle the save context menu entry.
320 """
321 fname, selectedFilter = QFileDialog.getSaveFileNameAndFilter(\
322 self,
323 self.trUtf8("Save Diagram"),
324 "",
325 self.trUtf8("Portable Network Graphics (*.png);;"
326 "Scalable Vector Graphics (*.svg)"),
327 "",
328 QFileDialog.Options(QFileDialog.DontConfirmOverwrite))
329 if fname:
330 ext = QFileInfo(fname).suffix()
331 if not ext:
332 ex = selectedFilter.split("(*")[1].split(")")[0]
333 if ex:
334 fname += ex
335 if QFileInfo(fname).exists():
336 res = QMessageBox.warning(self,
337 self.trUtf8("Save Diagram"),
338 self.trUtf8("<p>The file <b>{0}</b> already exists.</p>")
339 .format(fname),
340 QMessageBox.StandardButtons(\
341 QMessageBox.Abort | \
342 QMessageBox.Save),
343 QMessageBox.Abort)
344 if res == QMessageBox.Abort or res == QMessageBox.Cancel:
345 return
346
347 success = self.saveImage(fname, QFileInfo(fname).suffix().upper())
348 if not success:
349 QMessageBox.critical(None,
350 self.trUtf8("Save Diagram"),
351 self.trUtf8("""<p>The file <b>{0}</b> could not be saved.</p>""")
352 .format(fname))
353
354 def __relayout(self):
355 """
356 Private method to handle the re-layout context menu entry.
357 """
358 scene = self.scene()
359 for itm in scene.items()[:]:
360 if itm.scene() == scene:
361 scene.removeItem(itm)
362 self.emit(SIGNAL("relayout()"))
363
364 def __printDiagram(self):
365 """
366 Private slot called to print the diagram.
367 """
368 printer = QPrinter(mode = QPrinter.ScreenResolution)
369 printer.setFullPage(True)
370 if Preferences.getPrinter("ColorMode"):
371 printer.setColorMode(QPrinter.Color)
372 else:
373 printer.setColorMode(QPrinter.GrayScale)
374 if Preferences.getPrinter("FirstPageFirst"):
375 printer.setPageOrder(QPrinter.FirstPageFirst)
376 else:
377 printer.setPageOrder(QPrinter.LastPageFirst)
378 printer.setPrinterName(Preferences.getPrinter("PrinterName"))
379
380 printDialog = QPrintDialog(printer, self)
381 if printDialog.exec_():
382 self.printDiagram(printer, self.diagramName)
383
384 def __printPreviewDiagram(self):
385 """
386 Private slot called to show a print preview of the diagram.
387 """
388 from PyQt4.QtGui import QPrintPreviewDialog
389
390 printer = QPrinter(mode = QPrinter.ScreenResolution)
391 printer.setFullPage(True)
392 if Preferences.getPrinter("ColorMode"):
393 printer.setColorMode(QPrinter.Color)
394 else:
395 printer.setColorMode(QPrinter.GrayScale)
396 if Preferences.getPrinter("FirstPageFirst"):
397 printer.setPageOrder(QPrinter.FirstPageFirst)
398 else:
399 printer.setPageOrder(QPrinter.LastPageFirst)
400 printer.setPrinterName(Preferences.getPrinter("PrinterName"))
401
402 preview = QPrintPreviewDialog(printer, self)
403 self.connect(preview, SIGNAL("paintRequested(QPrinter*)"), self.printDiagram)
404 preview.exec_()
405
406 def __zoom(self):
407 """
408 Private method to handle the zoom context menu action.
409 """
410 dlg = ZoomDialog(self.zoom(), self)
411 if dlg.exec_() == QDialog.Accepted:
412 zoom = dlg.getZoomSize()
413 self.setZoom(zoom)
414
415 def setDiagramName(self, name):
416 """
417 Public slot to set the diagram name.
418
419 @param name diagram name (string)
420 """
421 self.diagramName = name
422
423 def __alignShapes(self, alignment):
424 """
425 Private slot to align the selected shapes.
426
427 @param alignment alignment type (Qt.AlignmentFlag)
428 """
429 # step 1: get all selected items
430 items = self.scene().selectedItems()
431 if len(items) <= 1:
432 return
433
434 # step 2: find the index of the item to align in relation to
435 amount = None
436 for i, item in enumerate(items):
437 rect = item.sceneBoundingRect()
438 if alignment == Qt.AlignLeft:
439 if amount is None or rect.x() < amount:
440 amount = rect.x()
441 index = i
442 elif alignment == Qt.AlignRight:
443 if amount is None or rect.x() + rect.width() > amount:
444 amount = rect.x() + rect.width()
445 index = i
446 elif alignment == Qt.AlignHCenter:
447 if amount is None or rect.width() > amount:
448 amount = rect.width()
449 index = i
450 elif alignment == Qt.AlignTop:
451 if amount is None or rect.y() < amount:
452 amount = rect.y()
453 index = i
454 elif alignment == Qt.AlignBottom:
455 if amount is None or rect.y() + rect.height() > amount:
456 amount = rect.y() + rect.height()
457 index = i
458 elif alignment == Qt.AlignVCenter:
459 if amount is None or rect.height() > amount:
460 amount = rect.height()
461 index = i
462 rect = items[index].sceneBoundingRect()
463
464 # step 3: move the other items
465 for i, item in enumerate(items):
466 if i == index:
467 continue
468 itemrect = item.sceneBoundingRect()
469 xOffset = yOffset = 0
470 if alignment == Qt.AlignLeft:
471 xOffset = rect.x() - itemrect.x()
472 elif alignment == Qt.AlignRight:
473 xOffset = (rect.x() + rect.width()) - \
474 (itemrect.x() + itemrect.width())
475 elif alignment == Qt.AlignHCenter:
476 xOffset = (rect.x() + rect.width() / 2) - \
477 (itemrect.x() + itemrect.width() / 2)
478 elif alignment == Qt.AlignTop:
479 yOffset = rect.y() - itemrect.y()
480 elif alignment == Qt.AlignBottom:
481 yOffset = (rect.y() + rect.height()) - \
482 (itemrect.y() + itemrect.height())
483 elif alignment == Qt.AlignVCenter:
484 yOffset = (rect.y() + rect.height() / 2) - \
485 (itemrect.y() + itemrect.height() / 2)
486 item.moveBy(xOffset, yOffset)
487
488 self.scene().update()

eric ide

mercurial