1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2004 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing the UMLItem base class. 8""" 9 10from PyQt5.QtCore import Qt, QSizeF 11from PyQt5.QtGui import QColor, QPen 12from PyQt5.QtWidgets import QGraphicsItem, QGraphicsRectItem, QStyle 13 14import Preferences 15 16 17class UMLModel: 18 """ 19 Class implementing the UMLModel base class. 20 """ 21 def __init__(self, name): 22 """ 23 Constructor 24 25 @param name package name 26 @type str 27 """ 28 self.name = name 29 30 def getName(self): 31 """ 32 Public method to retrieve the model name. 33 34 @return model name 35 @rtype str 36 """ 37 return self.name 38 39 40class UMLItem(QGraphicsRectItem): 41 """ 42 Class implementing the UMLItem base class. 43 """ 44 ItemType = "UMLItem" 45 46 def __init__(self, model=None, x=0, y=0, rounded=False, colors=None, 47 parent=None): 48 """ 49 Constructor 50 51 @param model UML model containing the item data 52 @type UMLModel 53 @param x x-coordinate 54 @type int 55 @param y y-coordinate 56 @type int 57 @param rounded flag indicating a rounded corner 58 @type bool 59 @param colors tuple containing the foreground and background colors 60 @type tuple of (QColor, QColor) 61 @param parent reference to the parent object 62 @type QGraphicsItem 63 """ 64 super().__init__(parent) 65 self.model = model 66 67 if colors is None: 68 self._colors = (QColor(Qt.GlobalColor.black), 69 QColor(Qt.GlobalColor.white)) 70 else: 71 self._colors = colors 72 self.setPen(QPen(self._colors[0])) 73 74 self.font = Preferences.getGraphics("Font") 75 self.margin = 5 76 self.associations = [] 77 self.shouldAdjustAssociations = False 78 self.__id = -1 79 80 self.setRect(x, y, 60, 30) 81 82 if rounded: 83 p = self.pen() 84 p.setCapStyle(Qt.PenCapStyle.RoundCap) 85 p.setJoinStyle(Qt.PenJoinStyle.RoundJoin) 86 87 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True) 88 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True) 89 self.setFlag( 90 QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges, True) 91 92 def getName(self): 93 """ 94 Public method to retrieve the item name. 95 96 @return item name 97 @rtype str 98 """ 99 if self.model: 100 return self.model.name 101 else: 102 return "" 103 104 def setSize(self, width, height): 105 """ 106 Public method to set the rectangles size. 107 108 @param width width of the rectangle 109 @type float 110 @param height height of the rectangle 111 @type float 112 """ 113 rect = self.rect() 114 rect.setSize(QSizeF(width, height)) 115 self.setRect(rect) 116 117 def addAssociation(self, assoc): 118 """ 119 Public method to add an association to this widget. 120 121 @param assoc association to be added 122 @type AssociationWidget 123 """ 124 if assoc and assoc not in self.associations: 125 self.associations.append(assoc) 126 127 def removeAssociation(self, assoc): 128 """ 129 Public method to remove an association to this widget. 130 131 @param assoc association to be removed 132 @type AssociationWidget 133 """ 134 if assoc and assoc in self.associations: 135 self.associations.remove(assoc) 136 137 def removeAssociations(self): 138 """ 139 Public method to remove all associations of this widget. 140 """ 141 for assoc in self.associations[:]: 142 assoc.unassociate() 143 assoc.hide() 144 del assoc 145 146 def adjustAssociations(self): 147 """ 148 Public method to adjust the associations to widget movements. 149 """ 150 if self.shouldAdjustAssociations: 151 for assoc in self.associations: 152 assoc.widgetMoved() 153 self.shouldAdjustAssociations = False 154 155 def moveBy(self, dx, dy): 156 """ 157 Public overriden method to move the widget relative. 158 159 @param dx relative movement in x-direction 160 @type float 161 @param dy relative movement in y-direction 162 @type float 163 """ 164 super().moveBy(dx, dy) 165 self.adjustAssociations() 166 167 def setPos(self, x, y): 168 """ 169 Public overriden method to set the items position. 170 171 @param x absolute x-position 172 @type float 173 @param y absolute y-position 174 @type float 175 """ 176 super().setPos(x, y) 177 self.adjustAssociations() 178 179 def itemChange(self, change, value): 180 """ 181 Public method called when an items state changes. 182 183 @param change the item's change 184 @type QGraphicsItem.GraphicsItemChange 185 @param value the value of the change 186 @return adjusted values 187 """ 188 if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange: 189 # 1. remember to adjust associations 190 self.shouldAdjustAssociations = True 191 192 # 2. ensure the new position is inside the scene 193 scene = self.scene() 194 if scene: 195 rect = scene.sceneRect() 196 if not rect.contains(value): 197 # keep the item inside the scene 198 value.setX(min(rect.right(), max(value.x(), rect.left()))) 199 value.setY(min(rect.bottom(), max(value.y(), rect.top()))) 200 return value 201 202 return QGraphicsItem.itemChange(self, change, value) 203 204 def paint(self, painter, option, widget=None): 205 """ 206 Public method to paint the item in local coordinates. 207 208 @param painter reference to the painter object 209 @type QPainter 210 @param option style options 211 @type QStyleOptionGraphicsItem 212 @param widget optional reference to the widget painted on 213 @type QWidget 214 """ 215 pen = self.pen() 216 if ( 217 (option.state & QStyle.StateFlag.State_Selected) == 218 QStyle.State(QStyle.StateFlag.State_Selected) 219 ): 220 pen.setWidth(2) 221 else: 222 pen.setWidth(1) 223 224 painter.setPen(pen) 225 painter.setBrush(self.brush()) 226 painter.drawRect(self.rect()) 227 self.adjustAssociations() 228 229 def setId(self, itemId): 230 """ 231 Public method to assign an ID to the item. 232 233 @param itemId assigned ID 234 @type int 235 """ 236 self.__id = itemId 237 238 def getId(self): 239 """ 240 Public method to get the item ID. 241 242 @return ID of the item 243 @rtype int 244 """ 245 return self.__id 246 247 def getItemType(self): 248 """ 249 Public method to get the item's type. 250 251 @return item type 252 @rtype str 253 """ 254 return self.ItemType 255 256 def buildItemDataString(self): 257 """ 258 Public method to build a string to persist the specific item data. 259 260 This string must start with ", " and should be built like 261 "attribute=value" with pairs separated by ", ". value must not 262 contain ", " or newlines. 263 264 @return persistence data 265 @rtype str 266 """ 267 return "" 268 269 def parseItemDataString(self, version, data): 270 """ 271 Public method to parse the given persistence data. 272 273 @param version version of the data 274 @type str 275 @param data persisted data to be parsed 276 @type str 277 @return flag indicating success 278 @rtype bool 279 """ 280 return True 281 282 def toDict(self): 283 """ 284 Public method to collect data to be persisted. 285 286 @return dictionary containing data to be persisted 287 @rtype dict 288 """ 289 return { 290 "id": self.getId(), 291 "x": self.x(), 292 "y": self.y(), 293 "type": self.getItemType(), 294 "model_name": self.model.getName(), 295 } 296 297 @classmethod 298 def fromDict(cls, data, colors=None): 299 """ 300 Class method to create a generic UML item from persisted data. 301 302 @param data dictionary containing the persisted data as generated 303 by toDict() 304 @type dict 305 @param colors tuple containing the foreground and background colors 306 @type tuple of (QColor, QColor) 307 @return created UML item 308 @rtype UMLItem 309 """ 310 try: 311 model = UMLModel(data["model_name"]) 312 itm = cls(model=model, 313 x=0, 314 y=0, 315 colors=colors) 316 itm.setPos(data["x"], data["y"]) 317 itm.setId(data["id"]) 318 return itm 319 except KeyError: 320 return None 321