eric7/Graphics/ClassItem.py

branch
eric7
changeset 8312
800c432b34c8
parent 8295
3f5e8b0a338e
child 8318
962bce857696
--- /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

eric ide

mercurial