46 outside the package |
46 outside the package |
47 @type bool |
47 @type bool |
48 """ |
48 """ |
49 super().__init__(dialog, view, project) |
49 super().__init__(dialog, view, project) |
50 self.setObjectName("ImportsDiagram") |
50 self.setObjectName("ImportsDiagram") |
51 |
51 |
52 self.showExternalImports = showExternalImports |
52 self.showExternalImports = showExternalImports |
53 self.packagePath = os.path.abspath(package) |
53 self.packagePath = os.path.abspath(package) |
54 |
54 |
55 self.__relPackagePath = ( |
55 self.__relPackagePath = ( |
56 self.project.getRelativePath(self.packagePath) |
56 self.project.getRelativePath(self.packagePath) |
57 if self.project.isProjectSource(self.packagePath) else |
57 if self.project.isProjectSource(self.packagePath) |
58 "" |
58 else "" |
59 ) |
59 ) |
60 |
60 |
61 def initialize(self): |
61 def initialize(self): |
62 """ |
62 """ |
63 Public method to initialize the object. |
63 Public method to initialize the object. |
64 """ |
64 """ |
65 self.package = os.path.splitdrive(self.packagePath)[1].replace( |
65 self.package = os.path.splitdrive(self.packagePath)[1].replace(os.sep, ".")[1:] |
66 os.sep, '.')[1:] |
|
67 hasInit = True |
66 hasInit = True |
68 ppath = self.packagePath |
67 ppath = self.packagePath |
69 while hasInit: |
68 while hasInit: |
70 ppath = os.path.dirname(ppath) |
69 ppath = os.path.dirname(ppath) |
71 hasInit = len(glob.glob(os.path.join(ppath, '__init__.*'))) > 0 |
70 hasInit = len(glob.glob(os.path.join(ppath, "__init__.*"))) > 0 |
72 self.shortPackage = self.packagePath.replace(ppath, '').replace( |
71 self.shortPackage = self.packagePath.replace(ppath, "").replace(os.sep, ".")[1:] |
73 os.sep, '.')[1:] |
72 |
74 |
|
75 pname = self.project.getProjectName() |
73 pname = self.project.getProjectName() |
76 name = ( |
74 name = ( |
77 self.tr("Imports Diagramm {0}: {1}").format( |
75 self.tr("Imports Diagramm {0}: {1}").format( |
78 pname, self.project.getRelativePath(self.packagePath)) |
76 pname, self.project.getRelativePath(self.packagePath) |
79 if pname else |
77 ) |
80 self.tr("Imports Diagramm: {0}").format(self.packagePath) |
78 if pname |
|
79 else self.tr("Imports Diagramm: {0}").format(self.packagePath) |
81 ) |
80 ) |
82 self.umlView.setDiagramName(name) |
81 self.umlView.setDiagramName(name) |
83 |
82 |
84 def __buildModulesDict(self): |
83 def __buildModulesDict(self): |
85 """ |
84 """ |
86 Private method to build a dictionary of modules contained in the |
85 Private method to build a dictionary of modules contained in the |
87 package. |
86 package. |
88 |
87 |
89 @return dictionary of modules contained in the package |
88 @return dictionary of modules contained in the package |
90 @rtype dict |
89 @rtype dict |
91 """ |
90 """ |
92 import Utilities.ModuleParser |
91 import Utilities.ModuleParser |
93 extensions = ( |
92 |
94 Preferences.getPython("Python3Extensions") |
93 extensions = Preferences.getPython("Python3Extensions") |
95 ) |
|
96 moduleDict = {} |
94 moduleDict = {} |
97 modules = [] |
95 modules = [] |
98 for ext in ( |
96 for ext in Preferences.getPython("Python3Extensions"): |
99 Preferences.getPython("Python3Extensions") |
97 modules.extend( |
100 ): |
98 glob.glob(Utilities.normjoinpath(self.packagePath, "*{0}".format(ext))) |
101 modules.extend(glob.glob(Utilities.normjoinpath( |
99 ) |
102 self.packagePath, '*{0}'.format(ext)))) |
100 |
103 |
|
104 tot = len(modules) |
101 tot = len(modules) |
105 progress = EricProgressDialog( |
102 progress = EricProgressDialog( |
106 self.tr("Parsing modules..."), |
103 self.tr("Parsing modules..."), |
107 None, 0, tot, self.tr("%v/%m Modules"), self.parent()) |
104 None, |
|
105 0, |
|
106 tot, |
|
107 self.tr("%v/%m Modules"), |
|
108 self.parent(), |
|
109 ) |
108 progress.setWindowTitle(self.tr("Imports Diagramm")) |
110 progress.setWindowTitle(self.tr("Imports Diagramm")) |
109 try: |
111 try: |
110 progress.show() |
112 progress.show() |
111 QApplication.processEvents() |
113 QApplication.processEvents() |
112 |
114 |
113 now = time.monotonic() |
115 now = time.monotonic() |
114 for prog, module in enumerate(modules): |
116 for prog, module in enumerate(modules): |
115 progress.setValue(prog) |
117 progress.setValue(prog) |
116 if time.monotonic() - now > 0.01: |
118 if time.monotonic() - now > 0.01: |
117 QApplication.processEvents() |
119 QApplication.processEvents() |
118 now = time.monotonic() |
120 now = time.monotonic() |
119 try: |
121 try: |
120 mod = Utilities.ModuleParser.readModule( |
122 mod = Utilities.ModuleParser.readModule( |
121 module, extensions=extensions, caching=False) |
123 module, extensions=extensions, caching=False |
|
124 ) |
122 except ImportError: |
125 except ImportError: |
123 continue |
126 continue |
124 else: |
127 else: |
125 name = mod.name |
128 name = mod.name |
126 if name.startswith(self.package): |
129 if name.startswith(self.package): |
127 name = name[len(self.package) + 1:] |
130 name = name[len(self.package) + 1 :] |
128 moduleDict[name] = mod |
131 moduleDict[name] = mod |
129 finally: |
132 finally: |
130 progress.setValue(tot) |
133 progress.setValue(tot) |
131 progress.deleteLater() |
134 progress.deleteLater() |
132 return moduleDict |
135 return moduleDict |
133 |
136 |
134 def buildDiagram(self): |
137 def buildDiagram(self): |
135 """ |
138 """ |
136 Public method to build the modules shapes of the diagram. |
139 Public method to build the modules shapes of the diagram. |
137 """ |
140 """ |
138 initlist = glob.glob(os.path.join(self.packagePath, '__init__.*')) |
141 initlist = glob.glob(os.path.join(self.packagePath, "__init__.*")) |
139 if len(initlist) == 0: |
142 if len(initlist) == 0: |
140 ct = QGraphicsTextItem(None) |
143 ct = QGraphicsTextItem(None) |
141 ct.setHtml(self.buildErrorMessage( |
144 ct.setHtml( |
142 self.tr("The directory <b>'{0}'</b> is not a Python" |
145 self.buildErrorMessage( |
143 " package.").format(self.package) |
146 self.tr( |
144 )) |
147 "The directory <b>'{0}'</b> is not a Python" " package." |
|
148 ).format(self.package) |
|
149 ) |
|
150 ) |
145 self.scene.addItem(ct) |
151 self.scene.addItem(ct) |
146 return |
152 return |
147 |
153 |
148 self.__shapes = {} |
154 self.__shapes = {} |
149 |
155 |
150 modules = self.__buildModulesDict() |
156 modules = self.__buildModulesDict() |
151 externalMods = [] |
157 externalMods = [] |
152 packageList = self.shortPackage.split('.') |
158 packageList = self.shortPackage.split(".") |
153 packageListLen = len(packageList) |
159 packageListLen = len(packageList) |
154 for module in sorted(modules.keys()): |
160 for module in sorted(modules.keys()): |
155 impLst = [] |
161 impLst = [] |
156 for importName in modules[module].imports: |
162 for importName in modules[module].imports: |
157 n = ( |
163 n = ( |
158 importName[len(self.package) + 1:] |
164 importName[len(self.package) + 1 :] |
159 if importName.startswith(self.package) else |
165 if importName.startswith(self.package) |
160 importName |
166 else importName |
161 ) |
167 ) |
162 if importName in modules: |
168 if importName in modules: |
163 impLst.append(n) |
169 impLst.append(n) |
164 elif self.showExternalImports: |
170 elif self.showExternalImports: |
165 impLst.append(n) |
171 impLst.append(n) |
166 if n not in externalMods: |
172 if n not in externalMods: |
167 externalMods.append(n) |
173 externalMods.append(n) |
168 for importName in list(modules[module].from_imports.keys()): |
174 for importName in list(modules[module].from_imports.keys()): |
169 if importName.startswith('.'): |
175 if importName.startswith("."): |
170 dots = len(importName) - len(importName.lstrip('.')) |
176 dots = len(importName) - len(importName.lstrip(".")) |
171 if dots == 1: |
177 if dots == 1: |
172 n = importName[1:] |
178 n = importName[1:] |
173 importName = n |
179 importName = n |
174 else: |
180 else: |
175 if self.showExternalImports: |
181 if self.showExternalImports: |
176 n = '.'.join( |
182 n = ".".join( |
177 packageList[:packageListLen - dots + 1] + |
183 packageList[: packageListLen - dots + 1] |
178 [importName[dots:]]) |
184 + [importName[dots:]] |
|
185 ) |
179 else: |
186 else: |
180 n = importName |
187 n = importName |
181 elif importName.startswith(self.package): |
188 elif importName.startswith(self.package): |
182 n = importName[len(self.package) + 1:] |
189 n = importName[len(self.package) + 1 :] |
183 else: |
190 else: |
184 n = importName |
191 n = importName |
185 if importName in modules: |
192 if importName in modules: |
186 impLst.append(n) |
193 impLst.append(n) |
187 elif self.showExternalImports: |
194 elif self.showExternalImports: |
188 impLst.append(n) |
195 impLst.append(n) |
189 if n not in externalMods: |
196 if n not in externalMods: |
190 externalMods.append(n) |
197 externalMods.append(n) |
191 |
198 |
192 classNames = [] |
199 classNames = [] |
193 for class_ in list(modules[module].classes.keys()): |
200 for class_ in list(modules[module].classes.keys()): |
194 className = modules[module].classes[class_].name |
201 className = modules[module].classes[class_].name |
195 if className not in classNames: |
202 if className not in classNames: |
196 classNames.append(className) |
203 classNames.append(className) |
197 shape = self.__addModule(module, classNames, 0.0, 0.0) |
204 shape = self.__addModule(module, classNames, 0.0, 0.0) |
198 self.__shapes[module] = (shape, impLst) |
205 self.__shapes[module] = (shape, impLst) |
199 |
206 |
200 for module in externalMods: |
207 for module in externalMods: |
201 shape = self.__addModule(module, [], 0.0, 0.0) |
208 shape = self.__addModule(module, [], 0.0, 0.0) |
202 self.__shapes[module] = (shape, []) |
209 self.__shapes[module] = (shape, []) |
203 |
210 |
204 # build a list of routes |
211 # build a list of routes |
205 nodes = [] |
212 nodes = [] |
206 routes = [] |
213 routes = [] |
207 for module in self.__shapes: |
214 for module in self.__shapes: |
208 nodes.append(module) |
215 nodes.append(module) |
209 for rel in self.__shapes[module][1]: |
216 for rel in self.__shapes[module][1]: |
210 route = (module, rel) |
217 route = (module, rel) |
211 if route not in routes: |
218 if route not in routes: |
212 routes.append(route) |
219 routes.append(route) |
213 |
220 |
214 self.__arrangeNodes(nodes, routes[:]) |
221 self.__arrangeNodes(nodes, routes[:]) |
215 self.__createAssociations(routes) |
222 self.__createAssociations(routes) |
216 self.umlView.autoAdjustSceneSize(limit=True) |
223 self.umlView.autoAdjustSceneSize(limit=True) |
217 |
224 |
218 def __addModule(self, name, classes, x, y): |
225 def __addModule(self, name, classes, x, y): |
219 """ |
226 """ |
220 Private method to add a module to the diagram. |
227 Private method to add a module to the diagram. |
221 |
228 |
222 @param name module name to be shown |
229 @param name module name to be shown |
223 @type str |
230 @type str |
224 @param classes list of class names contained in the module |
231 @param classes list of class names contained in the module |
225 @type list of str |
232 @type list of str |
226 @param x x-coordinate |
233 @param x x-coordinate |
229 @type float |
236 @type float |
230 @return reference to the imports item |
237 @return reference to the imports item |
231 @rtype ModuleItem |
238 @rtype ModuleItem |
232 """ |
239 """ |
233 from .ModuleItem import ModuleItem, ModuleModel |
240 from .ModuleItem import ModuleItem, ModuleModel |
|
241 |
234 classes.sort() |
242 classes.sort() |
235 impM = ModuleModel(name, classes) |
243 impM = ModuleModel(name, classes) |
236 impW = ModuleItem(impM, x, y, scene=self.scene, |
244 impW = ModuleItem( |
237 colors=self.umlView.getDrawingColors()) |
245 impM, x, y, scene=self.scene, colors=self.umlView.getDrawingColors() |
|
246 ) |
238 impW.setId(self.umlView.getItemId()) |
247 impW.setId(self.umlView.getItemId()) |
239 return impW |
248 return impW |
240 |
249 |
241 def __arrangeNodes(self, nodes, routes, whiteSpaceFactor=1.2): |
250 def __arrangeNodes(self, nodes, routes, whiteSpaceFactor=1.2): |
242 """ |
251 """ |
243 Private method to arrange the shapes on the canvas. |
252 Private method to arrange the shapes on the canvas. |
244 |
253 |
245 The algorithm is borrowed from Boa Constructor. |
254 The algorithm is borrowed from Boa Constructor. |
246 |
255 |
247 @param nodes list of nodes to arrange |
256 @param nodes list of nodes to arrange |
248 @type list of str |
257 @type list of str |
249 @param routes list of routes |
258 @param routes list of routes |
250 @type list of tuple of (str, str) |
259 @type list of tuple of (str, str) |
251 @param whiteSpaceFactor factor to increase whitespace between |
260 @param whiteSpaceFactor factor to increase whitespace between |
252 items |
261 items |
253 @type float |
262 @type float |
254 """ |
263 """ |
255 from . import GraphicsUtilities |
264 from . import GraphicsUtilities |
|
265 |
256 generations = GraphicsUtilities.sort(nodes, routes) |
266 generations = GraphicsUtilities.sort(nodes, routes) |
257 |
267 |
258 # calculate width and height of all elements |
268 # calculate width and height of all elements |
259 sizes = [] |
269 sizes = [] |
260 for generation in generations: |
270 for generation in generations: |
261 sizes.append([]) |
271 sizes.append([]) |
262 for child in generation: |
272 for child in generation: |
263 sizes[-1].append( |
273 sizes[-1].append(self.__shapes[child][0].sceneBoundingRect()) |
264 self.__shapes[child][0].sceneBoundingRect()) |
274 |
265 |
|
266 # calculate total width and total height |
275 # calculate total width and total height |
267 width = 0 |
276 width = 0 |
268 height = 0 |
277 height = 0 |
269 widths = [] |
278 widths = [] |
270 heights = [] |
279 heights = [] |
271 for generation in sizes: |
280 for generation in sizes: |
272 currentWidth = 0 |
281 currentWidth = 0 |
273 currentHeight = 0 |
282 currentHeight = 0 |
274 |
283 |
275 for rect in generation: |
284 for rect in generation: |
276 if rect.height() > currentHeight: |
285 if rect.height() > currentHeight: |
277 currentHeight = rect.height() |
286 currentHeight = rect.height() |
278 currentWidth += rect.width() |
287 currentWidth += rect.width() |
279 |
288 |
280 # update totals |
289 # update totals |
281 if currentWidth > width: |
290 if currentWidth > width: |
282 width = currentWidth |
291 width = currentWidth |
283 height += currentHeight |
292 height += currentHeight |
284 |
293 |
285 # store generation info |
294 # store generation info |
286 widths.append(currentWidth) |
295 widths.append(currentWidth) |
287 heights.append(currentHeight) |
296 heights.append(currentHeight) |
288 |
297 |
289 # add in some whitespace |
298 # add in some whitespace |
290 width *= whiteSpaceFactor |
299 width *= whiteSpaceFactor |
291 height = height * whiteSpaceFactor - 20 |
300 height = height * whiteSpaceFactor - 20 |
292 verticalWhiteSpace = 40.0 |
301 verticalWhiteSpace = 40.0 |
293 |
302 |
294 sceneRect = self.umlView.sceneRect() |
303 sceneRect = self.umlView.sceneRect() |
295 width += 50.0 |
304 width += 50.0 |
296 height += 50.0 |
305 height += 50.0 |
297 swidth = sceneRect.width() if width < sceneRect.width() else width |
306 swidth = sceneRect.width() if width < sceneRect.width() else width |
298 sheight = sceneRect.height() if height < sceneRect.height() else height |
307 sheight = sceneRect.height() if height < sceneRect.height() else height |
299 self.umlView.setSceneSize(swidth, sheight) |
308 self.umlView.setSceneSize(swidth, sheight) |
300 |
309 |
301 # distribute each generation across the width and the |
310 # distribute each generation across the width and the |
302 # generations across height |
311 # generations across height |
303 y = 10.0 |
312 y = 10.0 |
304 for currentWidth, currentHeight, generation in ( |
313 for currentWidth, currentHeight, generation in zip( |
305 zip(reversed(widths), reversed(heights), reversed(generations)) |
314 reversed(widths), reversed(heights), reversed(generations) |
306 ): |
315 ): |
307 x = 10.0 |
316 x = 10.0 |
308 # whiteSpace is the space between any two elements |
317 # whiteSpace is the space between any two elements |
309 whiteSpace = ( |
318 whiteSpace = (width - currentWidth - 20) / (len(generation) - 1.0 or 2.0) |
310 (width - currentWidth - 20) / |
|
311 (len(generation) - 1.0 or 2.0) |
|
312 ) |
|
313 for name in generation: |
319 for name in generation: |
314 shape = self.__shapes[name][0] |
320 shape = self.__shapes[name][0] |
315 shape.setPos(x, y) |
321 shape.setPos(x, y) |
316 rect = shape.sceneBoundingRect() |
322 rect = shape.sceneBoundingRect() |
317 x = x + rect.width() + whiteSpace |
323 x = x + rect.width() + whiteSpace |
318 y = y + currentHeight + verticalWhiteSpace |
324 y = y + currentHeight + verticalWhiteSpace |
319 |
325 |
320 def __createAssociations(self, routes): |
326 def __createAssociations(self, routes): |
321 """ |
327 """ |
322 Private method to generate the associations between the module shapes. |
328 Private method to generate the associations between the module shapes. |
323 |
329 |
324 @param routes list of associations |
330 @param routes list of associations |
325 @type list of tuple of (str, str) |
331 @type list of tuple of (str, str) |
326 """ |
332 """ |
327 from .AssociationItem import AssociationItem, AssociationType |
333 from .AssociationItem import AssociationItem, AssociationType |
|
334 |
328 for route in routes: |
335 for route in routes: |
329 assoc = AssociationItem( |
336 assoc = AssociationItem( |
330 self.__shapes[route[0]][0], |
337 self.__shapes[route[0]][0], |
331 self.__shapes[route[1]][0], |
338 self.__shapes[route[1]][0], |
332 AssociationType.IMPORTS, |
339 AssociationType.IMPORTS, |
333 colors=self.umlView.getDrawingColors()) |
340 colors=self.umlView.getDrawingColors(), |
|
341 ) |
334 self.scene.addItem(assoc) |
342 self.scene.addItem(assoc) |
335 |
343 |
336 def parsePersistenceData(self, version, data): |
344 def parsePersistenceData(self, version, data): |
337 """ |
345 """ |
338 Public method to parse persisted data. |
346 Public method to parse persisted data. |
339 |
347 |
340 @param version version of the data |
348 @param version version of the data |
341 @type str |
349 @type str |
342 @param data persisted data to be parsed |
350 @param data persisted data to be parsed |
343 @type str |
351 @type str |
344 @return flag indicating success |
352 @return flag indicating success |
345 @rtype bool |
353 @rtype bool |
346 """ |
354 """ |
347 parts = data.split(", ") |
355 parts = data.split(", ") |
348 if ( |
356 if ( |
349 len(parts) != 2 or |
357 len(parts) != 2 |
350 not parts[0].startswith("package=") or |
358 or not parts[0].startswith("package=") |
351 not parts[1].startswith("show_external=") |
359 or not parts[1].startswith("show_external=") |
352 ): |
360 ): |
353 return False |
361 return False |
354 |
362 |
355 self.packagePath = parts[0].split("=", 1)[1].strip() |
363 self.packagePath = parts[0].split("=", 1)[1].strip() |
356 self.showExternalImports = Utilities.toBool( |
364 self.showExternalImports = Utilities.toBool(parts[1].split("=", 1)[1].strip()) |
357 parts[1].split("=", 1)[1].strip()) |
365 |
358 |
|
359 self.initialize() |
366 self.initialize() |
360 |
367 |
361 return True |
368 return True |
362 |
369 |
363 def toDict(self): |
370 def toDict(self): |
364 """ |
371 """ |
365 Public method to collect data to be persisted. |
372 Public method to collect data to be persisted. |
366 |
373 |
367 @return dictionary containing data to be persisted |
374 @return dictionary containing data to be persisted |
368 @rtype dict |
375 @rtype dict |
369 """ |
376 """ |
370 data = { |
377 data = { |
371 "project_name": self.project.getProjectName(), |
378 "project_name": self.project.getProjectName(), |
372 "show_external": self.showExternalImports, |
379 "show_external": self.showExternalImports, |
373 } |
380 } |
374 |
381 |
375 data["package"] = ( |
382 data["package"] = ( |
376 Utilities.fromNativeSeparators(self.__relPackagePath) |
383 Utilities.fromNativeSeparators(self.__relPackagePath) |
377 if self.__relPackagePath else |
384 if self.__relPackagePath |
378 Utilities.fromNativeSeparators(self.packagePath) |
385 else Utilities.fromNativeSeparators(self.packagePath) |
379 ) |
386 ) |
380 |
387 |
381 return data |
388 return data |
382 |
389 |
383 def fromDict(self, version, data): |
390 def fromDict(self, version, data): |
384 """ |
391 """ |
385 Public method to populate the class with data persisted by 'toDict()'. |
392 Public method to populate the class with data persisted by 'toDict()'. |
386 |
393 |
387 @param version version of the data |
394 @param version version of the data |
388 @type str |
395 @type str |
389 @param data dictionary containing the persisted data |
396 @param data dictionary containing the persisted data |
390 @type dict |
397 @type dict |
391 @return tuple containing a flag indicating success and an info |
398 @return tuple containing a flag indicating success and an info |
392 message in case the diagram belongs to a different project |
399 message in case the diagram belongs to a different project |
393 @rtype tuple of (bool, str) |
400 @rtype tuple of (bool, str) |
394 """ |
401 """ |
395 try: |
402 try: |
396 self.showExternalImports = data["show_external"] |
403 self.showExternalImports = data["show_external"] |
397 |
404 |
398 packagePath = Utilities.toNativeSeparators(data["package"]) |
405 packagePath = Utilities.toNativeSeparators(data["package"]) |
399 if os.path.isabs(packagePath): |
406 if os.path.isabs(packagePath): |
400 self.packagePath = packagePath |
407 self.packagePath = packagePath |
401 self.__relPackagePath = "" |
408 self.__relPackagePath = "" |
402 else: |
409 else: |