--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/Graphics/ClassItem.py Sat May 15 18:45:04 2021 +0200 @@ -0,0 +1,442 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an UML like class item. +""" + +from PyQt5.QtCore import QCoreApplication +from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import QGraphicsSimpleTextItem, QStyle + +from .UMLItem import UMLModel, UMLItem + +import Utilities + + +class ClassModel(UMLModel): + """ + Class implementing the class model. + """ + def __init__(self, name, methods=None, instanceAttributes=None, + classAttributes=None): + """ + Constructor + + @param name the class name + @type str + @param methods list of method names of the class + @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.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 + @type str + """ + self.methods.append(method) + + def addInstanceAttribute(self, attribute): + """ + Public method to add an instance attribute to the class model. + + @param attribute instance attribute name to be added + @type str + """ + self.instanceAttributes.append(attribute) + + def addClassAttribute(self, 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 + @rtype list of str + """ + return self.methods[:] + + def getInstanceAttributes(self): + """ + Public method to retrieve the attributes of the class. + + @return list of instance attributes + @rtype list of str + """ + return self.instanceAttributes[:] + + def getClassAttributes(self): + """ + Public method to retrieve the global attributes of the class. + + @return list of class attributes + @rtype list of str + """ + return self.classAttributes[:] + + +class ClassItem(UMLItem): + """ + Class implementing an UML like class item. + """ + ItemType = "class" + + def __init__(self, model=None, external=False, x=0, y=0, + rounded=False, noAttrs=False, colors=None, parent=None, + scene=None): + """ + Constructor + + @param model class model containing the class data + @type ClassModel + @param external flag indicating a class defined outside our scope + @type boolean + @param x x-coordinate + @type int + @param y y-coordinate + @type int + @param rounded flag indicating a rounded corner + @type bool + @param noAttrs flag indicating, that no attributes should be shown + @type bool + @param colors tuple containing the foreground and background colors + @type tuple of (QColor, QColor) + @param parent reference to the parent object + @type QGraphicsItem + @param scene reference to the scene object + @type QGraphicsScene + """ + UMLItem.__init__(self, model, x, y, rounded, colors, parent) + + self.external = external + self.noAttrs = noAttrs + + if scene: + scene.addItem(self) + + if self.model: + self.__createTexts() + self.__calculateSize() + + def __createTexts(self): + """ + Private method to create the text items of the class item. + """ + if self.model is None: + return + + boldFont = QFont(self.font) + boldFont.setBold(True) + boldFont.setUnderline(True) + + classAttributes = self.model.getClassAttributes() + attrs = self.model.getInstanceAttributes() + meths = self.model.getMethods() + + x = self.margin + self.rect().x() + y = self.margin + self.rect().y() + self.header = QGraphicsSimpleTextItem(self) + self.header.setBrush(self._colors[0]) + self.header.setFont(boldFont) + 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: + 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) + self.attrs.setText(txt) + self.attrs.setPos(x, y) + y += self.attrs.boundingRect().height() + self.margin + else: + self.attrs = None + + 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) + self.meths.setText(txt) + self.meths.setPos(x, y) + + def __calculateSize(self): + """ + Private method to calculate the size of the class item. + """ + if self.model is None: + return + + 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): + """ + Public method to set the class model. + + @param model class model containing the class data + @type ClassModel + """ + 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 + if self.meths: + self.scene().removeItem(self.meths) + self.meths = None + self.model = model + self.__createTexts() + self.__calculateSize() + + def paint(self, painter, option, widget=None): + """ + Public method to paint the item in local coordinates. + + @param painter reference to the painter object + @type QPainter + @param option style options + @type QStyleOptionGraphicsItem + @param widget optional reference to the widget painted on + @type QWidget + """ + pen = self.pen() + if ( + (option.state & QStyle.StateFlag.State_Selected) == + QStyle.State(QStyle.StateFlag.State_Selected) + ): + pen.setWidth(2) + else: + pen.setWidth(1) + + painter.setPen(pen) + painter.setBrush(self.brush()) + painter.setFont(self.font) + + offsetX = self.rect().x() + offsetY = self.rect().y() + w = self.rect().width() + h = self.rect().height() + + 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, + offsetX + w - 1, offsetY + y) + + self.adjustAssociations() + + def isExternal(self): + """ + Public method returning the external state. + + @return external state + @rtype bool + """ + return self.external + + def buildItemDataString(self): + """ + Public method to build a string to persist the specific item data. + + This string must start with ", " and should be built like + "attribute=value" with pairs separated by ", ". value must not + contain ", " or newlines. + + @return persistence data + @rtype str + """ + entries = [ + "is_external={0}".format(self.external), + "no_attributes={0}".format(self.noAttrs), + "name={0}".format(self.model.getName()), + ] + 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))) + classAttributes = self.model.getClassAttributes() + if classAttributes: + entries.append("class_attributes={0}".format( + "||".join(classAttributes))) + + return ", " + ", ".join(entries) + + def parseItemDataString(self, version, data): + """ + Public method to parse the given persistence data. + + @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 len(parts) < 3: + return False + + name = "" + instanceAttributes = [] + methods = [] + classAttributes = [] + + for part in parts: + key, value = part.split("=", 1) + if key == "is_external": + self.external = Utilities.toBool(value.strip()) + elif key == "no_attributes": + self.noAttrs = Utilities.toBool(value.strip()) + elif key == "name": + name = value.strip() + elif key == "attributes": + 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, instanceAttributes, + classAttributes) + self.__createTexts() + self.__calculateSize() + + return True + + def toDict(self): + """ + Public method to collect data to be persisted. + + @return dictionary containing data to be persisted + @rtype dict + """ + return { + "id": self.getId(), + "x": self.x(), + "y": self.y(), + "type": self.getItemType(), + "is_external": self.external, + "no_attributes": self.noAttrs, + "model_name": self.model.getName(), + "attributes": self.model.getInstanceAttributes(), + "methods": self.model.getMethods(), + "class_attributes": self.model.getClassAttributes(), + } + + @classmethod + def fromDict(cls, data, colors=None): + """ + Class method to create a class item from persisted data. + + @param data dictionary containing the persisted data as generated + by toDict() + @type dict + @param colors tuple containing the foreground and background colors + @type tuple of (QColor, QColor) + @return created class item + @rtype ClassItem + """ + try: + model = ClassModel(data["model_name"], + data["methods"], + data["attributes"], + data["class_attributes"]) + itm = cls(model=model, + external=data["is_external"], + x=0, + y=0, + noAttrs=data["no_attributes"], + colors=colors) + itm.setPos(data["x"], data["y"]) + itm.setId(data["id"]) + return itm + except KeyError: + return None