1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing a package item.
8"""
9
10from PyQt5.QtGui import QFont
11from PyQt5.QtWidgets import QGraphicsSimpleTextItem, QStyle
12
13from .UMLItem import UMLModel, UMLItem
14
15import Utilities
16
17
18class PackageModel(UMLModel):
19    """
20    Class implementing the package model.
21    """
22    def __init__(self, name, moduleslist=None):
23        """
24        Constructor
25
26        @param name package name
27        @type str
28        @param moduleslist list of module names
29        @type list of str
30        """
31        super().__init__(name)
32
33        self.moduleslist = [] if moduleslist is None else moduleslist[:]
34
35    def addModule(self, modulename):
36        """
37        Public method to add a module to the package model.
38
39        @param modulename module name to be added
40        @type str
41        """
42        self.moduleslist.append(modulename)
43
44    def getModules(self):
45        """
46        Public method to retrieve the modules of the package.
47
48        @return list of module names
49        @rtype list of str
50        """
51        return self.moduleslist[:]
52
53
54class PackageItem(UMLItem):
55    """
56    Class implementing a package item.
57    """
58    ItemType = "package"
59
60    def __init__(self, model=None, x=0, y=0, rounded=False,
61                 noModules=False, colors=None, parent=None, scene=None):
62        """
63        Constructor
64
65        @param model package model containing the package data
66        @type PackageModel
67        @param x x-coordinate
68        @type int
69        @param y y-coordinate
70        @type int
71        @param rounded flag indicating a rounded corner
72        @type bool
73        @param noModules flag indicating, that no module names should be
74            shown
75        @type bool
76        @param colors tuple containing the foreground and background colors
77        @type tuple of (QColor, QColor)
78        @param parent reference to the parent object
79        @type QGraphicsItem
80        @param scene reference to the scene object
81        @type QGraphicsScene
82        """
83        UMLItem.__init__(self, model, x, y, rounded, colors, parent)
84        self.noModules = noModules
85
86        if scene:
87            scene.addItem(self)
88
89        if self.model:
90            self.__createTexts()
91            self.__calculateSize()
92
93    def __createTexts(self):
94        """
95        Private method to create the text items of the class item.
96        """
97        if self.model is None:
98            return
99
100        boldFont = QFont(self.font)
101        boldFont.setBold(True)
102
103        modules = self.model.getModules()
104
105        x = self.margin + self.rect().x()
106        y = self.margin + self.rect().y()
107        self.header = QGraphicsSimpleTextItem(self)
108        self.header.setBrush(self._colors[0])
109        self.header.setFont(boldFont)
110        self.header.setText(self.model.getName())
111        self.header.setPos(x, y)
112        y += self.header.boundingRect().height() + self.margin
113
114        if not self.noModules:
115            if modules:
116                txt = "\n".join(modules)
117            else:
118                txt = " "
119            self.modules = QGraphicsSimpleTextItem(self)
120            self.modules.setBrush(self._colors[0])
121            self.modules.setFont(self.font)
122            self.modules.setText(txt)
123            self.modules.setPos(x, y)
124        else:
125            self.modules = None
126
127    def __calculateSize(self):
128        """
129        Private method to calculate the size of the package widget.
130        """
131        if self.model is None:
132            return
133
134        width = self.header.boundingRect().width()
135        height = self.header.boundingRect().height()
136        if self.modules:
137            width = max(width, self.modules.boundingRect().width())
138            height += self.modules.boundingRect().height()
139        latchW = width / 3.0
140        latchH = min(15.0, latchW)
141        self.setSize(width + 2 * self.margin,
142                     height + latchH + 2 * self.margin)
143
144        x = self.margin + self.rect().x()
145        y = self.margin + self.rect().y() + latchH
146        self.header.setPos(x, y)
147        y += self.header.boundingRect().height() + self.margin
148        if self.modules:
149            self.modules.setPos(x, y)
150
151    def setModel(self, model):
152        """
153        Public method to set the package model.
154
155        @param model package model containing the package data
156        @type PackageModel
157        """
158        self.scene().removeItem(self.header)
159        self.header = None
160        if self.modules:
161            self.scene().removeItem(self.modules)
162            self.modules = None
163        self.model = model
164        self.__createTexts()
165        self.__calculateSize()
166
167    def paint(self, painter, option, widget=None):
168        """
169        Public method to paint the item in local coordinates.
170
171        @param painter reference to the painter object
172        @type QPainter
173        @param option style options
174        @type QStyleOptionGraphicsItem
175        @param widget optional reference to the widget painted on
176        @type QWidget
177        """
178        pen = self.pen()
179        if (
180            (option.state & QStyle.StateFlag.State_Selected) ==
181            QStyle.State(QStyle.StateFlag.State_Selected)
182        ):
183            pen.setWidth(2)
184        else:
185            pen.setWidth(1)
186
187        offsetX = self.rect().x()
188        offsetY = self.rect().y()
189        w = self.rect().width()
190        latchW = w / 3.0
191        latchH = min(15.0, latchW)
192        h = self.rect().height() - latchH + 1
193
194        painter.setPen(pen)
195        painter.setBrush(self.brush())
196        painter.setFont(self.font)
197
198        painter.drawRect(offsetX, offsetY, latchW, latchH)
199        painter.drawRect(offsetX, offsetY + latchH, w, h)
200        y = self.margin + self.header.boundingRect().height() + latchH
201        painter.drawLine(offsetX, offsetY + y, offsetX + w - 1, offsetY + y)
202
203        self.adjustAssociations()
204
205    def buildItemDataString(self):
206        """
207        Public method to build a string to persist the specific item data.
208
209        This string must start with ", " and should be built like
210        "attribute=value" with pairs separated by ", ". value must not
211        contain ", " or newlines.
212
213        @return persistence data
214        @rtype str
215        """
216        entries = [
217            "no_modules={0}".format(self.noModules),
218            "name={0}".format(self.model.getName()),
219        ]
220        modules = self.model.getModules()
221        if modules:
222            entries.append("modules={0}".format("||".join(modules)))
223
224        return ", " + ", ".join(entries)
225
226    def parseItemDataString(self, version, data):
227        """
228        Public method to parse the given persistence data.
229
230        @param version version of the data
231        @type str
232        @param data persisted data to be parsed
233        @type str
234        @return flag indicating success
235        @rtype bool
236        """
237        parts = data.split(", ")
238        if len(parts) < 2:
239            return False
240
241        name = ""
242        modules = []
243
244        for part in parts:
245            key, value = part.split("=", 1)
246            if key == "no_modules":
247                self.external = Utilities.toBool(value.strip())
248            elif key == "name":
249                name = value.strip()
250            elif key == "modules":
251                modules = value.strip().split("||")
252            else:
253                return False
254
255        self.model = PackageModel(name, modules)
256        self.__createTexts()
257        self.__calculateSize()
258
259        return True
260
261    def toDict(self):
262        """
263        Public method to collect data to be persisted.
264
265        @return dictionary containing data to be persisted
266        @rtype dict
267        """
268        return {
269            "id": self.getId(),
270            "x": self.x(),
271            "y": self.y(),
272            "type": self.getItemType(),
273            "model_name": self.model.getName(),
274            "no_nodules": self.noModules,
275            "modules": self.model.getModules(),
276        }
277
278    @classmethod
279    def fromDict(cls, data, colors=None):
280        """
281        Class method to create a class item from persisted data.
282
283        @param data dictionary containing the persisted data as generated
284            by toDict()
285        @type dict
286        @param colors tuple containing the foreground and background colors
287        @type tuple of (QColor, QColor)
288        @return created class item
289        @rtype ClassItem
290        """
291        try:
292            model = PackageModel(data["model_name"],
293                                 data["modules"])
294            itm = cls(model,
295                      x=0,
296                      y=0,
297                      noModules=data["no_nodules"],
298                      colors=colors)
299            itm.setPos(data["x"], data["y"])
300            itm.setId(data["id"])
301            return itm
302        except KeyError:
303            return None
304