119 Public method to build the modules shapes of the diagram. |
131 Public method to build the modules shapes of the diagram. |
120 """ |
132 """ |
121 initlist = glob.glob(os.path.join(self.packagePath, '__init__.*')) |
133 initlist = glob.glob(os.path.join(self.packagePath, '__init__.*')) |
122 if len(initlist) == 0: |
134 if len(initlist) == 0: |
123 ct = QGraphicsTextItem(None) |
135 ct = QGraphicsTextItem(None) |
124 ct.setHtml( |
136 ct.setHtml(self.buildErrorMessage( |
125 self.tr( |
137 self.tr("The directory <b>'{0}'</b> is not a Python" |
126 "The directory <b>'{0}'</b> is not a Python package.") |
138 " package.").format(self.package) |
127 .format(self.package)) |
139 )) |
128 self.scene.addItem(ct) |
140 self.scene.addItem(ct) |
129 return |
141 return |
130 |
142 |
131 shapes = {} |
143 self.__shapes = {} |
132 p = 10 |
|
133 y = 10 |
|
134 maxHeight = 0 |
|
135 sceneRect = self.umlView.sceneRect() |
|
136 |
144 |
137 modules = self.__buildModulesDict() |
145 modules = self.__buildModulesDict() |
138 sortedkeys = sorted(modules.keys()) |
|
139 externalMods = [] |
146 externalMods = [] |
140 packageList = self.shortPackage.split('.') |
147 packageList = self.shortPackage.split('.') |
141 packageListLen = len(packageList) |
148 packageListLen = len(packageList) |
142 for module in sortedkeys: |
149 for module in sorted(modules.keys()): |
143 impLst = [] |
150 impLst = [] |
144 for i in modules[module].imports: |
151 for importName in modules[module].imports: |
145 n = (i[len(self.package) + 1:] |
152 n = ( |
146 if i.startswith(self.package) else i) |
153 importName[len(self.package) + 1:] |
147 if i in modules: |
154 if importName.startswith(self.package) else |
|
155 importName |
|
156 ) |
|
157 if importName in modules: |
148 impLst.append(n) |
158 impLst.append(n) |
149 elif self.showExternalImports: |
159 elif self.showExternalImports: |
150 impLst.append(n) |
160 impLst.append(n) |
151 if n not in externalMods: |
161 if n not in externalMods: |
152 externalMods.append(n) |
162 externalMods.append(n) |
153 for i in list(modules[module].from_imports.keys()): |
163 for importName in list(modules[module].from_imports.keys()): |
154 if i.startswith('.'): |
164 if importName.startswith('.'): |
155 dots = len(i) - len(i.lstrip('.')) |
165 dots = len(importName) - len(importName.lstrip('.')) |
156 if dots == 1: |
166 if dots == 1: |
157 n = i[1:] |
167 n = importName[1:] |
158 i = n |
168 importName = n |
159 else: |
169 else: |
160 if self.showExternalImports: |
170 if self.showExternalImports: |
161 n = '.'.join( |
171 n = '.'.join( |
162 packageList[:packageListLen - dots + 1] + |
172 packageList[:packageListLen - dots + 1] + |
163 [i[dots:]]) |
173 [importName[dots:]]) |
164 else: |
174 else: |
165 n = i |
175 n = importName |
166 elif i.startswith(self.package): |
176 elif importName.startswith(self.package): |
167 n = i[len(self.package) + 1:] |
177 n = importName[len(self.package) + 1:] |
168 else: |
178 else: |
169 n = i |
179 n = importName |
170 if i in modules: |
180 if importName in modules: |
171 impLst.append(n) |
181 impLst.append(n) |
172 elif self.showExternalImports: |
182 elif self.showExternalImports: |
173 impLst.append(n) |
183 impLst.append(n) |
174 if n not in externalMods: |
184 if n not in externalMods: |
175 externalMods.append(n) |
185 externalMods.append(n) |
|
186 |
176 classNames = [] |
187 classNames = [] |
177 for cls in list(modules[module].classes.keys()): |
188 for class_ in list(modules[module].classes.keys()): |
178 className = modules[module].classes[cls].name |
189 className = modules[module].classes[class_].name |
179 if className not in classNames: |
190 if className not in classNames: |
180 classNames.append(className) |
191 classNames.append(className) |
181 shape = self.__addModule(module, classNames, 0.0, 0.0) |
192 shape = self.__addModule(module, classNames, 0.0, 0.0) |
182 shapeRect = shape.sceneBoundingRect() |
193 self.__shapes[module] = (shape, impLst) |
183 shapes[module] = (shape, impLst) |
|
184 pn = p + shapeRect.width() + 10 |
|
185 maxHeight = max(maxHeight, shapeRect.height()) |
|
186 if pn > sceneRect.width(): |
|
187 p = 10 |
|
188 y += maxHeight + 10 |
|
189 maxHeight = shapeRect.height() |
|
190 shape.setPos(p, y) |
|
191 p += shapeRect.width() + 10 |
|
192 else: |
|
193 shape.setPos(p, y) |
|
194 p = pn |
|
195 |
194 |
196 for module in externalMods: |
195 for module in externalMods: |
197 shape = self.__addModule(module, [], 0.0, 0.0) |
196 shape = self.__addModule(module, [], 0.0, 0.0) |
198 shapeRect = shape.sceneBoundingRect() |
197 self.__shapes[module] = (shape, []) |
199 shapes[module] = (shape, []) |
198 |
200 pn = p + shapeRect.width() + 10 |
199 # build a list of routes |
201 maxHeight = max(maxHeight, shapeRect.height()) |
200 nodes = [] |
202 if pn > sceneRect.width(): |
201 routes = [] |
203 p = 10 |
202 for module in self.__shapes: |
204 y += maxHeight + 10 |
203 nodes.append(module) |
205 maxHeight = shapeRect.height() |
204 for rel in self.__shapes[module][1]: |
206 shape.setPos(p, y) |
205 route = (module, rel) |
207 p += shapeRect.width() + 10 |
206 if route not in routes: |
208 else: |
207 routes.append(route) |
209 shape.setPos(p, y) |
208 |
210 p = pn |
209 self.__arrangeNodes(nodes, routes[:]) |
211 |
210 self.__createAssociations(routes) |
212 rect = self.umlView._getDiagramRect(10) |
|
213 sceneRect = self.umlView.sceneRect() |
|
214 if rect.width() > sceneRect.width(): |
|
215 sceneRect.setWidth(rect.width()) |
|
216 if rect.height() > sceneRect.height(): |
|
217 sceneRect.setHeight(rect.height()) |
|
218 self.umlView.setSceneSize(sceneRect.width(), sceneRect.height()) |
|
219 |
|
220 self.__createAssociations(shapes) |
|
221 self.umlView.autoAdjustSceneSize(limit=True) |
211 self.umlView.autoAdjustSceneSize(limit=True) |
222 |
212 |
223 def __addModule(self, name, classes, x, y): |
213 def __addModule(self, name, classes, x, y): |
224 """ |
214 """ |
225 Private method to add a module to the diagram. |
215 Private method to add a module to the diagram. |
226 |
216 |
227 @param name module name to be shown (string) |
217 @param name module name to be shown |
|
218 @type str |
228 @param classes list of class names contained in the module |
219 @param classes list of class names contained in the module |
229 (list of strings) |
220 @type list of str |
230 @param x x-coordinate (float) |
221 @param x x-coordinate |
231 @param y y-coordinate (float) |
222 @type float |
232 @return reference to the imports item (ModuleItem) |
223 @param y y-coordinate |
|
224 @type float |
|
225 @return reference to the imports item |
|
226 @rtype ModuleItem |
233 """ |
227 """ |
234 from .ModuleItem import ModuleItem, ModuleModel |
228 from .ModuleItem import ModuleItem, ModuleModel |
235 classes.sort() |
229 classes.sort() |
236 impM = ModuleModel(name, classes) |
230 impM = ModuleModel(name, classes) |
237 impW = ModuleItem(impM, x, y, scene=self.scene, |
231 impW = ModuleItem(impM, x, y, scene=self.scene, |
238 colors=self.umlView.getDrawingColors()) |
232 colors=self.umlView.getDrawingColors()) |
239 impW.setId(self.umlView.getItemId()) |
233 impW.setId(self.umlView.getItemId()) |
240 return impW |
234 return impW |
241 |
235 |
242 def __createAssociations(self, shapes): |
236 def __arrangeNodes(self, nodes, routes, whiteSpaceFactor=1.2): |
|
237 """ |
|
238 Private method to arrange the shapes on the canvas. |
|
239 |
|
240 The algorithm is borrowed from Boa Constructor. |
|
241 |
|
242 @param nodes list of nodes to arrange |
|
243 @type list of str |
|
244 @param routes list of routes |
|
245 @type list of tuple of (str, str) |
|
246 @param whiteSpaceFactor factor to increase whitespace between |
|
247 items |
|
248 @type float |
|
249 """ |
|
250 from . import GraphicsUtilities |
|
251 generations = GraphicsUtilities.sort(nodes, routes) |
|
252 |
|
253 # calculate width and height of all elements |
|
254 sizes = [] |
|
255 for generation in generations: |
|
256 sizes.append([]) |
|
257 for child in generation: |
|
258 sizes[-1].append( |
|
259 self.__shapes[child][0].sceneBoundingRect()) |
|
260 |
|
261 # calculate total width and total height |
|
262 width = 0 |
|
263 height = 0 |
|
264 widths = [] |
|
265 heights = [] |
|
266 for generation in sizes: |
|
267 currentWidth = 0 |
|
268 currentHeight = 0 |
|
269 |
|
270 for rect in generation: |
|
271 if rect.height() > currentHeight: |
|
272 currentHeight = rect.height() |
|
273 currentWidth += rect.width() |
|
274 |
|
275 # update totals |
|
276 if currentWidth > width: |
|
277 width = currentWidth |
|
278 height += currentHeight |
|
279 |
|
280 # store generation info |
|
281 widths.append(currentWidth) |
|
282 heights.append(currentHeight) |
|
283 |
|
284 # add in some whitespace |
|
285 width *= whiteSpaceFactor |
|
286 height = height * whiteSpaceFactor - 20 |
|
287 verticalWhiteSpace = 40.0 |
|
288 |
|
289 sceneRect = self.umlView.sceneRect() |
|
290 width += 50.0 |
|
291 height += 50.0 |
|
292 swidth = sceneRect.width() if width < sceneRect.width() else width |
|
293 sheight = sceneRect.height() if height < sceneRect.height() else height |
|
294 self.umlView.setSceneSize(swidth, sheight) |
|
295 |
|
296 # distribute each generation across the width and the |
|
297 # generations across height |
|
298 y = 10.0 |
|
299 for currentWidth, currentHeight, generation in ( |
|
300 zip(reversed(widths), reversed(heights), reversed(generations)) |
|
301 ): |
|
302 x = 10.0 |
|
303 # whiteSpace is the space between any two elements |
|
304 whiteSpace = ( |
|
305 (width - currentWidth - 20) / |
|
306 (len(generation) - 1.0 or 2.0) |
|
307 ) |
|
308 for name in generation: |
|
309 shape = self.__shapes[name][0] |
|
310 shape.setPos(x, y) |
|
311 rect = shape.sceneBoundingRect() |
|
312 x = x + rect.width() + whiteSpace |
|
313 y = y + currentHeight + verticalWhiteSpace |
|
314 |
|
315 def __createAssociations(self, routes): |
243 """ |
316 """ |
244 Private method to generate the associations between the module shapes. |
317 Private method to generate the associations between the module shapes. |
245 |
318 |
246 @param shapes list of shapes |
319 @param routes list of associations |
|
320 @type list of tuple of (str, str) |
247 """ |
321 """ |
248 from .AssociationItem import AssociationItem, AssociationType |
322 from .AssociationItem import AssociationItem, AssociationType |
249 for module in list(shapes.keys()): |
323 for route in routes: |
250 for rel in shapes[module][1]: |
324 assoc = AssociationItem( |
251 assoc = AssociationItem( |
325 self.__shapes[route[0]][0], |
252 shapes[module][0], shapes[rel][0], |
326 self.__shapes[route[1]][0], |
253 AssociationType.IMPORTS, |
327 AssociationType.IMPORTS, |
254 colors=self.umlView.getDrawingColors()) |
328 colors=self.umlView.getDrawingColors()) |
255 self.scene.addItem(assoc) |
329 self.scene.addItem(assoc) |
256 |
330 |
257 def getPersistenceData(self): |
331 def getPersistenceData(self): |
258 """ |
332 """ |
259 Public method to get a string for data to be persisted. |
333 Public method to get a string for data to be persisted. |
260 |
334 |
261 @return persisted data string (string) |
335 @return persisted data string |
|
336 @rtype str |
262 """ |
337 """ |
263 return "package={0}, show_external={1}".format( |
338 return "package={0}, show_external={1}".format( |
264 self.packagePath, self.showExternalImports) |
339 self.packagePath, self.showExternalImports) |
265 |
340 |
266 def parsePersistenceData(self, version, data): |
341 def parsePersistenceData(self, version, data): |
267 """ |
342 """ |
268 Public method to parse persisted data. |
343 Public method to parse persisted data. |
269 |
344 |
270 @param version version of the data (string) |
345 @param version version of the data |
271 @param data persisted data to be parsed (string) |
346 @type str |
272 @return flag indicating success (boolean) |
347 @param data persisted data to be parsed |
|
348 @type str |
|
349 @return flag indicating success |
|
350 @rtype bool |
273 """ |
351 """ |
274 parts = data.split(", ") |
352 parts = data.split(", ") |
275 if ( |
353 if ( |
276 len(parts) != 2 or |
354 len(parts) != 2 or |
277 not parts[0].startswith("package=") or |
355 not parts[0].startswith("package=") or |