Sat, 01 May 2021 18:48:35 +0200
UML Diagrams
- extended the class items to show class attributes
--- a/docs/changelog Sat May 01 16:01:43 2021 +0200 +++ b/docs/changelog Sat May 01 18:48:35 2021 +0200 @@ -2,6 +2,8 @@ ---------- Version 21.6: - bug fixes +- UML Diagrams + -- extended the class items to show class attributes Version 21.5: - bug fixes
--- a/eric6/E5Graphics/E5GraphicsView.py Sat May 01 16:01:43 2021 +0200 +++ b/eric6/E5Graphics/E5GraphicsView.py Sat May 01 18:48:35 2021 +0200 @@ -283,10 +283,14 @@ Private method to retrieve the diagram from the scene fitting it in the minimum rectangle. - @param rect minimum rectangle fitting the diagram (QRectF) - @param imageFormat format for the image file (string) - @param filename name of the file for non pixmaps (string) - @return diagram pixmap to receive the diagram (QPixmap) + @param rect minimum rectangle fitting the diagram + @type QRectF + @param imageFormat format for the image file + @type str + @param filename name of the file for non pixmaps + str + @return paint device containing the diagram + @rtype QPixmap or QSvgGenerator """ selectedItems = self.scene().selectedItems()
--- a/eric6/Graphics/ClassItem.py Sat May 01 16:01:43 2021 +0200 +++ b/eric6/Graphics/ClassItem.py Sat May 01 18:48:35 2021 +0200 @@ -7,6 +7,7 @@ Module implementing an UML like class item. """ +from PyQt5.QtCore import QCoreApplication from PyQt5.QtGui import QFont from PyQt5.QtWidgets import QGraphicsSimpleTextItem, QStyle @@ -19,53 +20,88 @@ """ Class implementing the class model. """ - def __init__(self, name, methods=None, attributes=None): + def __init__(self, name, methods=None, instanceAttributes=None, + classAttributes=None): """ Constructor - @param name the class name (string) + @param name the class name + @type str @param methods list of method names of the class - (list of strings) - @param attributes list of attribute names of the class - (list of strings) + @type list of str + @param instanceAttributes list of instance attribute names of the class + @type list of str + @param classAttributes list of class attribute names of the class + @type list of str """ super().__init__(name) self.methods = [] if methods is None else methods[:] - self.attributes = [] if attributes is None else attributes[:] + self.instanceAttributes = ( + [] + if instanceAttributes is None else + instanceAttributes[:] + ) + self.classAttributes = ( + [] + if classAttributes is None else + classAttributes[:] + ) def addMethod(self, method): """ Public method to add a method to the class model. - @param method method name to be added (string) + @param method method name to be added + @type str """ self.methods.append(method) - def addAttribute(self, attribute): + def addInstanceAttribute(self, attribute): """ - Public method to add an attribute to the class model. + Public method to add an instance attribute to the class model. - @param attribute attribute name to be added (string) + @param attribute instance attribute name to be added + @type str + """ + self.instanceAttributes.append(attribute) + + def addClassAttribute(self, attribute): """ - self.attributes.append(attribute) + Public method to add a class attribute to the class model. + + @param attribute class attribute name to be added + @type str + """ + self.classAttributes.append(attribute) def getMethods(self): """ Public method to retrieve the methods of the class. - @return list of class methods (list of strings) + @return list of class methods + @type list of str """ return self.methods[:] - def getAttributes(self): + def getInstanceAttributes(self): """ Public method to retrieve the attributes of the class. - @return list of class attributes (list of strings) + @return list of instance attributes + @type list of str + """ + return self.instanceAttributes[:] + + def getClassAttributes(self): """ - return self.attributes[:] - + Public method to retrieve the global attributes of the class. + + @return list of class attributes + @type list of str + """ + return self.classAttributes[:] + class ClassItem(UMLItem): """ @@ -118,8 +154,10 @@ boldFont = QFont(self.font) boldFont.setBold(True) + boldFont.setUnderline(True) - attrs = self.model.getAttributes() + classAttributes = self.model.getClassAttributes() + attrs = self.model.getInstanceAttributes() meths = self.model.getMethods() x = self.margin + self.rect().x() @@ -130,13 +168,32 @@ self.header.setText(self.model.getName()) self.header.setPos(x, y) y += self.header.boundingRect().height() + self.margin + + if self.external: + self.classAttributes = None + else: + txt = QCoreApplication.translate( + "ClassItem", "Class Attributes:\n ") + txt += ( + "\n ".join(classAttributes) + if globals else + " " + QCoreApplication.translate("ClassItem", "none") + ) + self.classAttributes = QGraphicsSimpleTextItem(self) + self.classAttributes.setBrush(self._colors[0]) + self.classAttributes.setFont(self.font) + self.classAttributes.setText(txt) + self.classAttributes.setPos(x, y) + y += self.classAttributes.boundingRect().height() + self.margin + if not self.noAttrs and not self.external: - # TODO: add class attributes next to the instance attributes - # separate class and instance attributes by dashed line - if attrs: - txt = "\n".join(attrs) - else: - txt = " " + txt = QCoreApplication.translate( + "ClassItem", "Instance Attributes:\n ") + txt += ( + "\n ".join(attrs) + if attrs else + " " + QCoreApplication.translate("ClassItem", "none") + ) self.attrs = QGraphicsSimpleTextItem(self) self.attrs.setBrush(self._colors[0]) self.attrs.setFont(self.font) @@ -145,7 +202,16 @@ y += self.attrs.boundingRect().height() + self.margin else: self.attrs = None - txt = "\n".join(meths) if meths else " " + + if self.external: + txt = " " + else: + txt = QCoreApplication.translate("ClassItem", "Methods:\n ") + txt += ( + "\n ".join(meths) + if meths else + " " + QCoreApplication.translate("ClassItem", "none") + ) self.meths = QGraphicsSimpleTextItem(self) self.meths.setBrush(self._colors[0]) self.meths.setFont(self.font) @@ -161,12 +227,16 @@ width = self.header.boundingRect().width() height = self.header.boundingRect().height() + if self.classAttributes: + width = max(width, self.classAttributes.boundingRect().width()) + height += self.classAttributes.boundingRect().height() + self.margin if self.attrs: width = max(width, self.attrs.boundingRect().width()) height = height + self.attrs.boundingRect().height() + self.margin if self.meths: width = max(width, self.meths.boundingRect().width()) height += self.meths.boundingRect().height() + self.setSize(width + 2 * self.margin, height + 2 * self.margin) def setModel(self, model): @@ -177,6 +247,9 @@ """ self.scene().removeItem(self.header) self.header = None + if self.classAttributes: + self.scene().removeItem(self.classAttributes) + self.classAttributes = None if self.attrs: self.scene().removeItem(self.attrs) self.attrs = None @@ -216,6 +289,10 @@ painter.drawRect(offsetX, offsetY, w, h) y = self.margin + self.header.boundingRect().height() painter.drawLine(offsetX, offsetY + y, offsetX + w - 1, offsetY + y) + if self.classAttributes: + y += self.margin + self.classAttributes.boundingRect().height() + painter.drawLine(offsetX, offsetY + y, + offsetX + w - 1, offsetY + y) if self.attrs: y += self.margin + self.attrs.boundingRect().height() painter.drawLine(offsetX, offsetY + y, @@ -246,12 +323,18 @@ "no_attributes={0}".format(self.noAttrs), "name={0}".format(self.model.getName()), ] - attributes = self.model.getAttributes() - if attributes: - entries.append("attributes={0}".format("||".join(attributes))) + instanceAttributes = self.model.getInstanceAttributes() + if instanceAttributes: + entries.append("attributes={0}".format( + "||".join(instanceAttributes))) methods = self.model.getMethods() if methods: - entries.append("methods={0}".format("||".join(methods))) + entries.append("methods={0}".format( + "||".join(methods))) + classAttributes = self.model.getClassAttributes() + if classAttributes: + entries.append("class_attributes={0}".format( + "||".join(classAttributes))) return ", " + ", ".join(entries) @@ -268,8 +351,9 @@ return False name = "" - attributes = [] + instanceAttributes = [] methods = [] + classAttributes = [] for part in parts: key, value = part.split("=", 1) @@ -280,13 +364,16 @@ elif key == "name": name = value.strip() elif key == "attributes": - attributes = value.strip().split("||") + instanceAttributes = value.strip().split("||") elif key == "methods": methods = value.strip().split("||") + elif key == "class_attributes": + classAttributes = value.strip().split("||") else: return False - self.model = ClassModel(name, methods, attributes) + self.model = ClassModel(name, methods, instanceAttributes, + classAttributes) self.__createTexts() self.__calculateSize()
--- a/eric6/Graphics/ImportsDiagramBuilder.py Sat May 01 16:01:43 2021 +0200 +++ b/eric6/Graphics/ImportsDiagramBuilder.py Sat May 01 18:48:35 2021 +0200 @@ -135,39 +135,41 @@ sceneRect = self.umlView.sceneRect() modules = self.__buildModulesDict() - sortedkeys = sorted(modules.keys()) externalMods = [] packageList = self.shortPackage.split('.') packageListLen = len(packageList) - for module in sortedkeys: + for module in sorted(modules.keys()): impLst = [] - for i in modules[module].imports: - n = (i[len(self.package) + 1:] - if i.startswith(self.package) else i) - if i in modules: + for importName in modules[module].imports: + n = ( + importName[len(self.package) + 1:] + if importName.startswith(self.package) else + importName + ) + if importName in modules: impLst.append(n) elif self.showExternalImports: impLst.append(n) if n not in externalMods: externalMods.append(n) - for i in list(modules[module].from_imports.keys()): - if i.startswith('.'): - dots = len(i) - len(i.lstrip('.')) + for importName in list(modules[module].from_imports.keys()): + if importName.startswith('.'): + dots = len(importName) - len(importName.lstrip('.')) if dots == 1: - n = i[1:] - i = n + n = importName[1:] + importName = n else: if self.showExternalImports: n = '.'.join( packageList[:packageListLen - dots + 1] + - [i[dots:]]) + [importName[dots:]]) else: - n = i - elif i.startswith(self.package): - n = i[len(self.package) + 1:] + n = importName + elif importName.startswith(self.package): + n = importName[len(self.package) + 1:] else: - n = i - if i in modules: + n = importName + if importName in modules: impLst.append(n) elif self.showExternalImports: impLst.append(n)
--- a/eric6/Graphics/PackageDiagramBuilder.py Sat May 01 16:01:43 2021 +0200 +++ b/eric6/Graphics/PackageDiagramBuilder.py Sat May 01 18:48:35 2021 +0200 @@ -378,12 +378,15 @@ @param isRbModule flag indicating a Ruby module (boolean) """ 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())
--- a/eric6/Graphics/UMLClassDiagramBuilder.py Sat May 01 16:01:43 2021 +0200 +++ b/eric6/Graphics/UMLClassDiagramBuilder.py Sat May 01 18:48:35 2021 +0200 @@ -234,12 +234,15 @@ @param isRbModule flag indicating a Ruby module (boolean) """ 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())
--- a/eric6/Project/ProjectSourcesBrowser.py Sat May 01 16:01:43 2021 +0200 +++ b/eric6/Project/ProjectSourcesBrowser.py Sat May 01 18:48:35 2021 +0200 @@ -123,7 +123,6 @@ self.tr('Profile data...'), self.__showProfileData) self.menuShow.aboutToShow.connect(self.__showContextMenuShow) - # TODO: differentiate between file and directory/package self.graphicsMenu = QMenu(self.tr('Diagrams')) self.classDiagramAction = self.graphicsMenu.addAction( self.tr("Class Diagram..."), self.__showClassDiagram) @@ -1094,7 +1093,7 @@ fn = itm.fileName() except AttributeError: fn = itm.dirName() - package = os.path.isdir(fn) and fn or os.path.dirname(fn) + package = fn if os.path.isdir(fn) else os.path.dirname(fn) res = E5MessageBox.yesNo( self, self.tr("Imports Diagram"), @@ -1115,7 +1114,7 @@ fn = itm.fileName() except AttributeError: fn = itm.dirName() - package = os.path.isdir(fn) and fn or os.path.dirname(fn) + package = fn if os.path.isdir(fn) else os.path.dirname(fn) res = E5MessageBox.yesNo( self, self.tr("Package Diagram"),
--- a/eric6/QScintilla/Editor.py Sat May 01 16:01:43 2021 +0200 +++ b/eric6/QScintilla/Editor.py Sat May 01 18:48:35 2021 +0200 @@ -7595,10 +7595,7 @@ if not self.checkDirty(): return - package = ( - os.path.isdir(self.fileName) and - self.fileName or os.path.dirname(self.fileName) - ) + package = os.path.dirname(self.fileName) res = E5MessageBox.yesNo( self, self.tr("Imports Diagram"),