86 moduleDict[name] = mod |
86 moduleDict[name] = mod |
87 finally: |
87 finally: |
88 progress.setValue(tot) |
88 progress.setValue(tot) |
89 progress.deleteLater() |
89 progress.deleteLater() |
90 return moduleDict |
90 return moduleDict |
|
91 |
|
92 def __findApplicationRoot(self): |
|
93 """ |
|
94 Private method to find the application root path. |
|
95 |
|
96 @return application root path |
|
97 @rtype str |
|
98 """ |
|
99 candidates = [] |
|
100 path = self.project.getProjectPath() |
|
101 init = os.path.join(path, "__init__.py") |
|
102 if os.path.exists(init): |
|
103 # project is a package |
|
104 return path |
|
105 else: |
|
106 # check, if one of the top directories is a package |
|
107 for entry in os.listdir(path): |
|
108 if entry.startswith("."): |
|
109 # ignore hidden files and directories |
|
110 continue |
|
111 |
|
112 fullpath = os.path.join(path, entry) |
|
113 if os.path.isdir(fullpath): |
|
114 init = os.path.join(fullpath, "__init__.py") |
|
115 if os.path.exists(init): |
|
116 candidates.append(fullpath) |
|
117 |
|
118 if len(candidates) == 1: |
|
119 return candidates[0] |
|
120 elif len(candidates) > 1: |
|
121 root, ok = QInputDialog.getItem( |
|
122 None, |
|
123 self.tr("Application Diagram"), |
|
124 self.tr("Select the application directory:"), |
|
125 sorted(candidates), |
|
126 0, True) |
|
127 if ok: |
|
128 return root |
|
129 else: |
|
130 E5MessageBox.warning( |
|
131 None, |
|
132 self.tr("Application Diagram"), |
|
133 self.tr("""No application package could be detected.""" |
|
134 """ Aborting...""")) |
|
135 return None |
91 |
136 |
92 def buildDiagram(self): |
137 def buildDiagram(self): |
93 """ |
138 """ |
94 Public method to build the packages shapes of the diagram. |
139 Public method to build the packages shapes of the diagram. |
95 """ |
140 """ |
96 project = ( |
141 rpath = self.__findApplicationRoot() |
97 os.path.splitdrive(self.project.getProjectPath())[1] |
142 if rpath is None: |
98 .replace(os.sep, '.')[1:] |
143 # no root path detected |
99 ) |
144 return |
|
145 |
|
146 root = os.path.splitdrive(rpath)[1].replace(os.sep, '.')[1:] |
|
147 |
100 packages = {} |
148 packages = {} |
101 shapes = {} |
149 self.__shapes = {} |
102 p = 10 |
|
103 y = 10 |
|
104 maxHeight = 0 |
|
105 sceneRect = self.umlView.sceneRect() |
|
106 |
150 |
107 modules = self.__buildModulesDict() |
151 modules = self.__buildModulesDict() |
108 |
152 |
109 # step 1: build a dictionary of packages |
153 # step 1: build a dictionary of packages |
110 for module in sorted(modules.keys()): |
154 for module in sorted(modules.keys()): |
111 packageName, moduleName = module.rsplit(".", 1) |
155 if "." in module: |
|
156 packageName, moduleName = module.rsplit(".", 1) |
|
157 else: |
|
158 packageName, moduleName = "", module |
112 if packageName in packages: |
159 if packageName in packages: |
113 packages[packageName][0].append(moduleName) |
160 packages[packageName][0].append(moduleName) |
114 else: |
161 else: |
115 packages[packageName] = ([moduleName], []) |
162 packages[packageName] = ([moduleName], []) |
116 |
163 |
168 n = "{0}.{1}".format(modules[module].package, |
215 n = "{0}.{1}".format(modules[module].package, |
169 moduleImport) |
216 moduleImport) |
170 if n in modules: |
217 if n in modules: |
171 impLst.append(n) |
218 impLst.append(n) |
172 else: |
219 else: |
173 n = "{0}.{1}".format(project, moduleImport) |
220 n = "{0}.{1}".format(root, moduleImport) |
174 if n in modules: |
221 if n in modules: |
175 impLst.append(n) |
222 impLst.append(n) |
176 elif n in packages: |
223 elif n in packages: |
177 n = "{0}.<<Dummy>>".format(n) |
224 n = "{0}.<<Dummy>>".format(n) |
178 impLst.append(n) |
225 impLst.append(n) |
179 else: |
226 else: |
180 n = "{0}.{1}".format(project, moduleImport) |
227 n = "{0}.{1}".format(root, moduleImport) |
181 if n in modules: |
228 if n in modules: |
182 impLst.append(n) |
229 impLst.append(n) |
183 for moduleImport in impLst: |
230 for moduleImport in impLst: |
184 impPackage = moduleImport.rsplit(".", 1)[0] |
231 impPackage = moduleImport.rsplit(".", 1)[0] |
185 if ( |
232 try: |
186 impPackage not in packages[package][1] and |
233 if ( |
187 impPackage != package |
234 impPackage not in packages[package][1] and |
188 ): |
235 impPackage != package |
189 packages[package][1].append(impPackage) |
236 ): |
190 |
237 packages[package][1].append(impPackage) |
|
238 except KeyError: |
|
239 continue |
|
240 |
191 for package in sorted(packages.keys()): |
241 for package in sorted(packages.keys()): |
192 if package: |
242 if package: |
193 relPackage = package.replace(project, '') |
243 relPackage = package.replace(root, '') |
194 if relPackage and relPackage[0] == '.': |
244 if relPackage and relPackage[0] == '.': |
195 relPackage = relPackage[1:] |
245 relPackage = relPackage[1:] |
196 else: |
246 else: |
197 relPackage = self.tr("<<Application>>") |
247 relPackage = self.tr("<<Application>>") |
198 else: |
248 else: |
199 relPackage = self.tr("<<Others>>") |
249 relPackage = self.tr("<<Others>>") |
200 shape = self.__addPackage( |
250 shape = self.__addPackage( |
201 relPackage, packages[package][0], 0.0, 0.0) |
251 relPackage, packages[package][0], 0.0, 0.0) |
202 shapeRect = shape.sceneBoundingRect() |
252 self.__shapes[package] = (shape, packages[package][1]) |
203 shapes[package] = (shape, packages[package][1]) |
253 |
204 pn = p + shapeRect.width() + 10 |
254 # build a list of routes |
205 maxHeight = max(maxHeight, shapeRect.height()) |
255 nodes = [] |
206 if pn > sceneRect.width(): |
256 routes = [] |
207 p = 10 |
257 for module in self.__shapes: |
208 y += maxHeight + 10 |
258 nodes.append(module) |
209 maxHeight = shapeRect.height() |
259 for rel in self.__shapes[module][1]: |
210 shape.setPos(p, y) |
260 route = (module, rel) |
211 p += shapeRect.width() + 10 |
261 if route not in routes: |
212 else: |
262 routes.append(route) |
213 shape.setPos(p, y) |
263 |
214 p = pn |
264 self.__arrangeNodes(nodes, routes[:]) |
215 |
265 self.__createAssociations(routes) |
216 rect = self.umlView._getDiagramRect(10) |
|
217 sceneRect = self.umlView.sceneRect() |
|
218 if rect.width() > sceneRect.width(): |
|
219 sceneRect.setWidth(rect.width()) |
|
220 if rect.height() > sceneRect.height(): |
|
221 sceneRect.setHeight(rect.height()) |
|
222 self.umlView.setSceneSize(sceneRect.width(), sceneRect.height()) |
|
223 |
|
224 self.__createAssociations(shapes) |
|
225 self.umlView.autoAdjustSceneSize(limit=True) |
266 self.umlView.autoAdjustSceneSize(limit=True) |
226 |
267 |
227 def __addPackage(self, name, modules, x, y): |
268 def __addPackage(self, name, modules, x, y): |
228 """ |
269 """ |
229 Private method to add a package to the diagram. |
270 Private method to add a package to the diagram. |
230 |
271 |
231 @param name package name to be shown (string) |
272 @param name package name to be shown (string) |
240 pm = PackageModel(name, modules) |
281 pm = PackageModel(name, modules) |
241 pw = PackageItem(pm, x, y, noModules=self.noModules, scene=self.scene, |
282 pw = PackageItem(pm, x, y, noModules=self.noModules, scene=self.scene, |
242 colors=self.umlView.getDrawingColors()) |
283 colors=self.umlView.getDrawingColors()) |
243 pw.setId(self.umlView.getItemId()) |
284 pw.setId(self.umlView.getItemId()) |
244 return pw |
285 return pw |
245 |
286 |
246 def __createAssociations(self, shapes): |
287 def __arrangeNodes(self, nodes, routes, whiteSpaceFactor=1.2): |
247 """ |
288 """ |
248 Private method to generate the associations between the package shapes. |
289 Private method to arrange the shapes on the canvas. |
249 |
290 |
250 @param shapes list of shapes |
291 The algorithm is borrowed from Boa Constructor. |
|
292 |
|
293 @param nodes list of nodes to arrange |
|
294 @type list of str |
|
295 @param routes list of routes |
|
296 @type list of tuple of (str, str) |
|
297 @param whiteSpaceFactor factor to increase whitespace between |
|
298 items |
|
299 @type float |
|
300 """ |
|
301 from . import GraphicsUtilities |
|
302 generations = GraphicsUtilities.sort(nodes, routes) |
|
303 |
|
304 # calculate width and height of all elements |
|
305 sizes = [] |
|
306 for generation in generations: |
|
307 sizes.append([]) |
|
308 for child in generation: |
|
309 sizes[-1].append( |
|
310 self.__shapes[child][0].sceneBoundingRect()) |
|
311 |
|
312 # calculate total width and total height |
|
313 width = 0 |
|
314 height = 0 |
|
315 widths = [] |
|
316 heights = [] |
|
317 for generation in sizes: |
|
318 currentWidth = 0 |
|
319 currentHeight = 0 |
|
320 |
|
321 for rect in generation: |
|
322 if rect.height() > currentHeight: |
|
323 currentHeight = rect.height() |
|
324 currentWidth += rect.width() |
|
325 |
|
326 # update totals |
|
327 if currentWidth > width: |
|
328 width = currentWidth |
|
329 height += currentHeight |
|
330 |
|
331 # store generation info |
|
332 widths.append(currentWidth) |
|
333 heights.append(currentHeight) |
|
334 |
|
335 # add in some whitespace |
|
336 width *= whiteSpaceFactor |
|
337 height = height * whiteSpaceFactor - 20 |
|
338 verticalWhiteSpace = 40.0 |
|
339 |
|
340 sceneRect = self.umlView.sceneRect() |
|
341 width += 50.0 |
|
342 height += 50.0 |
|
343 swidth = sceneRect.width() if width < sceneRect.width() else width |
|
344 sheight = sceneRect.height() if height < sceneRect.height() else height |
|
345 self.umlView.setSceneSize(swidth, sheight) |
|
346 |
|
347 # distribute each generation across the width and the |
|
348 # generations across height |
|
349 y = 10.0 |
|
350 for currentWidth, currentHeight, generation in ( |
|
351 zip(reversed(widths), reversed(heights), reversed(generations)) |
|
352 ): |
|
353 x = 10.0 |
|
354 # whiteSpace is the space between any two elements |
|
355 whiteSpace = ( |
|
356 (width - currentWidth - 20) / |
|
357 (len(generation) - 1.0 or 2.0) |
|
358 ) |
|
359 for name in generation: |
|
360 shape = self.__shapes[name][0] |
|
361 shape.setPos(x, y) |
|
362 rect = shape.sceneBoundingRect() |
|
363 x = x + rect.width() + whiteSpace |
|
364 y = y + currentHeight + verticalWhiteSpace |
|
365 |
|
366 def __createAssociations(self, routes): |
|
367 """ |
|
368 Private method to generate the associations between the module shapes. |
|
369 |
|
370 @param routes list of associations |
|
371 @type list of tuple of (str, str) |
251 """ |
372 """ |
252 from .AssociationItem import AssociationItem, AssociationType |
373 from .AssociationItem import AssociationItem, AssociationType |
253 for package in shapes: |
374 for route in routes: |
254 for rel in shapes[package][1]: |
375 assoc = AssociationItem( |
255 assoc = AssociationItem( |
376 self.__shapes[route[0]][0], |
256 shapes[package][0], shapes[rel][0], |
377 self.__shapes[route[1]][0], |
257 AssociationType.IMPORTS, |
378 AssociationType.IMPORTS, |
258 colors=self.umlView.getDrawingColors()) |
379 colors=self.umlView.getDrawingColors()) |
259 self.scene.addItem(assoc) |
380 self.scene.addItem(assoc) |
260 |
381 |
261 def getPersistenceData(self): |
382 def getPersistenceData(self): |
262 """ |
383 """ |
263 Public method to get a string for data to be persisted. |
384 Public method to get a string for data to be persisted. |
264 |
385 |