eric6/Graphics/UMLClassDiagramBuilder.py

branch
maintenance
changeset 8400
b3eefd7e58d1
parent 8273
698ae46f40a4
parent 8295
3f5e8b0a338e
equal deleted inserted replaced
8274:197414ba11cc 8400:b3eefd7e58d1
6 """ 6 """
7 Module implementing a dialog showing a UML like class diagram. 7 Module implementing a dialog showing a UML like class diagram.
8 """ 8 """
9 9
10 from itertools import zip_longest 10 from itertools import zip_longest
11 import os
11 12
12 from PyQt5.QtWidgets import QGraphicsTextItem 13 from PyQt5.QtWidgets import QGraphicsTextItem
13 14
14 import Utilities 15 import Utilities
15 import Preferences 16 import Preferences
23 """ 24 """
24 def __init__(self, dialog, view, project, file, noAttrs=False): 25 def __init__(self, dialog, view, project, file, noAttrs=False):
25 """ 26 """
26 Constructor 27 Constructor
27 28
28 @param dialog reference to the UML dialog (UMLDialog) 29 @param dialog reference to the UML dialog
29 @param view reference to the view object (UMLGraphicsView) 30 @type UMLDialog
30 @param project reference to the project object (Project) 31 @param view reference to the view object
31 @param file file name of a python module to be shown (string) 32 @type UMLGraphicsView
33 @param project reference to the project object
34 @type Project
35 @param file file name of a python module to be shown
36 @type str
32 @param noAttrs flag indicating, that no attributes should be shown 37 @param noAttrs flag indicating, that no attributes should be shown
33 (boolean) 38 @type bool
34 """ 39 """
35 super().__init__(dialog, view, project) 40 super().__init__(dialog, view, project)
36 self.setObjectName("UMLClassDiagramBuilder") 41 self.setObjectName("UMLClassDiagramBuilder")
37 42
38 self.file = file 43 self.file = file
39 self.noAttrs = noAttrs 44 self.noAttrs = noAttrs
45
46 self.__relFile = (
47 self.project.getRelativePath(self.file)
48 if self.project.isProjectSource(self.file) else
49 ""
50 )
40 51
41 def initialize(self): 52 def initialize(self):
42 """ 53 """
43 Public method to initialize the object. 54 Public method to initialize the object.
44 """ 55 """
53 64
54 def __getCurrentShape(self, name): 65 def __getCurrentShape(self, name):
55 """ 66 """
56 Private method to get the named shape. 67 Private method to get the named shape.
57 68
58 @param name name of the shape (string) 69 @param name name of the shape
59 @return shape (QGraphicsItem) 70 @type str
71 @return shape
72 @rtype QGraphicsItem
60 """ 73 """
61 return self.allClasses.get(name) 74 return self.allClasses.get(name)
62 75
63 def buildDiagram(self): 76 def buildDiagram(self):
64 """ 77 """
78 ) 91 )
79 module = Utilities.ModuleParser.readModule( 92 module = Utilities.ModuleParser.readModule(
80 self.file, extensions=extensions, caching=False) 93 self.file, extensions=extensions, caching=False)
81 except ImportError: 94 except ImportError:
82 ct = QGraphicsTextItem(None) 95 ct = QGraphicsTextItem(None)
83 ct.setHtml( 96 ct.setHtml(self.buildErrorMessage(
84 self.tr("The module <b>'{0}'</b> could not be found.") 97 self.tr("The module <b>'{0}'</b> could not be found.")
85 .format(self.file)) 98 .format(self.file)
99 ))
86 self.scene.addItem(ct) 100 self.scene.addItem(ct)
87 return 101 return
88 102
89 if self.file not in self.allModules: 103 if self.file not in self.allModules:
90 self.allModules[self.file] = [] 104 self.allModules[self.file] = []
140 self.__arrangeClasses(nodes, routes[:]) 154 self.__arrangeClasses(nodes, routes[:])
141 self.__createAssociations(routes) 155 self.__createAssociations(routes)
142 self.umlView.autoAdjustSceneSize(limit=True) 156 self.umlView.autoAdjustSceneSize(limit=True)
143 else: 157 else:
144 ct = QGraphicsTextItem(None) 158 ct = QGraphicsTextItem(None)
145 ct.setHtml(self.tr( 159 ct.setHtml(self.buildErrorMessage(
146 "The module <b>'{0}'</b> does not contain any classes.") 160 self.tr("The module <b>'{0}'</b> does not contain any"
147 .format(self.file)) 161 " classes.").format(self.file)
162 ))
148 self.scene.addItem(ct) 163 self.scene.addItem(ct)
149 164
150 def __arrangeClasses(self, nodes, routes, whiteSpaceFactor=1.2): 165 def __arrangeClasses(self, nodes, routes, whiteSpaceFactor=1.2):
151 """ 166 """
152 Private method to arrange the shapes on the canvas. 167 Private method to arrange the shapes on the canvas.
153 168
154 The algorithm is borrowed from Boa Constructor. 169 The algorithm is borrowed from Boa Constructor.
155 170
156 @param nodes list of nodes to arrange 171 @param nodes list of nodes to arrange
172 @type list of str
157 @param routes list of routes 173 @param routes list of routes
174 @type list of tuple of (str, str)
158 @param whiteSpaceFactor factor to increase whitespace between 175 @param whiteSpaceFactor factor to increase whitespace between
159 items (float) 176 items
177 @type float
160 """ 178 """
161 from . import GraphicsUtilities 179 from . import GraphicsUtilities
162 generations = GraphicsUtilities.sort(nodes, routes) 180 generations = GraphicsUtilities.sort(nodes, routes)
163 181
164 # calculate width and height of all elements 182 # calculate width and height of all elements
225 243
226 def __addLocalClass(self, className, _class, x, y, isRbModule=False): 244 def __addLocalClass(self, className, _class, x, y, isRbModule=False):
227 """ 245 """
228 Private method to add a class defined in the module. 246 Private method to add a class defined in the module.
229 247
230 @param className name of the class to be as a dictionary key (string) 248 @param className name of the class to be as a dictionary key
231 @param _class class to be shown (ModuleParser.Class) 249 @type str
232 @param x x-coordinate (float) 250 @param _class class to be shown
233 @param y y-coordinate (float) 251 @type ModuleParser.Class
234 @param isRbModule flag indicating a Ruby module (boolean) 252 @param x x-coordinate
253 @type float
254 @param y y-coordinate
255 @type float
256 @param isRbModule flag indicating a Ruby module
257 @type bool
235 """ 258 """
236 from .ClassItem import ClassItem, ClassModel 259 from .ClassItem import ClassItem, ClassModel
237 meths = sorted(_class.methods.keys())
238 attrs = sorted(_class.attributes.keys())
239 name = _class.name 260 name = _class.name
240 if isRbModule: 261 if isRbModule:
241 name = "{0} (Module)".format(name) 262 name = "{0} (Module)".format(name)
242 cl = ClassModel(name, meths[:], attrs[:]) 263 cl = ClassModel(
264 name,
265 sorted(_class.methods.keys())[:],
266 sorted(_class.attributes.keys())[:],
267 sorted(_class.globals.keys())[:]
268 )
243 cw = ClassItem(cl, False, x, y, noAttrs=self.noAttrs, scene=self.scene, 269 cw = ClassItem(cl, False, x, y, noAttrs=self.noAttrs, scene=self.scene,
244 colors=self.umlView.getDrawingColors()) 270 colors=self.umlView.getDrawingColors())
245 cw.setId(self.umlView.getItemId()) 271 cw.setId(self.umlView.getItemId())
246 self.allClasses[className] = cw 272 self.allClasses[className] = cw
247 if _class.name not in self.allModules[self.file]: 273 if _class.name not in self.allModules[self.file]:
252 Private method to add a class defined outside the module. 278 Private method to add a class defined outside the module.
253 279
254 If the canvas is too small to take the shape, it 280 If the canvas is too small to take the shape, it
255 is enlarged. 281 is enlarged.
256 282
257 @param _class class to be shown (string) 283 @param _class class to be shown
258 @param x x-coordinate (float) 284 @type ModuleParser.Class
259 @param y y-coordinate (float) 285 @param x x-coordinate
286 @type float
287 @param y y-coordinate
288 @type float
260 """ 289 """
261 from .ClassItem import ClassItem, ClassModel 290 from .ClassItem import ClassItem, ClassModel
262 cl = ClassModel(_class) 291 cl = ClassModel(_class)
263 cw = ClassItem(cl, True, x, y, noAttrs=self.noAttrs, scene=self.scene, 292 cw = ClassItem(cl, True, x, y, noAttrs=self.noAttrs, scene=self.scene,
264 colors=self.umlView.getDrawingColors()) 293 colors=self.umlView.getDrawingColors())
270 def __createAssociations(self, routes): 299 def __createAssociations(self, routes):
271 """ 300 """
272 Private method to generate the associations between the class shapes. 301 Private method to generate the associations between the class shapes.
273 302
274 @param routes list of relationsships 303 @param routes list of relationsships
304 @type list of tuple of (str, str)
275 """ 305 """
276 from .AssociationItem import AssociationItem, AssociationType 306 from .AssociationItem import AssociationItem, AssociationType
277 for route in routes: 307 for route in routes:
278 if len(route) > 1: 308 if len(route) > 1:
279 assoc = AssociationItem( 309 assoc = AssociationItem(
286 316
287 def getPersistenceData(self): 317 def getPersistenceData(self):
288 """ 318 """
289 Public method to get a string for data to be persisted. 319 Public method to get a string for data to be persisted.
290 320
291 @return persisted data string (string) 321 @return persisted data string
322 @rtype str
292 """ 323 """
293 return "file={0}, no_attributes={1}".format(self.file, self.noAttrs) 324 return "file={0}, no_attributes={1}".format(self.file, self.noAttrs)
294 325
295 def parsePersistenceData(self, version, data): 326 def parsePersistenceData(self, version, data):
296 """ 327 """
297 Public method to parse persisted data. 328 Public method to parse persisted data.
298 329
299 @param version version of the data (string) 330 @param version version of the data
300 @param data persisted data to be parsed (string) 331 @type str
301 @return flag indicating success (boolean) 332 @param data persisted data to be parsed
333 @type str
334 @return flag indicating success
335 @rtype bool
302 """ 336 """
303 parts = data.split(", ") 337 parts = data.split(", ")
304 if ( 338 if (
305 len(parts) != 2 or 339 len(parts) != 2 or
306 not parts[0].startswith("file=") or 340 not parts[0].startswith("file=") or
312 self.noAttrs = Utilities.toBool(parts[1].split("=", 1)[1].strip()) 346 self.noAttrs = Utilities.toBool(parts[1].split("=", 1)[1].strip())
313 347
314 self.initialize() 348 self.initialize()
315 349
316 return True 350 return True
351
352 def toDict(self):
353 """
354 Public method to collect data to be persisted.
355
356 @return dictionary containing data to be persisted
357 @rtype dict
358 """
359 data = {
360 "project_name": self.project.getProjectName(),
361 "no_attributes": self.noAttrs,
362 }
363
364 data["file"] = (
365 Utilities.fromNativeSeparators(self.__relFile)
366 if self.__relFile else
367 Utilities.fromNativeSeparators(self.file)
368 )
369
370 return data
371
372 def fromDict(self, version, data):
373 """
374 Public method to populate the class with data persisted by 'toDict()'.
375
376 @param version version of the data
377 @type str
378 @param data dictionary containing the persisted data
379 @type dict
380 @return tuple containing a flag indicating success and an info
381 message in case the diagram belongs to a different project
382 @rtype tuple of (bool, str)
383 """
384 try:
385 self.noAttrs = data["no_attributes"]
386
387 file = Utilities.toNativeSeparators(data["file"])
388 if os.path.isabs(file):
389 self.file = file
390 self.__relFile = ""
391 else:
392 # relative file paths indicate a project file
393 if data["project_name"] != self.project.getProjectName():
394 msg = self.tr(
395 "<p>The diagram belongs to project <b>{0}</b>."
396 " Please open it and try again.</p>"
397 ).format(data["project_name"])
398 return False, msg
399
400 self.__relFile = file
401 self.file = self.project.getAbsolutePath(file)
402 except KeyError:
403 return False, ""
404
405 self.initialize()
406
407 return True, ""

eric ide

mercurial