diff -r 000000000000 -r de9c2efb9d02 Graphics/UMLGraphicsView.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Graphics/UMLGraphicsView.py Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,488 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2007 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a subclass of E4GraphicsView for our diagrams. +""" + +import sys + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from E4Graphics.E4GraphicsView import E4GraphicsView + +from UMLItem import UMLItem +from UMLSceneSizeDialog import UMLSceneSizeDialog +from ZoomDialog import ZoomDialog + +import UI.Config +import UI.PixmapCache + +import Preferences + +class UMLGraphicsView(E4GraphicsView): + """ + Class implementing a specialized E4GraphicsView for our diagrams. + + @signal relayout() emitted to indicate a relayout of the diagram + is requested + """ + def __init__(self, scene, diagramName = "Unnamed", parent = None, name = None): + """ + Constructor + + @param scene reference to the scene object (QGraphicsScene) + @param diagramName name of the diagram (string) + @param parent parent widget of the view (QWidget) + @param name name of the view widget (string) + """ + E4GraphicsView.__init__(self, scene, parent) + if name: + self.setObjectName(name) + + self.diagramName = diagramName + + self.border = 10 + self.deltaSize = 100.0 + + self.__initActions() + + self.connect(scene, SIGNAL("changed(const QList<QRectF> &)"), self.__sceneChanged) + + def __initActions(self): + """ + Private method to initialize the view actions. + """ + self.deleteShapeAct = \ + QAction(UI.PixmapCache.getIcon("deleteShape.png"), + self.trUtf8("Delete shapes"), self) + self.connect(self.deleteShapeAct, SIGNAL("triggered()"), self.__deleteShape) + + self.saveAct = \ + QAction(UI.PixmapCache.getIcon("fileSave.png"), + self.trUtf8("Save as PNG"), self) + self.connect(self.saveAct, SIGNAL("triggered()"), self.__saveImage) + + self.printAct = \ + QAction(UI.PixmapCache.getIcon("print.png"), + self.trUtf8("Print"), self) + self.connect(self.printAct, SIGNAL("triggered()"), self.__printDiagram) + + self.printPreviewAct = \ + QAction(UI.PixmapCache.getIcon("printPreview.png"), + self.trUtf8("Print Preview"), self) + self.connect(self.printPreviewAct, SIGNAL("triggered()"), + self.__printPreviewDiagram) + + self.zoomInAct = \ + QAction(UI.PixmapCache.getIcon("zoomIn.png"), + self.trUtf8("Zoom in"), self) + self.connect(self.zoomInAct, SIGNAL("triggered()"), self.zoomIn) + + self.zoomOutAct = \ + QAction(UI.PixmapCache.getIcon("zoomOut.png"), + self.trUtf8("Zoom out"), self) + self.connect(self.zoomOutAct, SIGNAL("triggered()"), self.zoomOut) + + self.zoomAct = \ + QAction(UI.PixmapCache.getIcon("zoomTo.png"), + self.trUtf8("Zoom..."), self) + self.connect(self.zoomAct, SIGNAL("triggered()"), self.__zoom) + + self.zoomResetAct = \ + QAction(UI.PixmapCache.getIcon("zoomReset.png"), + self.trUtf8("Zoom reset"), self) + self.connect(self.zoomResetAct, SIGNAL("triggered()"), self.zoomReset) + + self.incWidthAct = \ + QAction(UI.PixmapCache.getIcon("sceneWidthInc.png"), + self.trUtf8("Increase width by {0} points").format(self.deltaSize), + self) + self.connect(self.incWidthAct, SIGNAL("triggered()"), self.__incWidth) + + self.incHeightAct = \ + QAction(UI.PixmapCache.getIcon("sceneHeightInc.png"), + self.trUtf8("Increase height by {0} points").format(self.deltaSize), + self) + self.connect(self.incHeightAct, SIGNAL("triggered()"), self.__incHeight) + + self.decWidthAct = \ + QAction(UI.PixmapCache.getIcon("sceneWidthDec.png"), + self.trUtf8("Decrease width by {0} points").format(self.deltaSize), + self) + self.connect(self.decWidthAct, SIGNAL("triggered()"), self.__decWidth) + + self.decHeightAct = \ + QAction(UI.PixmapCache.getIcon("sceneHeightDec.png"), + self.trUtf8("Decrease height by {0} points").format(self.deltaSize), + self) + self.connect(self.decHeightAct, SIGNAL("triggered()"), self.__decHeight) + + self.setSizeAct = \ + QAction(UI.PixmapCache.getIcon("sceneSize.png"), + self.trUtf8("Set size"), self) + self.connect(self.setSizeAct, SIGNAL("triggered()"), self.__setSize) + + self.relayoutAct = \ + QAction(UI.PixmapCache.getIcon("reload.png"), + self.trUtf8("Re-Layout"), self) + self.connect(self.relayoutAct, SIGNAL("triggered()"), self.__relayout) + + self.alignLeftAct = \ + QAction(UI.PixmapCache.getIcon("shapesAlignLeft"), + self.trUtf8("Align Left"), self) + self.connect(self.alignLeftAct, SIGNAL("triggered()"), + lambda align=Qt.AlignLeft: self.__alignShapes(align)) + + self.alignHCenterAct = \ + QAction(UI.PixmapCache.getIcon("shapesAlignHCenter"), + self.trUtf8("Align Center Horizontal"), self) + self.connect(self.alignHCenterAct, SIGNAL("triggered()"), + lambda align=Qt.AlignHCenter: self.__alignShapes(align)) + + self.alignRightAct = \ + QAction(UI.PixmapCache.getIcon("shapesAlignRight"), + self.trUtf8("Align Right"), self) + self.connect(self.alignRightAct, SIGNAL("triggered()"), + lambda align=Qt.AlignRight: self.__alignShapes(align)) + + self.alignTopAct = \ + QAction(UI.PixmapCache.getIcon("shapesAlignTop"), + self.trUtf8("Align Top"), self) + self.connect(self.alignTopAct, SIGNAL("triggered()"), + lambda align=Qt.AlignTop: self.__alignShapes(align)) + + self.alignVCenterAct = \ + QAction(UI.PixmapCache.getIcon("shapesAlignVCenter"), + self.trUtf8("Align Center Vertical"), self) + self.connect(self.alignVCenterAct, SIGNAL("triggered()"), + lambda align=Qt.AlignVCenter: self.__alignShapes(align)) + + self.alignBottomAct = \ + QAction(UI.PixmapCache.getIcon("shapesAlignBottom"), + self.trUtf8("Align Bottom"), self) + self.connect(self.alignBottomAct, SIGNAL("triggered()"), + lambda align=Qt.AlignBottom: self.__alignShapes(align)) + + def __checkSizeActions(self): + """ + Private slot to set the enabled state of the size actions. + """ + diagramSize = self._getDiagramSize(10) + sceneRect = self.scene().sceneRect() + if (sceneRect.width() - self.deltaSize) <= diagramSize.width(): + self.decWidthAct.setEnabled(False) + else: + self.decWidthAct.setEnabled(True) + if (sceneRect.height() - self.deltaSize) <= diagramSize.height(): + self.decHeightAct.setEnabled(False) + else: + self.decHeightAct.setEnabled(True) + + def __sceneChanged(self, areas): + """ + Private slot called when the scene changes. + + @param areas list of rectangles that contain changes (list of QRectF) + """ + if len(self.scene().selectedItems()) > 0: + self.deleteShapeAct.setEnabled(True) + else: + self.deleteShapeAct.setEnabled(False) + + def initToolBar(self): + """ + Public method to populate a toolbar with our actions. + + @return the populated toolBar (QToolBar) + """ + toolBar = QToolBar(self.trUtf8("Graphics"), self) + toolBar.setIconSize(UI.Config.ToolBarIconSize) + toolBar.addAction(self.deleteShapeAct) + toolBar.addSeparator() + toolBar.addAction(self.saveAct) + toolBar.addSeparator() + toolBar.addAction(self.printPreviewAct) + toolBar.addAction(self.printAct) + toolBar.addSeparator() + toolBar.addAction(self.zoomInAct) + toolBar.addAction(self.zoomOutAct) + toolBar.addAction(self.zoomAct) + toolBar.addAction(self.zoomResetAct) + toolBar.addSeparator() + toolBar.addAction(self.alignLeftAct) + toolBar.addAction(self.alignHCenterAct) + toolBar.addAction(self.alignRightAct) + toolBar.addAction(self.alignTopAct) + toolBar.addAction(self.alignVCenterAct) + toolBar.addAction(self.alignBottomAct) + toolBar.addSeparator() + toolBar.addAction(self.incWidthAct) + toolBar.addAction(self.incHeightAct) + toolBar.addAction(self.decWidthAct) + toolBar.addAction(self.decHeightAct) + toolBar.addAction(self.setSizeAct) + toolBar.addSeparator() + toolBar.addAction(self.relayoutAct) + + return toolBar + + def filteredItems(self, items): + """ + Public method to filter a list of items. + + @param items list of items as returned by the scene object + (QGraphicsItem) + @return list of interesting collision items (QGraphicsItem) + """ + return [itm for itm in items if isinstance(itm, UMLItem)] + + def selectItems(self, items): + """ + Public method to select the given items. + + @param items list of items to be selected (list of QGraphicsItemItem) + """ + # step 1: deselect all items + self.unselectItems() + + # step 2: select all given items + for itm in items: + if isinstance(itm, UMLWidget): + itm.setSelected(True) + + def selectItem(self, item): + """ + Public method to select an item. + + @param item item to be selected (QGraphicsItemItem) + """ + if isinstance(item, UMLWidget): + item.setSelected(not item.isSelected()) + + def __deleteShape(self): + """ + Private method to delete the selected shapes from the display. + """ + for item in self.scene().selectedItems(): + item.removeAssociations() + item.setSelected(False) + self.scene().removeItem(item) + del item + + def __incWidth(self): + """ + Private method to handle the increase width context menu entry. + """ + self.resizeScene(self.deltaSize, True) + self.__checkSizeActions() + + def __incHeight(self): + """ + Private method to handle the increase height context menu entry. + """ + self.resizeScene(self.deltaSize, False) + self.__checkSizeActions() + + def __decWidth(self): + """ + Private method to handle the decrease width context menu entry. + """ + self.resizeScene(-self.deltaSize, True) + self.__checkSizeActions() + + def __decHeight(self): + """ + Private method to handle the decrease height context menu entry. + """ + self.resizeScene(-self.deltaSize, False) + self.__checkSizeActions() + + def __setSize(self): + """ + Private method to handle the set size context menu entry. + """ + rect = self._getDiagramRect(10) + sceneRect = self.scene().sceneRect() + dlg = UMLSceneSizeDialog(sceneRect.width(), sceneRect.height(), + rect.width(), rect.height(), self) + if dlg.exec_() == QDialog.Accepted: + width, height = dlg.getData() + self.setSceneSize(width, height) + self.__checkSizeActions() + + def __saveImage(self): + """ + Private method to handle the save context menu entry. + """ + fname, selectedFilter = QFileDialog.getSaveFileNameAndFilter(\ + self, + self.trUtf8("Save Diagram"), + "", + self.trUtf8("Portable Network Graphics (*.png);;" + "Scalable Vector Graphics (*.svg)"), + "", + QFileDialog.Options(QFileDialog.DontConfirmOverwrite)) + if fname: + ext = QFileInfo(fname).suffix() + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fname += ex + if QFileInfo(fname).exists(): + res = QMessageBox.warning(self, + self.trUtf8("Save Diagram"), + self.trUtf8("<p>The file <b>{0}</b> already exists.</p>") + .format(fname), + QMessageBox.StandardButtons(\ + QMessageBox.Abort | \ + QMessageBox.Save), + QMessageBox.Abort) + if res == QMessageBox.Abort or res == QMessageBox.Cancel: + return + + success = self.saveImage(fname, QFileInfo(fname).suffix().upper()) + if not success: + QMessageBox.critical(None, + self.trUtf8("Save Diagram"), + self.trUtf8("""<p>The file <b>{0}</b> could not be saved.</p>""") + .format(fname)) + + def __relayout(self): + """ + Private method to handle the re-layout context menu entry. + """ + scene = self.scene() + for itm in scene.items()[:]: + if itm.scene() == scene: + scene.removeItem(itm) + self.emit(SIGNAL("relayout()")) + + def __printDiagram(self): + """ + Private slot called to print the diagram. + """ + printer = QPrinter(mode = QPrinter.ScreenResolution) + printer.setFullPage(True) + if Preferences.getPrinter("ColorMode"): + printer.setColorMode(QPrinter.Color) + else: + printer.setColorMode(QPrinter.GrayScale) + if Preferences.getPrinter("FirstPageFirst"): + printer.setPageOrder(QPrinter.FirstPageFirst) + else: + printer.setPageOrder(QPrinter.LastPageFirst) + printer.setPrinterName(Preferences.getPrinter("PrinterName")) + + printDialog = QPrintDialog(printer, self) + if printDialog.exec_(): + self.printDiagram(printer, self.diagramName) + + def __printPreviewDiagram(self): + """ + Private slot called to show a print preview of the diagram. + """ + from PyQt4.QtGui import QPrintPreviewDialog + + printer = QPrinter(mode = QPrinter.ScreenResolution) + printer.setFullPage(True) + if Preferences.getPrinter("ColorMode"): + printer.setColorMode(QPrinter.Color) + else: + printer.setColorMode(QPrinter.GrayScale) + if Preferences.getPrinter("FirstPageFirst"): + printer.setPageOrder(QPrinter.FirstPageFirst) + else: + printer.setPageOrder(QPrinter.LastPageFirst) + printer.setPrinterName(Preferences.getPrinter("PrinterName")) + + preview = QPrintPreviewDialog(printer, self) + self.connect(preview, SIGNAL("paintRequested(QPrinter*)"), self.printDiagram) + preview.exec_() + + def __zoom(self): + """ + Private method to handle the zoom context menu action. + """ + dlg = ZoomDialog(self.zoom(), self) + if dlg.exec_() == QDialog.Accepted: + zoom = dlg.getZoomSize() + self.setZoom(zoom) + + def setDiagramName(self, name): + """ + Public slot to set the diagram name. + + @param name diagram name (string) + """ + self.diagramName = name + + def __alignShapes(self, alignment): + """ + Private slot to align the selected shapes. + + @param alignment alignment type (Qt.AlignmentFlag) + """ + # step 1: get all selected items + items = self.scene().selectedItems() + if len(items) <= 1: + return + + # step 2: find the index of the item to align in relation to + amount = None + for i, item in enumerate(items): + rect = item.sceneBoundingRect() + if alignment == Qt.AlignLeft: + if amount is None or rect.x() < amount: + amount = rect.x() + index = i + elif alignment == Qt.AlignRight: + if amount is None or rect.x() + rect.width() > amount: + amount = rect.x() + rect.width() + index = i + elif alignment == Qt.AlignHCenter: + if amount is None or rect.width() > amount: + amount = rect.width() + index = i + elif alignment == Qt.AlignTop: + if amount is None or rect.y() < amount: + amount = rect.y() + index = i + elif alignment == Qt.AlignBottom: + if amount is None or rect.y() + rect.height() > amount: + amount = rect.y() + rect.height() + index = i + elif alignment == Qt.AlignVCenter: + if amount is None or rect.height() > amount: + amount = rect.height() + index = i + rect = items[index].sceneBoundingRect() + + # step 3: move the other items + for i, item in enumerate(items): + if i == index: + continue + itemrect = item.sceneBoundingRect() + xOffset = yOffset = 0 + if alignment == Qt.AlignLeft: + xOffset = rect.x() - itemrect.x() + elif alignment == Qt.AlignRight: + xOffset = (rect.x() + rect.width()) - \ + (itemrect.x() + itemrect.width()) + elif alignment == Qt.AlignHCenter: + xOffset = (rect.x() + rect.width() / 2) - \ + (itemrect.x() + itemrect.width() / 2) + elif alignment == Qt.AlignTop: + yOffset = rect.y() - itemrect.y() + elif alignment == Qt.AlignBottom: + yOffset = (rect.y() + rect.height()) - \ + (itemrect.y() + itemrect.height()) + elif alignment == Qt.AlignVCenter: + yOffset = (rect.y() + rect.height() / 2) - \ + (itemrect.y() + itemrect.height() / 2) + item.moveBy(xOffset, yOffset) + + self.scene().update()