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, "" |