eric6/Graphics/ImportsDiagramBuilder.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog showing an imports diagram of a package.
8 """
9
10 from __future__ import unicode_literals
11
12 import glob
13 import os
14
15 from PyQt5.QtWidgets import QApplication, QGraphicsTextItem
16
17 from E5Gui.E5ProgressDialog import E5ProgressDialog
18
19 from .UMLDiagramBuilder import UMLDiagramBuilder
20
21 import Utilities
22 import Preferences
23
24
25 class ImportsDiagramBuilder(UMLDiagramBuilder):
26 """
27 Class implementing a builder for imports diagrams of a package.
28
29 Note: Only package internal imports are shown in order to maintain
30 some readability.
31 """
32 def __init__(self, dialog, view, project, package,
33 showExternalImports=False):
34 """
35 Constructor
36
37 @param dialog reference to the UML dialog (UMLDialog)
38 @param view reference to the view object (UMLGraphicsView)
39 @param project reference to the project object (Project)
40 @param package name of a python package to show the import
41 relationships (string)
42 @keyparam showExternalImports flag indicating to show exports from
43 outside the package (boolean)
44 """
45 super(ImportsDiagramBuilder, self).__init__(dialog, view, project)
46 self.setObjectName("ImportsDiagram")
47
48 self.showExternalImports = showExternalImports
49 self.packagePath = Utilities.normabspath(package)
50
51 def initialize(self):
52 """
53 Public method to initialize the object.
54 """
55 self.package = os.path.splitdrive(self.packagePath)[1].replace(
56 os.sep, '.')[1:]
57 hasInit = True
58 ppath = self.packagePath
59 while hasInit:
60 ppath = os.path.dirname(ppath)
61 hasInit = len(glob.glob(os.path.join(ppath, '__init__.*'))) > 0
62 self.shortPackage = self.packagePath.replace(ppath, '').replace(
63 os.sep, '.')[1:]
64
65 pname = self.project.getProjectName()
66 if pname:
67 name = self.tr("Imports Diagramm {0}: {1}").format(
68 pname, self.project.getRelativePath(self.packagePath))
69 else:
70 name = self.tr("Imports Diagramm: {0}").format(
71 self.packagePath)
72 self.umlView.setDiagramName(name)
73
74 def __buildModulesDict(self):
75 """
76 Private method to build a dictionary of modules contained in the
77 package.
78
79 @return dictionary of modules contained in the package.
80 """
81 import Utilities.ModuleParser
82 extensions = Preferences.getPython("PythonExtensions") + \
83 Preferences.getPython("Python3Extensions")
84 moduleDict = {}
85 modules = []
86 for ext in Preferences.getPython("PythonExtensions") + \
87 Preferences.getPython("Python3Extensions"):
88 modules.extend(glob.glob(Utilities.normjoinpath(
89 self.packagePath, '*{0}'.format(ext))))
90
91 tot = len(modules)
92 progress = E5ProgressDialog(
93 self.tr("Parsing modules..."),
94 None, 0, tot, self.tr("%v/%m Modules"), self.parent())
95 progress.setWindowTitle(self.tr("Imports Diagramm"))
96 try:
97 prog = 0
98 progress.show()
99 QApplication.processEvents()
100 for module in modules:
101 progress.setValue(prog)
102 QApplication.processEvents()
103 prog = prog + 1
104 try:
105 mod = Utilities.ModuleParser.readModule(
106 module, extensions=extensions, caching=False)
107 except ImportError:
108 continue
109 else:
110 name = mod.name
111 if name.startswith(self.package):
112 name = name[len(self.package) + 1:]
113 moduleDict[name] = mod
114 finally:
115 progress.setValue(tot)
116 progress.deleteLater()
117 return moduleDict
118
119 def buildDiagram(self):
120 """
121 Public method to build the modules shapes of the diagram.
122 """
123 initlist = glob.glob(os.path.join(self.packagePath, '__init__.*'))
124 if len(initlist) == 0:
125 ct = QGraphicsTextItem(None)
126 ct.setHtml(
127 self.tr(
128 "The directory <b>'{0}'</b> is not a Python package.")
129 .format(self.package))
130 self.scene.addItem(ct)
131 return
132
133 shapes = {}
134 p = 10
135 y = 10
136 maxHeight = 0
137 sceneRect = self.umlView.sceneRect()
138
139 modules = self.__buildModulesDict()
140 sortedkeys = sorted(modules.keys())
141 externalMods = []
142 packageList = self.shortPackage.split('.')
143 packageListLen = len(packageList)
144 for module in sortedkeys:
145 impLst = []
146 for i in modules[module].imports:
147 if i.startswith(self.package):
148 n = i[len(self.package) + 1:]
149 else:
150 n = i
151 if i in modules:
152 impLst.append(n)
153 elif self.showExternalImports:
154 impLst.append(n)
155 if n not in externalMods:
156 externalMods.append(n)
157 for i in list(modules[module].from_imports.keys()):
158 if i.startswith('.'):
159 dots = len(i) - len(i.lstrip('.'))
160 if dots == 1:
161 n = i[1:]
162 i = n
163 else:
164 if self.showExternalImports:
165 n = '.'.join(
166 packageList[:packageListLen - dots + 1] +
167 [i[dots:]])
168 else:
169 n = i
170 elif i.startswith(self.package):
171 n = i[len(self.package) + 1:]
172 else:
173 n = i
174 if i in modules:
175 impLst.append(n)
176 elif self.showExternalImports:
177 impLst.append(n)
178 if n not in externalMods:
179 externalMods.append(n)
180 classNames = []
181 for cls in list(modules[module].classes.keys()):
182 className = modules[module].classes[cls].name
183 if className not in classNames:
184 classNames.append(className)
185 shape = self.__addModule(module, classNames, 0.0, 0.0)
186 shapeRect = shape.sceneBoundingRect()
187 shapes[module] = (shape, impLst)
188 pn = p + shapeRect.width() + 10
189 maxHeight = max(maxHeight, shapeRect.height())
190 if pn > sceneRect.width():
191 p = 10
192 y += maxHeight + 10
193 maxHeight = shapeRect.height()
194 shape.setPos(p, y)
195 p += shapeRect.width() + 10
196 else:
197 shape.setPos(p, y)
198 p = pn
199
200 for module in externalMods:
201 shape = self.__addModule(module, [], 0.0, 0.0)
202 shapeRect = shape.sceneBoundingRect()
203 shapes[module] = (shape, [])
204 pn = p + shapeRect.width() + 10
205 maxHeight = max(maxHeight, shapeRect.height())
206 if pn > sceneRect.width():
207 p = 10
208 y += maxHeight + 10
209 maxHeight = shapeRect.height()
210 shape.setPos(p, y)
211 p += shapeRect.width() + 10
212 else:
213 shape.setPos(p, y)
214 p = pn
215
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)
226
227 def __addModule(self, name, classes, x, y):
228 """
229 Private method to add a module to the diagram.
230
231 @param name module name to be shown (string)
232 @param classes list of class names contained in the module
233 (list of strings)
234 @param x x-coordinate (float)
235 @param y y-coordinate (float)
236 @return reference to the imports item (ModuleItem)
237 """
238 from .ModuleItem import ModuleItem, ModuleModel
239 classes.sort()
240 impM = ModuleModel(name, classes)
241 impW = ModuleItem(impM, x, y, scene=self.scene)
242 impW.setId(self.umlView.getItemId())
243 return impW
244
245 def __createAssociations(self, shapes):
246 """
247 Private method to generate the associations between the module shapes.
248
249 @param shapes list of shapes
250 """
251 from .AssociationItem import AssociationItem, Imports
252 for module in list(shapes.keys()):
253 for rel in shapes[module][1]:
254 assoc = AssociationItem(
255 shapes[module][0], shapes[rel][0],
256 Imports)
257 self.scene.addItem(assoc)
258
259 def getPersistenceData(self):
260 """
261 Public method to get a string for data to be persisted.
262
263 @return persisted data string (string)
264 """
265 return "package={0}, show_external={1}".format(
266 self.packagePath, self.showExternalImports)
267
268 def parsePersistenceData(self, version, data):
269 """
270 Public method to parse persisted data.
271
272 @param version version of the data (string)
273 @param data persisted data to be parsed (string)
274 @return flag indicating success (boolean)
275 """
276 parts = data.split(", ")
277 if len(parts) != 2 or \
278 not parts[0].startswith("package=") or \
279 not parts[1].startswith("show_external="):
280 return False
281
282 self.packagePath = parts[0].split("=", 1)[1].strip()
283 self.showExternalImports = Utilities.toBool(
284 parts[1].split("=", 1)[1].strip())
285
286 self.initialize()
287
288 return True

eric ide

mercurial