eric6/Graphics/UMLGraphicsView.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 6989
8b8cadf8d7e9
child 7198
684261ef2165
diff -r f99d60d6b59b -r 2602857055c5 eric6/Graphics/UMLGraphicsView.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Graphics/UMLGraphicsView.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,762 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2007 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a subclass of E5GraphicsView for our diagrams.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSignal, Qt, QSignalMapper, QFileInfo, QEvent, \
+    QRectF
+from PyQt5.QtWidgets import QGraphicsView, QAction, QToolBar, QDialog
+from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
+
+from E5Graphics.E5GraphicsView import E5GraphicsView
+
+from E5Gui import E5MessageBox, E5FileDialog
+from E5Gui.E5ZoomWidget import E5ZoomWidget
+
+from .UMLItem import UMLItem
+
+import UI.Config
+import UI.PixmapCache
+
+import Preferences
+from Globals import qVersionTuple
+
+
+class UMLGraphicsView(E5GraphicsView):
+    """
+    Class implementing a specialized E5GraphicsView for our diagrams.
+    
+    @signal relayout() emitted to indicate a relayout of the diagram
+        is requested
+    """
+    relayout = pyqtSignal()
+    
+    def __init__(self, scene, parent=None):
+        """
+        Constructor
+        
+        @param scene reference to the scene object (QGraphicsScene)
+        @param parent parent widget of the view (QWidget)
+        """
+        E5GraphicsView.__init__(self, scene, parent)
+        self.setObjectName("UMLGraphicsView")
+        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
+        
+        self.diagramName = "Unnamed"
+        self.__itemId = -1
+        
+        self.border = 10
+        self.deltaSize = 100.0
+        
+        self.__zoomWidget = E5ZoomWidget(
+            UI.PixmapCache.getPixmap("zoomOut.png"),
+            UI.PixmapCache.getPixmap("zoomIn.png"),
+            UI.PixmapCache.getPixmap("zoomReset.png"), self)
+        parent.statusBar().addPermanentWidget(self.__zoomWidget)
+        self.__zoomWidget.setMapping(
+            E5GraphicsView.ZoomLevels, E5GraphicsView.ZoomLevelDefault)
+        self.__zoomWidget.valueChanged.connect(self.setZoom)
+        self.zoomValueChanged.connect(self.__zoomWidget.setValue)
+        
+        self.__initActions()
+        
+        scene.changed.connect(self.__sceneChanged)
+        
+        self.grabGesture(Qt.PinchGesture)
+        
+    def __initActions(self):
+        """
+        Private method to initialize the view actions.
+        """
+        self.alignMapper = QSignalMapper(self)
+        self.alignMapper.mapped[int].connect(self.__alignShapes)
+        
+        self.deleteShapeAct = \
+            QAction(UI.PixmapCache.getIcon("deleteShape.png"),
+                    self.tr("Delete shapes"), self)
+        self.deleteShapeAct.triggered.connect(self.__deleteShape)
+        
+        self.incWidthAct = \
+            QAction(UI.PixmapCache.getIcon("sceneWidthInc.png"),
+                    self.tr("Increase width by {0} points").format(
+                        self.deltaSize),
+                    self)
+        self.incWidthAct.triggered.connect(self.__incWidth)
+        
+        self.incHeightAct = \
+            QAction(UI.PixmapCache.getIcon("sceneHeightInc.png"),
+                    self.tr("Increase height by {0} points").format(
+                        self.deltaSize),
+                    self)
+        self.incHeightAct.triggered.connect(self.__incHeight)
+        
+        self.decWidthAct = \
+            QAction(UI.PixmapCache.getIcon("sceneWidthDec.png"),
+                    self.tr("Decrease width by {0} points").format(
+                        self.deltaSize),
+                    self)
+        self.decWidthAct.triggered.connect(self.__decWidth)
+        
+        self.decHeightAct = \
+            QAction(UI.PixmapCache.getIcon("sceneHeightDec.png"),
+                    self.tr("Decrease height by {0} points").format(
+                        self.deltaSize),
+                    self)
+        self.decHeightAct.triggered.connect(self.__decHeight)
+        
+        self.setSizeAct = \
+            QAction(UI.PixmapCache.getIcon("sceneSize.png"),
+                    self.tr("Set size"), self)
+        self.setSizeAct.triggered.connect(self.__setSize)
+        
+        self.rescanAct = \
+            QAction(UI.PixmapCache.getIcon("rescan.png"),
+                    self.tr("Re-Scan"), self)
+        self.rescanAct.triggered.connect(self.__rescan)
+        
+        self.relayoutAct = \
+            QAction(UI.PixmapCache.getIcon("relayout.png"),
+                    self.tr("Re-Layout"), self)
+        self.relayoutAct.triggered.connect(self.__relayout)
+        
+        self.alignLeftAct = \
+            QAction(UI.PixmapCache.getIcon("shapesAlignLeft.png"),
+                    self.tr("Align Left"), self)
+        self.alignMapper.setMapping(self.alignLeftAct, Qt.AlignLeft)
+        self.alignLeftAct.triggered.connect(self.alignMapper.map)
+        
+        self.alignHCenterAct = \
+            QAction(UI.PixmapCache.getIcon("shapesAlignHCenter.png"),
+                    self.tr("Align Center Horizontal"), self)
+        self.alignMapper.setMapping(self.alignHCenterAct, Qt.AlignHCenter)
+        self.alignHCenterAct.triggered.connect(self.alignMapper.map)
+        
+        self.alignRightAct = \
+            QAction(UI.PixmapCache.getIcon("shapesAlignRight.png"),
+                    self.tr("Align Right"), self)
+        self.alignMapper.setMapping(self.alignRightAct, Qt.AlignRight)
+        self.alignRightAct.triggered.connect(self.alignMapper.map)
+        
+        self.alignTopAct = \
+            QAction(UI.PixmapCache.getIcon("shapesAlignTop.png"),
+                    self.tr("Align Top"), self)
+        self.alignMapper.setMapping(self.alignTopAct, Qt.AlignTop)
+        self.alignTopAct.triggered.connect(self.alignMapper.map)
+        
+        self.alignVCenterAct = \
+            QAction(UI.PixmapCache.getIcon("shapesAlignVCenter.png"),
+                    self.tr("Align Center Vertical"), self)
+        self.alignMapper.setMapping(self.alignVCenterAct, Qt.AlignVCenter)
+        self.alignVCenterAct.triggered.connect(self.alignMapper.map)
+        
+        self.alignBottomAct = \
+            QAction(UI.PixmapCache.getIcon("shapesAlignBottom.png"),
+                    self.tr("Align Bottom"), self)
+        self.alignMapper.setMapping(self.alignBottomAct, Qt.AlignBottom)
+        self.alignBottomAct.triggered.connect(self.alignMapper.map)
+        
+    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)
+        
+        sceneRect = self.scene().sceneRect()
+        newWidth = width = sceneRect.width()
+        newHeight = height = sceneRect.height()
+        rect = self.scene().itemsBoundingRect()
+        # calculate with 10 pixel border on each side
+        if sceneRect.right() - 10 < rect.right():
+            newWidth = rect.right() + 10
+        if sceneRect.bottom() - 10 < rect.bottom():
+            newHeight = rect.bottom() + 10
+        
+        if newHeight != height or newWidth != width:
+            self.setSceneSize(newWidth, newHeight)
+            self.__checkSizeActions()
+        
+    def initToolBar(self):
+        """
+        Public method to populate a toolbar with our actions.
+        
+        @return the populated toolBar (QToolBar)
+        """
+        toolBar = QToolBar(self.tr("Graphics"), self)
+        toolBar.setIconSize(UI.Config.ToolBarIconSize)
+        toolBar.addAction(self.deleteShapeAct)
+        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.rescanAct)
+        toolBar.addAction(self.relayoutAct)
+        
+        return toolBar
+        
+    def filteredItems(self, items, itemType=UMLItem):
+        """
+        Public method to filter a list of items.
+        
+        @param items list of items as returned by the scene object
+            (QGraphicsItem)
+        @param itemType type to be filtered (class)
+        @return list of interesting collision items (QGraphicsItem)
+        """
+        return [itm for itm in items if isinstance(itm, itemType)]
+        
+    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, UMLItem):
+                itm.setSelected(True)
+        
+    def selectItem(self, item):
+        """
+        Public method to select an item.
+        
+        @param item item to be selected (QGraphicsItemItem)
+        """
+        if isinstance(item, UMLItem):
+            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.
+        """
+        from .UMLSceneSizeDialog import UMLSceneSizeDialog
+        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 autoAdjustSceneSize(self, limit=False):
+        """
+        Public method to adjust the scene size to the diagram size.
+        
+        @param limit flag indicating to limit the scene to the
+            initial size (boolean)
+        """
+        super(UMLGraphicsView, self).autoAdjustSceneSize(limit=limit)
+        self.__checkSizeActions()
+        
+    def saveImage(self):
+        """
+        Public method to handle the save context menu entry.
+        """
+        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
+            self,
+            self.tr("Save Diagram"),
+            "",
+            self.tr("Portable Network Graphics (*.png);;"
+                    "Scalable Vector Graphics (*.svg)"),
+            "",
+            E5FileDialog.Options(E5FileDialog.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 = E5MessageBox.yesNo(
+                    self,
+                    self.tr("Save Diagram"),
+                    self.tr("<p>The file <b>{0}</b> already exists."
+                            " Overwrite it?</p>").format(fname),
+                    icon=E5MessageBox.Warning)
+                if not res:
+                    return
+            
+            success = super(UMLGraphicsView, self).saveImage(
+                fname, QFileInfo(fname).suffix().upper())
+            if not success:
+                E5MessageBox.critical(
+                    self,
+                    self.tr("Save Diagram"),
+                    self.tr(
+                        """<p>The file <b>{0}</b> could not be saved.</p>""")
+                    .format(fname))
+        
+    def __relayout(self):
+        """
+        Private slot to handle the re-layout context menu entry.
+        """
+        self.__itemId = -1
+        self.scene().clear()
+        self.relayout.emit()
+        
+    def __rescan(self):
+        """
+        Private slot to handle the re-scan context menu entry.
+        """
+        # 1. save positions of all items and names of selected items
+        itemPositions = {}
+        selectedItems = []
+        for item in self.filteredItems(self.scene().items(), UMLItem):
+            name = item.getName()
+            if name:
+                itemPositions[name] = (item.x(), item.y())
+                if item.isSelected():
+                    selectedItems.append(name)
+        
+        # 2. save
+        
+        # 2. re-layout the diagram
+        self.__relayout()
+        
+        # 3. move known items to the saved positions
+        for item in self.filteredItems(self.scene().items(), UMLItem):
+            name = item.getName()
+            if name in itemPositions:
+                item.setPos(*itemPositions[name])
+            if name in selectedItems:
+                item.setSelected(True)
+        
+    def printDiagram(self):
+        """
+        Public 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.setPageMargins(
+            Preferences.getPrinter("LeftMargin") * 10,
+            Preferences.getPrinter("TopMargin") * 10,
+            Preferences.getPrinter("RightMargin") * 10,
+            Preferences.getPrinter("BottomMargin") * 10,
+            QPrinter.Millimeter
+        )
+        printerName = Preferences.getPrinter("PrinterName")
+        if printerName:
+            printer.setPrinterName(printerName)
+        
+        printDialog = QPrintDialog(printer, self)
+        if printDialog.exec_():
+            super(UMLGraphicsView, self).printDiagram(
+                printer, self.diagramName)
+        
+    def printPreviewDiagram(self):
+        """
+        Public slot called to show a print preview of the diagram.
+        """
+        from PyQt5.QtPrintSupport 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.setPageMargins(
+            Preferences.getPrinter("LeftMargin") * 10,
+            Preferences.getPrinter("TopMargin") * 10,
+            Preferences.getPrinter("RightMargin") * 10,
+            Preferences.getPrinter("BottomMargin") * 10,
+            QPrinter.Millimeter
+        )
+        printerName = Preferences.getPrinter("PrinterName")
+        if printerName:
+            printer.setPrinterName(printerName)
+        
+        preview = QPrintPreviewDialog(printer, self)
+        preview.paintRequested[QPrinter].connect(self.__printPreviewPrint)
+        preview.exec_()
+        
+    def __printPreviewPrint(self, printer):
+        """
+        Private slot to generate a print preview.
+        
+        @param printer reference to the printer object (QPrinter)
+        """
+        super(UMLGraphicsView, self).printDiagram(printer, self.diagramName)
+        
+    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()
+    
+    def __itemsBoundingRect(self, items):
+        """
+        Private method to calculate the bounding rectangle of the given items.
+        
+        @param items list of items to operate on (list of UMLItem)
+        @return bounding rectangle (QRectF)
+        """
+        rect = self.scene().sceneRect()
+        right = rect.left()
+        bottom = rect.top()
+        left = rect.right()
+        top = rect.bottom()
+        for item in items:
+            rect = item.sceneBoundingRect()
+            left = min(rect.left(), left)
+            right = max(rect.right(), right)
+            top = min(rect.top(), top)
+            bottom = max(rect.bottom(), bottom)
+        return QRectF(left, top, right - left, bottom - top)
+    
+    def keyPressEvent(self, evt):
+        """
+        Protected method handling key press events.
+        
+        @param evt reference to the key event (QKeyEvent)
+        """
+        key = evt.key()
+        if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]:
+            items = self.filteredItems(self.scene().selectedItems())
+            if items:
+                if evt.modifiers() & Qt.ControlModifier:
+                    stepSize = 50
+                else:
+                    stepSize = 5
+                if key == Qt.Key_Up:
+                    dx = 0
+                    dy = -stepSize
+                elif key == Qt.Key_Down:
+                    dx = 0
+                    dy = stepSize
+                elif key == Qt.Key_Left:
+                    dx = -stepSize
+                    dy = 0
+                else:
+                    dx = stepSize
+                    dy = 0
+                for item in items:
+                    item.moveBy(dx, dy)
+                evt.accept()
+                return
+        
+        super(UMLGraphicsView, self).keyPressEvent(evt)
+    
+    def wheelEvent(self, evt):
+        """
+        Protected method to handle wheel events.
+        
+        @param evt reference to the wheel event (QWheelEvent)
+        """
+        if evt.modifiers() & Qt.ControlModifier:
+            if qVersionTuple() >= (5, 0, 0):
+                delta = evt.angleDelta().y()
+            else:
+                delta = evt.delta()
+            if delta < 0:
+                self.zoomOut()
+            elif delta > 0:
+                self.zoomIn()
+            evt.accept()
+            return
+        
+        super(UMLGraphicsView, self).wheelEvent(evt)
+    
+    def event(self, evt):
+        """
+        Public method handling events.
+        
+        @param evt reference to the event (QEvent)
+        @return flag indicating, if the event was handled (boolean)
+        """
+        if evt.type() == QEvent.Gesture:
+            self.gestureEvent(evt)
+            return True
+        
+        return super(UMLGraphicsView, self).event(evt)
+    
+    def gestureEvent(self, evt):
+        """
+        Protected method handling gesture events.
+        
+        @param evt reference to the gesture event (QGestureEvent
+        """
+        pinch = evt.gesture(Qt.PinchGesture)
+        if pinch:
+            if pinch.state() == Qt.GestureStarted:
+                pinch.setTotalScaleFactor(self.zoom() / 100.0)
+            elif pinch.state() == Qt.GestureUpdated:
+                self.setZoom(int(pinch.totalScaleFactor() * 100))
+            evt.accept()
+    
+    def getItemId(self):
+        """
+        Public method to get the ID to be assigned to an item.
+        
+        @return item ID (integer)
+        """
+        self.__itemId += 1
+        return self.__itemId
+
+    def findItem(self, itemId):
+        """
+        Public method to find an UML item based on the ID.
+        
+        @param itemId of the item to search for (integer)
+        @return item found (UMLItem) or None
+        """
+        for item in self.scene().items():
+            try:
+                if item.getId() == itemId:
+                    return item
+            except AttributeError:
+                continue
+        
+        return None
+    
+    def findItemByName(self, name):
+        """
+        Public method to find an UML item based on its name.
+        
+        @param name name to look for (string)
+        @return item found (UMLItem) or None
+        """
+        for item in self.scene().items():
+            try:
+                if item.getName() == name:
+                    return item
+            except AttributeError:
+                continue
+        
+        return None
+    
+    def getPersistenceData(self):
+        """
+        Public method to get a list of data to be persisted.
+        
+        @return list of data to be persisted (list of strings)
+        """
+        lines = [
+            "diagram_name: {0}".format(self.diagramName),
+        ]
+        
+        for item in self.filteredItems(self.scene().items(), UMLItem):
+            lines.append("item: id={0}, x={1}, y={2}, item_type={3}{4}".format(
+                item.getId(), item.x(), item.y(), item.getItemType(),
+                item.buildItemDataString()))
+        
+        from .AssociationItem import AssociationItem
+        for item in self.filteredItems(self.scene().items(), AssociationItem):
+            lines.append("association: {0}".format(
+                item.buildAssociationItemDataString()))
+        
+        return lines
+    
+    def parsePersistenceData(self, version, data):
+        """
+        Public method to parse persisted data.
+        
+        @param version version of the data (string)
+        @param data persisted data to be parsed (list of string)
+        @return tuple of flag indicating success (boolean) and faulty line
+            number (integer)
+        """
+        umlItems = {}
+        
+        if not data[0].startswith("diagram_name:"):
+            return False, 0
+        self.diagramName = data[0].split(": ", 1)[1].strip()
+        
+        from .ClassItem import ClassItem
+        from .ModuleItem import ModuleItem
+        from .PackageItem import PackageItem
+        from .AssociationItem import AssociationItem
+        
+        linenum = 0
+        for line in data[1:]:
+            linenum += 1
+            if not line.startswith(("item:", "association:")):
+                return False, linenum
+            
+            key, value = line.split(": ", 1)
+            if key == "item":
+                itemId, x, y, itemType, itemData = value.split(", ", 4)
+                try:
+                    itemId = int(itemId.split("=", 1)[1].strip())
+                    x = float(x.split("=", 1)[1].strip())
+                    y = float(y.split("=", 1)[1].strip())
+                    itemType = itemType.split("=", 1)[1].strip()
+                    if itemType == ClassItem.ItemType:
+                        itm = ClassItem(x=x, y=y, scene=self.scene())
+                    elif itemType == ModuleItem.ItemType:
+                        itm = ModuleItem(x=x, y=y, scene=self.scene())
+                    elif itemType == PackageItem.ItemType:
+                        itm = PackageItem(x=x, y=y, scene=self.scene())
+                    itm.setId(itemId)
+                    umlItems[itemId] = itm
+                    if not itm.parseItemDataString(version, itemData):
+                        return False, linenum
+                except ValueError:
+                    return False, linenum
+            elif key == "association":
+                srcId, dstId, assocType, topToBottom = \
+                    AssociationItem.parseAssociationItemDataString(
+                        value.strip())
+                assoc = AssociationItem(umlItems[srcId], umlItems[dstId],
+                                        assocType, topToBottom)
+                self.scene().addItem(assoc)
+        
+        return True, -1

eric ide

mercurial