eric6/Graphics/PackageDiagramBuilder.py

branch
maintenance
changeset 8400
b3eefd7e58d1
parent 8273
698ae46f40a4
parent 8295
3f5e8b0a338e
--- a/eric6/Graphics/PackageDiagramBuilder.py	Sat May 01 14:27:38 2021 +0200
+++ b/eric6/Graphics/PackageDiagramBuilder.py	Thu Jun 03 11:39:23 2021 +0200
@@ -4,7 +4,7 @@
 #
 
 """
-Module implementing a dialog showing a UML like class diagram of a package.
+Module implementing a dialog showing an UML like class diagram of a package.
 """
 
 import glob
@@ -29,18 +29,28 @@
         """
         Constructor
         
-        @param dialog reference to the UML dialog (UMLDialog)
-        @param view reference to the view object (UMLGraphicsView)
-        @param project reference to the project object (Project)
-        @param package name of a python package to be shown (string)
+        @param dialog reference to the UML dialog
+        @type UMLDialog
+        @param view reference to the view object
+        @type UMLGraphicsView
+        @param project reference to the project object
+        @type Project
+        @param package name of a python package to be shown
+        @type str
         @param noAttrs flag indicating, that no attributes should be shown
-            (boolean)
+        @type bool
         """
         super().__init__(dialog, view, project)
         self.setObjectName("PackageDiagram")
         
         self.package = os.path.abspath(package)
         self.noAttrs = noAttrs
+        
+        self.__relPackage = (
+            self.project.getRelativePath(self.package)
+            if self.project.isProjectSource(self.package) else
+            ""
+        )
     
     def initialize(self):
         """
@@ -59,8 +69,10 @@
         """
         Private method to get the named shape.
         
-        @param name name of the shape (string)
-        @return shape (QCanvasItem)
+        @param name name of the shape
+        @type str
+        @return shape
+        @rtype QCanvasItem
         """
         return self.allClasses.get(name)
     
@@ -69,7 +81,8 @@
         Private method to build a dictionary of modules contained in the
         package.
         
-        @return dictionary of modules contained in the package.
+        @return dictionary of modules contained in the package
+        @rtype dict
         """
         import Utilities.ModuleParser
         
@@ -121,6 +134,7 @@
         package.
         
         @return dictionary of sub-packages contained in this package
+        @rtype dict
         """
         import Utilities.ModuleParser
         
@@ -208,13 +222,15 @@
             return
         
         modules = self.__buildModulesDict()
-        if not modules:
+        subpackages = self.__buildSubpackagesDict()
+        
+        if not modules and not subpackages:
             ct = QGraphicsTextItem(None)
             self.scene.addItem(ct)
-            ct.setHtml(
-                self.tr(
-                    "The package <b>'{0}'</b> does not contain any modules.")
-                .format(self.package))
+            ct.setHtml(self.buildErrorMessage(
+                self.tr("The package <b>'{0}'</b> does not contain any modules"
+                        " or subpackages.").format(self.package)
+            ))
             return
             
         # step 1: build all classes found in the modules
@@ -225,13 +241,13 @@
             for cls in list(module.classes.keys()):
                 classesFound = True
                 self.__addLocalClass(cls, module.classes[cls], 0, 0)
-        if not classesFound:
+        if not classesFound and not subpackages:
             ct = QGraphicsTextItem(None)
             self.scene.addItem(ct)
-            ct.setHtml(
-                self.tr(
-                    "The package <b>'{0}'</b> does not contain any classes.")
-                .format(self.package))
+            ct.setHtml(self.buildErrorMessage(
+                self.tr("The package <b>'{0}'</b> does not contain any"
+                        " classes or subpackages.").format(self.package)
+            ))
             return
         
         # step 2: build the class hierarchies
@@ -243,7 +259,7 @@
             todo = [module.createHierarchy()]
             while todo:
                 hierarchy = todo[0]
-                for className in list(hierarchy.keys()):
+                for className in hierarchy:
                     cw = self.__getCurrentShape(className)
                     if not cw and className.find('.') >= 0:
                         cw = self.__getCurrentShape(className.split('.')[-1])
@@ -282,7 +298,6 @@
                 del todo[0]
         
         # step 3: build the subpackages
-        subpackages = self.__buildSubpackagesDict()
         for subpackage in sorted(subpackages.keys()):
             self.__addPackage(subpackage, subpackages[subpackage], 0, 0)
             nodes.append(subpackage)
@@ -298,9 +313,12 @@
         The algorithm is borrowed from Boa Constructor.
         
         @param nodes list of nodes to arrange
+        @type list of str
         @param routes list of routes
+        @type list of tuple of (str, str)
         @param whiteSpaceFactor factor to increase whitespace between
-            items (float)
+            items
+        @type float
         """
         from . import GraphicsUtilities
         generations = GraphicsUtilities.sort(nodes, routes)
@@ -335,7 +353,7 @@
             # store generation info
             widths.append(currentWidth)
             heights.append(currentHeight)
-            
+        
         # add in some whitespace
         width *= whiteSpaceFactor
         height = height * whiteSpaceFactor - 20
@@ -344,8 +362,8 @@
         sceneRect = self.umlView.sceneRect()
         width += 50.0
         height += 50.0
-        swidth = width < sceneRect.width() and sceneRect.width() or width
-        sheight = height < sceneRect.height() and sceneRect.height() or height
+        swidth = sceneRect.width() if width < sceneRect.width() else width
+        sheight = sceneRect.height() if height < sceneRect.height() else height
         self.umlView.setSceneSize(swidth, sheight)
         
         # distribute each generation across the width and the
@@ -371,19 +389,27 @@
         """
         Private method to add a class defined in the module.
         
-        @param className name of the class to be as a dictionary key (string)
-        @param _class class to be shown (ModuleParser.Class)
-        @param x x-coordinate (float)
-        @param y y-coordinate (float)
-        @param isRbModule flag indicating a Ruby module (boolean)
+        @param className name of the class to be as a dictionary key
+        @type str
+        @param _class class to be shown
+        @type ModuleParser.Class
+        @param x x-coordinate
+        @type float
+        @param y y-coordinate
+        @type float
+        @param isRbModule flag indicating a Ruby module
+        @type bool
         """
         from .ClassItem import ClassItem, ClassModel
-        meths = sorted(_class.methods.keys())
-        attrs = sorted(_class.attributes.keys())
         name = _class.name
         if isRbModule:
             name = "{0} (Module)".format(name)
-        cl = ClassModel(name, meths[:], attrs[:])
+        cl = ClassModel(
+            name,
+            sorted(_class.methods.keys())[:],
+            sorted(_class.attributes.keys())[:],
+            sorted(_class.globals.keys())[:]
+        )
         cw = ClassItem(cl, False, x, y, noAttrs=self.noAttrs, scene=self.scene,
                        colors=self.umlView.getDrawingColors())
         cw.setId(self.umlView.getItemId())
@@ -396,9 +422,12 @@
         If the canvas is too small to take the shape, it
         is enlarged.
         
-        @param _class class to be shown (string)
-        @param x x-coordinate (float)
-        @param y y-coordinate (float)
+        @param _class class to be shown
+        @type ModuleParser.Class
+        @param x x-coordinate
+        @type float
+        @param y y-coordinate
+        @type float
         """
         from .ClassItem import ClassItem, ClassModel
         cl = ClassModel(_class)
@@ -411,11 +440,14 @@
         """
         Private method to add a package to the diagram.
         
-        @param name package name to be shown (string)
+        @param name package name to be shown
+        @type str
         @param modules list of module names contained in the package
-            (list of strings)
-        @param x x-coordinate (float)
-        @param y y-coordinate (float)
+        @type list of str
+        @param x x-coordinate
+        @type float
+        @param y y-coordinate
+        @type float
         """
         from .PackageItem import PackageItem, PackageModel
         pm = PackageModel(name, modules)
@@ -429,6 +461,7 @@
         Private method to generate the associations between the class shapes.
         
         @param routes list of relationsships
+        @type list of tuple of (str, str)
         """
         from .AssociationItem import AssociationItem, AssociationType
         for route in routes:
@@ -445,7 +478,8 @@
         """
         Public method to get a string for data to be persisted.
         
-        @return persisted data string (string)
+        @return persisted data string
+        @rtype str
         """
         return "package={0}, no_attributes={1}".format(
             self.package, self.noAttrs)
@@ -454,9 +488,12 @@
         """
         Public method to parse persisted data.
         
-        @param version version of the data (string)
-        @param data persisted data to be parsed (string)
-        @return flag indicating success (boolean)
+        @param version version of the data
+        @type str
+        @param data persisted data to be parsed
+        @type str
+        @return flag indicating success
+        @rtype bool
         """
         parts = data.split(", ")
         if (
@@ -472,3 +509,60 @@
         self.initialize()
         
         return True
+    
+    def toDict(self):
+        """
+        Public method to collect data to be persisted.
+        
+        @return dictionary containing data to be persisted
+        @rtype dict
+        """
+        data = {
+            "project_name": self.project.getProjectName(),
+            "no_attributes": self.noAttrs,
+        }
+        
+        data["package"] = (
+            Utilities.fromNativeSeparators(self.__relPackage)
+            if self.__relPackage else
+            Utilities.fromNativeSeparators(self.package)
+        )
+        
+        return data
+    
+    def fromDict(self, version, data):
+        """
+        Public method to populate the class with data persisted by 'toDict()'.
+        
+        @param version version of the data
+        @type str
+        @param data dictionary containing the persisted data
+        @type dict
+        @return tuple containing a flag indicating success and an info
+            message in case the diagram belongs to a different project
+        @rtype tuple of (bool, str)
+        """
+        try:
+            self.noAttrs = data["no_attributes"]
+            
+            package = Utilities.toNativeSeparators(data["package"])
+            if os.path.isabs(package):
+                self.package = package
+                self.__relPackage = ""
+            else:
+                # relative package paths indicate a project package
+                if data["project_name"] != self.project.getProjectName():
+                    msg = self.tr(
+                        "<p>The diagram belongs to project <b>{0}</b>."
+                        " Please open it and try again.</p>"
+                    ).format(data["project_name"])
+                    return False, msg
+                
+                self.__relPackage = package
+                self.package = self.project.getAbsolutePath(package)
+        except KeyError:
+            return False, ""
+        
+        self.initialize()
+        
+        return True, ""

eric ide

mercurial