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