--- a/Graphics/UMLClassDiagram.py Sun Sep 09 14:46:59 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2007 - 2012 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing a dialog showing a UML like class diagram. -""" - -import itertools - -from PyQt4.QtGui import QGraphicsTextItem - -import Utilities.ModuleParser -import Preferences - -from .UMLDialog import UMLDialog -from .ClassItem import ClassItem, ClassModel -from .AssociationItem import AssociationItem, Generalisation -from . import GraphicsUtilities - - -class UMLClassDiagram(UMLDialog): - """ - Class implementing a dialog showing a UML like class diagram. - """ - def __init__(self, project, file, parent=None, name=None, noAttrs=False): - """ - Constructor - - @param project reference to the project object - @param file filename of a python module to be shown (string) - @param parent parent widget of the view (QWidget) - @param name name of the view widget (string) - @keyparam noAttrs flag indicating, that no attributes should be shown (boolean) - """ - UMLDialog.__init__(self, "UMLClassDiagram", buildFunction=self.__buildClasses, - parent=parent) - - self.file = file - self.noAttrs = noAttrs - - self.umlView.setPersistenceData("file={0}".format(file)) - - pname = project.getProjectName() - if pname and project.isProjectSource(self.file): - name = self.trUtf8("Class Diagram {0}: {1}").format( - pname, project.getRelativePath(self.file)) - else: - name = self.trUtf8("Class Diagram: {0}").format(self.file) - self.setDiagramName(name) - - if not name: - self.setObjectName("UMLClassDiagram") - else: - self.setObjectName(name) - - self.allClasses = {} - self.allModules = {} - - self.umlView.relayout.connect(self.relayout) - - def __getCurrentShape(self, name): - """ - Private method to get the named shape. - - @param name name of the shape (string) - @return shape (QGraphicsItem) - """ - return self.allClasses.get(name) - - def __buildClasses(self): - """ - Private method to build the class shapes of the class diagram. - - The algorithm is borrowed from Boa Constructor. - """ - try: - extensions = Preferences.getPython("PythonExtensions") + \ - Preferences.getPython("Python3Extensions") + ['.rb'] - module = Utilities.ModuleParser.readModule(self.file, extensions=extensions) - except ImportError: - ct = QGraphicsTextItem(None, self.scene) - ct.setHtml( - self.trUtf8("The module <b>'{0}'</b> could not be found.") - .format(self.file)) - return - - if self.file not in self.allModules: - self.allModules[self.file] = [] - - routes = [] - nodes = [] - todo = [module.createHierarchy()] - classesFound = False - while todo: - hierarchy = todo[0] - for className in hierarchy: - classesFound = True - cw = self.__getCurrentShape(className) - if not cw and className.find('.') >= 0: - cw = self.__getCurrentShape(className.split('.')[-1]) - if cw: - self.allClasses[className] = cw - if className not in self.allModules[self.file]: - self.allModules[self.file].append(className) - if cw and cw.noAttrs != self.noAttrs: - cw = None - if cw and not (cw.external and \ - (className in module.classes or - className in module.modules) - ): - if cw.scene() != self.scene: - self.scene.addItem(cw) - cw.setPos(10, 10) - if className not in nodes: - nodes.append(className) - else: - if className in module.classes: - # this is a local class (defined in this module) - self.__addLocalClass( - className, module.classes[className], 0, 0) - elif className in module.modules: - # this is a local module (defined in this module) - self.__addLocalClass( - className, module.modules[className], 0, 0, True) - else: - self.__addExternalClass(className, 0, 0) - nodes.append(className) - - if hierarchy.get(className): - todo.append(hierarchy.get(className)) - children = list(hierarchy.get(className).keys()) - for child in children: - if (className, child) not in routes: - routes.append((className, child)) - - del todo[0] - - if classesFound: - self.__arrangeClasses(nodes, routes[:]) - self.__createAssociations(routes) - self.umlView.autoAdjustSceneSize(limit=True) - else: - ct = QGraphicsTextItem(None, self.scene) - ct.setHtml( - self.trUtf8("The module <b>'{0}'</b> does not contain any classes.")\ - .format(self.file)) - - def __arrangeClasses(self, nodes, routes, whiteSpaceFactor=1.2): - """ - Private method to arrange the shapes on the canvas. - - The algorithm is borrowed from Boa Constructor. - """ - 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.__getCurrentShape(child).sceneBoundingRect()) - - # calculate total width and total height - width = 0 - height = 0 - widths = [] - heights = [] - for generation in sizes: - currentWidth = 0 - currentHeight = 0 - - for rect in generation: - if rect.bottom() > currentHeight: - currentHeight = rect.bottom() - currentWidth = currentWidth + rect.right() - - # update totals - if currentWidth > width: - width = currentWidth - height = height + currentHeight - - # store generation info - widths.append(currentWidth) - heights.append(currentHeight) - - # add in some whitespace - width = width * whiteSpaceFactor -## rawHeight = height - height = height * whiteSpaceFactor - 20 -## verticalWhiteSpace = max( -## (height - rawHeight) / (len(generations) - 1.0 or 2.0), -## 40.0 -## ) - verticalWhiteSpace = 40.0 - - 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 - self.umlView.setSceneSize(swidth, sheight) - - # distribute each generation across the width and the - # generations across height - y = 10.0 - for currentWidth, currentHeight, generation in \ - itertools.zip_longest(widths, heights, generations): - x = 10.0 - # whiteSpace is the space between any two elements - whiteSpace = (width - currentWidth - 20) / (len(generation) - 1.0 or 2.0) - for className in generation: - cw = self.__getCurrentShape(className) - cw.setPos(x, y) - rect = cw.sceneBoundingRect() - x = x + rect.width() + whiteSpace - y = y + currentHeight + verticalWhiteSpace - - def __addLocalClass(self, className, _class, x, y, isRbModule=False): - """ - 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) - """ - 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[:]) - cw = ClassItem(cl, False, x, y, noAttrs=self.noAttrs, scene=self.scene) - cw.setId(self.umlView.getItemId()) - self.allClasses[className] = cw - if _class.name not in self.allModules[self.file]: - self.allModules[self.file].append(_class.name) - - def __addExternalClass(self, _class, x, y): - """ - Private method to add a class defined outside the module. - - 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) - """ - cl = ClassModel(_class) - cw = ClassItem(cl, True, x, y, noAttrs=self.noAttrs, scene=self.scene) - cw.setId(self.umlView.getItemId()) - self.allClasses[_class] = cw - if _class not in self.allModules[self.file]: - self.allModules[self.file].append(_class) - - def __createAssociations(self, routes): - """ - Private method to generate the associations between the class shapes. - - @param routes list of relationsships - """ - for route in routes: - if len(route) > 1: - assoc = AssociationItem( - self.__getCurrentShape(route[1]), - self.__getCurrentShape(route[0]), - Generalisation, - topToBottom=True) - self.scene.addItem(assoc) - - def relayout(self): - """ - Public method to relayout the diagram. - """ - self.allClasses.clear() - self.allModules.clear() - self.__buildClasses()