--- a/src/eric7/Graphics/ImportsDiagramBuilder.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Graphics/ImportsDiagramBuilder.py Wed Jul 13 14:55:47 2022 +0200 @@ -24,15 +24,15 @@ class ImportsDiagramBuilder(UMLDiagramBuilder): """ Class implementing a builder for imports diagrams of a package. - + Note: Only package internal imports are shown in order to maintain some readability. """ - def __init__(self, dialog, view, project, package, - showExternalImports=False): + + def __init__(self, dialog, view, project, package, showExternalImports=False): """ Constructor - + @param dialog reference to the UML dialog @type UMLDialog @param view reference to the view object @@ -48,68 +48,70 @@ """ super().__init__(dialog, view, project) self.setObjectName("ImportsDiagram") - + self.showExternalImports = showExternalImports self.packagePath = os.path.abspath(package) - + self.__relPackagePath = ( self.project.getRelativePath(self.packagePath) - if self.project.isProjectSource(self.packagePath) else - "" + if self.project.isProjectSource(self.packagePath) + else "" ) - + def initialize(self): """ Public method to initialize the object. """ - self.package = os.path.splitdrive(self.packagePath)[1].replace( - os.sep, '.')[1:] + self.package = os.path.splitdrive(self.packagePath)[1].replace(os.sep, ".")[1:] hasInit = True ppath = self.packagePath while hasInit: ppath = os.path.dirname(ppath) - hasInit = len(glob.glob(os.path.join(ppath, '__init__.*'))) > 0 - self.shortPackage = self.packagePath.replace(ppath, '').replace( - os.sep, '.')[1:] - + hasInit = len(glob.glob(os.path.join(ppath, "__init__.*"))) > 0 + self.shortPackage = self.packagePath.replace(ppath, "").replace(os.sep, ".")[1:] + pname = self.project.getProjectName() name = ( self.tr("Imports Diagramm {0}: {1}").format( - pname, self.project.getRelativePath(self.packagePath)) - if pname else - self.tr("Imports Diagramm: {0}").format(self.packagePath) + pname, self.project.getRelativePath(self.packagePath) + ) + if pname + else self.tr("Imports Diagramm: {0}").format(self.packagePath) ) self.umlView.setDiagramName(name) - + def __buildModulesDict(self): """ Private method to build a dictionary of modules contained in the package. - + @return dictionary of modules contained in the package @rtype dict """ import Utilities.ModuleParser - extensions = ( - Preferences.getPython("Python3Extensions") - ) + + extensions = Preferences.getPython("Python3Extensions") moduleDict = {} modules = [] - for ext in ( - Preferences.getPython("Python3Extensions") - ): - modules.extend(glob.glob(Utilities.normjoinpath( - self.packagePath, '*{0}'.format(ext)))) - + for ext in Preferences.getPython("Python3Extensions"): + modules.extend( + glob.glob(Utilities.normjoinpath(self.packagePath, "*{0}".format(ext))) + ) + tot = len(modules) progress = EricProgressDialog( self.tr("Parsing modules..."), - None, 0, tot, self.tr("%v/%m Modules"), self.parent()) + None, + 0, + tot, + self.tr("%v/%m Modules"), + self.parent(), + ) progress.setWindowTitle(self.tr("Imports Diagramm")) try: progress.show() QApplication.processEvents() - + now = time.monotonic() for prog, module in enumerate(modules): progress.setValue(prog) @@ -118,46 +120,50 @@ now = time.monotonic() try: mod = Utilities.ModuleParser.readModule( - module, extensions=extensions, caching=False) + module, extensions=extensions, caching=False + ) except ImportError: continue else: name = mod.name if name.startswith(self.package): - name = name[len(self.package) + 1:] + name = name[len(self.package) + 1 :] moduleDict[name] = mod finally: progress.setValue(tot) progress.deleteLater() return moduleDict - + def buildDiagram(self): """ Public method to build the modules shapes of the diagram. """ - initlist = glob.glob(os.path.join(self.packagePath, '__init__.*')) + initlist = glob.glob(os.path.join(self.packagePath, "__init__.*")) if len(initlist) == 0: ct = QGraphicsTextItem(None) - ct.setHtml(self.buildErrorMessage( - self.tr("The directory <b>'{0}'</b> is not a Python" - " package.").format(self.package) - )) + ct.setHtml( + self.buildErrorMessage( + self.tr( + "The directory <b>'{0}'</b> is not a Python" " package." + ).format(self.package) + ) + ) self.scene.addItem(ct) return - + self.__shapes = {} - + modules = self.__buildModulesDict() externalMods = [] - packageList = self.shortPackage.split('.') + packageList = self.shortPackage.split(".") packageListLen = len(packageList) for module in sorted(modules.keys()): impLst = [] for importName in modules[module].imports: n = ( - importName[len(self.package) + 1:] - if importName.startswith(self.package) else - importName + importName[len(self.package) + 1 :] + if importName.startswith(self.package) + else importName ) if importName in modules: impLst.append(n) @@ -166,20 +172,21 @@ if n not in externalMods: externalMods.append(n) for importName in list(modules[module].from_imports.keys()): - if importName.startswith('.'): - dots = len(importName) - len(importName.lstrip('.')) + if importName.startswith("."): + dots = len(importName) - len(importName.lstrip(".")) if dots == 1: n = importName[1:] importName = n else: if self.showExternalImports: - n = '.'.join( - packageList[:packageListLen - dots + 1] + - [importName[dots:]]) + n = ".".join( + packageList[: packageListLen - dots + 1] + + [importName[dots:]] + ) else: n = importName elif importName.startswith(self.package): - n = importName[len(self.package) + 1:] + n = importName[len(self.package) + 1 :] else: n = importName if importName in modules: @@ -188,7 +195,7 @@ impLst.append(n) if n not in externalMods: externalMods.append(n) - + classNames = [] for class_ in list(modules[module].classes.keys()): className = modules[module].classes[class_].name @@ -196,11 +203,11 @@ classNames.append(className) shape = self.__addModule(module, classNames, 0.0, 0.0) self.__shapes[module] = (shape, impLst) - + for module in externalMods: shape = self.__addModule(module, [], 0.0, 0.0) self.__shapes[module] = (shape, []) - + # build a list of routes nodes = [] routes = [] @@ -210,15 +217,15 @@ route = (module, rel) if route not in routes: routes.append(route) - + self.__arrangeNodes(nodes, routes[:]) self.__createAssociations(routes) self.umlView.autoAdjustSceneSize(limit=True) - + def __addModule(self, name, classes, x, y): """ Private method to add a module to the diagram. - + @param name module name to be shown @type str @param classes list of class names contained in the module @@ -231,19 +238,21 @@ @rtype ModuleItem """ from .ModuleItem import ModuleItem, ModuleModel + classes.sort() impM = ModuleModel(name, classes) - impW = ModuleItem(impM, x, y, scene=self.scene, - colors=self.umlView.getDrawingColors()) + impW = ModuleItem( + impM, x, y, scene=self.scene, colors=self.umlView.getDrawingColors() + ) impW.setId(self.umlView.getItemId()) return impW - + def __arrangeNodes(self, nodes, routes, whiteSpaceFactor=1.2): """ Private method to arrange the shapes on the canvas. - + The algorithm is borrowed from Boa Constructor. - + @param nodes list of nodes to arrange @type list of str @param routes list of routes @@ -253,16 +262,16 @@ @type float """ from . import GraphicsUtilities + generations = GraphicsUtilities.sort(nodes, routes) - + # calculate width and height of all elements sizes = [] for generation in generations: sizes.append([]) for child in generation: - sizes[-1].append( - self.__shapes[child][0].sceneBoundingRect()) - + sizes[-1].append(self.__shapes[child][0].sceneBoundingRect()) + # calculate total width and total height width = 0 height = 0 @@ -271,72 +280,71 @@ for generation in sizes: currentWidth = 0 currentHeight = 0 - + for rect in generation: if rect.height() > currentHeight: currentHeight = rect.height() currentWidth += rect.width() - + # update totals if currentWidth > width: width = currentWidth height += currentHeight - + # store generation info widths.append(currentWidth) heights.append(currentHeight) - + # add in some whitespace width *= whiteSpaceFactor height = height * whiteSpaceFactor - 20 verticalWhiteSpace = 40.0 - + sceneRect = self.umlView.sceneRect() width += 50.0 height += 50.0 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 # generations across height y = 10.0 - for currentWidth, currentHeight, generation in ( - zip(reversed(widths), reversed(heights), reversed(generations)) + for currentWidth, currentHeight, generation in zip( + reversed(widths), reversed(heights), reversed(generations) ): x = 10.0 # whiteSpace is the space between any two elements - whiteSpace = ( - (width - currentWidth - 20) / - (len(generation) - 1.0 or 2.0) - ) + whiteSpace = (width - currentWidth - 20) / (len(generation) - 1.0 or 2.0) for name in generation: shape = self.__shapes[name][0] shape.setPos(x, y) rect = shape.sceneBoundingRect() x = x + rect.width() + whiteSpace y = y + currentHeight + verticalWhiteSpace - + def __createAssociations(self, routes): """ Private method to generate the associations between the module shapes. - + @param routes list of associations @type list of tuple of (str, str) """ from .AssociationItem import AssociationItem, AssociationType + for route in routes: assoc = AssociationItem( self.__shapes[route[0]][0], self.__shapes[route[1]][0], AssociationType.IMPORTS, - colors=self.umlView.getDrawingColors()) + colors=self.umlView.getDrawingColors(), + ) self.scene.addItem(assoc) - + def parsePersistenceData(self, version, data): """ Public method to parse persisted data. - + @param version version of the data @type str @param data persisted data to be parsed @@ -346,24 +354,23 @@ """ parts = data.split(", ") if ( - len(parts) != 2 or - not parts[0].startswith("package=") or - not parts[1].startswith("show_external=") + len(parts) != 2 + or not parts[0].startswith("package=") + or not parts[1].startswith("show_external=") ): return False - + self.packagePath = parts[0].split("=", 1)[1].strip() - self.showExternalImports = Utilities.toBool( - parts[1].split("=", 1)[1].strip()) - + self.showExternalImports = Utilities.toBool(parts[1].split("=", 1)[1].strip()) + self.initialize() - + return True - + def toDict(self): """ Public method to collect data to be persisted. - + @return dictionary containing data to be persisted @rtype dict """ @@ -371,19 +378,19 @@ "project_name": self.project.getProjectName(), "show_external": self.showExternalImports, } - + data["package"] = ( Utilities.fromNativeSeparators(self.__relPackagePath) - if self.__relPackagePath else - Utilities.fromNativeSeparators(self.packagePath) + if self.__relPackagePath + else Utilities.fromNativeSeparators(self.packagePath) ) - + 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 @@ -394,7 +401,7 @@ """ try: self.showExternalImports = data["show_external"] - + packagePath = Utilities.toNativeSeparators(data["package"]) if os.path.isabs(packagePath): self.packagePath = packagePath @@ -407,12 +414,12 @@ " Please open it and try again.</p>" ).format(data["project_name"]) return False, msg - + self.__relPackagePath = packagePath self.package = self.project.getAbsolutePath(packagePath) except KeyError: return False, "" - + self.initialize() - + return True, ""