eric6/Graphics/ApplicationDiagramBuilder.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 the application.
8 """
9
10 from __future__ import unicode_literals
11
12 import os
13 import glob
14
15 from PyQt5.QtWidgets import QApplication
16
17 from E5Gui import E5MessageBox
18 from E5Gui.E5ProgressDialog import E5ProgressDialog
19
20 from .UMLDiagramBuilder import UMLDiagramBuilder
21
22 import Utilities
23 import Preferences
24
25
26 class ApplicationDiagramBuilder(UMLDiagramBuilder):
27 """
28 Class implementing a builder for imports diagrams of the application.
29 """
30 def __init__(self, dialog, view, project, noModules=False):
31 """
32 Constructor
33
34 @param dialog reference to the UML dialog (UMLDialog)
35 @param view reference to the view object (UMLGraphicsView)
36 @param project reference to the project object (Project)
37 @keyparam noModules flag indicating, that no module names should be
38 shown (boolean)
39 """
40 super(ApplicationDiagramBuilder, self).__init__(dialog, view, project)
41 self.setObjectName("ApplicationDiagram")
42
43 self.noModules = noModules
44
45 self.umlView.setDiagramName(
46 self.tr("Application Diagram {0}").format(
47 self.project.getProjectName()))
48
49 def __buildModulesDict(self):
50 """
51 Private method to build a dictionary of modules contained in the
52 application.
53
54 @return dictionary of modules contained in the application.
55 """
56 import Utilities.ModuleParser
57 extensions = Preferences.getPython("PythonExtensions") + \
58 Preferences.getPython("Python3Extensions") + ['.rb']
59 moduleDict = {}
60 mods = self.project.pdata["SOURCES"]
61 modules = []
62 for module in mods:
63 modules.append(Utilities.normabsjoinpath(
64 self.project.ppath, module))
65 tot = len(modules)
66 progress = E5ProgressDialog(
67 self.tr("Parsing modules..."),
68 None, 0, tot, self.tr("%v/%m Modules"), self.parent())
69 progress.setWindowTitle(self.tr("Application Diagram"))
70 try:
71 prog = 0
72 progress.show()
73 QApplication.processEvents()
74
75 for module in modules:
76 progress.setValue(prog)
77 QApplication.processEvents()
78 prog += 1
79 if module.endswith("__init__.py"):
80 continue
81 try:
82 mod = Utilities.ModuleParser.readModule(
83 module, extensions=extensions, caching=False)
84 except ImportError:
85 continue
86 else:
87 name = mod.name
88 moduleDict[name] = mod
89 finally:
90 progress.setValue(tot)
91 progress.deleteLater()
92 return moduleDict
93
94 def buildDiagram(self):
95 """
96 Public method to build the packages shapes of the diagram.
97 """
98 project = os.path.splitdrive(self.project.getProjectPath())[1]\
99 .replace(os.sep, '.')[1:]
100 packages = {}
101 shapes = {}
102 p = 10
103 y = 10
104 maxHeight = 0
105 sceneRect = self.umlView.sceneRect()
106
107 modules = self.__buildModulesDict()
108 sortedkeys = sorted(modules.keys())
109
110 # step 1: build a dictionary of packages
111 for module in sortedkeys:
112 li = module.split('.')
113 package = '.'.join(li[:-1])
114 if package in packages:
115 packages[package][0].append(li[-1])
116 else:
117 packages[package] = ([li[-1]], [])
118
119 # step 2: assign modules to dictionaries and update import relationship
120 for module in sortedkeys:
121 li = module.split('.')
122 package = '.'.join(li[:-1])
123 impLst = []
124 for i in modules[module].imports:
125 if i in modules:
126 impLst.append(i)
127 else:
128 if i.find('.') == -1:
129 n = "{0}.{1}".format(modules[module].package, i)
130 if n in modules:
131 impLst.append(n)
132 else:
133 n = "{0}.{1}".format(project, i)
134 if n in modules:
135 impLst.append(n)
136 elif n in packages:
137 n = "{0}.<<Dummy>>".format(n)
138 impLst.append(n)
139 else:
140 n = "{0}.{1}".format(project, i)
141 if n in modules:
142 impLst.append(n)
143 for i in list(modules[module].from_imports.keys()):
144 if i.startswith('.'):
145 dots = len(i) - len(i.lstrip('.'))
146 if dots == 1:
147 i = i[1:]
148 elif dots > 1:
149 packagePath = os.path.dirname(modules[module].file)
150 hasInit = True
151 ppath = packagePath
152 while hasInit:
153 ppath = os.path.dirname(ppath)
154 hasInit = len(glob.glob(os.path.join(
155 ppath, '__init__.*'))) > 0
156 shortPackage = packagePath.replace(ppath, '')\
157 .replace(os.sep, '.')[1:]
158 packageList = shortPackage.split('.')[1:]
159 packageListLen = len(packageList)
160 i = '.'.join(
161 packageList[:packageListLen - dots + 1] +
162 [i[dots:]])
163
164 if i in modules:
165 impLst.append(i)
166 else:
167 if i.find('.') == -1:
168 n = "{0}.{1}".format(modules[module].package, i)
169 if n in modules:
170 impLst.append(n)
171 else:
172 n = "{0}.{1}".format(project, i)
173 if n in modules:
174 impLst.append(n)
175 elif n in packages:
176 n = "{0}.<<Dummy>>".format(n)
177 impLst.append(n)
178 else:
179 n = "{0}.{1}".format(project, i)
180 if n in modules:
181 impLst.append(n)
182 for imp in impLst:
183 impPackage = '.'.join(imp.split('.')[:-1])
184 if impPackage not in packages[package][1] and \
185 not impPackage == package:
186 packages[package][1].append(impPackage)
187
188 sortedkeys = sorted(packages.keys())
189 for package in sortedkeys:
190 if package:
191 relPackage = package.replace(project, '')
192 if relPackage and relPackage[0] == '.':
193 relPackage = relPackage[1:]
194 else:
195 relPackage = self.tr("<<Application>>")
196 else:
197 relPackage = self.tr("<<Others>>")
198 shape = self.__addPackage(
199 relPackage, packages[package][0], 0.0, 0.0)
200 shapeRect = shape.sceneBoundingRect()
201 shapes[package] = (shape, packages[package][1])
202 pn = p + shapeRect.width() + 10
203 maxHeight = max(maxHeight, shapeRect.height())
204 if pn > sceneRect.width():
205 p = 10
206 y += maxHeight + 10
207 maxHeight = shapeRect.height()
208 shape.setPos(p, y)
209 p += shapeRect.width() + 10
210 else:
211 shape.setPos(p, y)
212 p = pn
213
214 rect = self.umlView._getDiagramRect(10)
215 sceneRect = self.umlView.sceneRect()
216 if rect.width() > sceneRect.width():
217 sceneRect.setWidth(rect.width())
218 if rect.height() > sceneRect.height():
219 sceneRect.setHeight(rect.height())
220 self.umlView.setSceneSize(sceneRect.width(), sceneRect.height())
221
222 self.__createAssociations(shapes)
223 self.umlView.autoAdjustSceneSize(limit=True)
224
225 def __addPackage(self, name, modules, x, y):
226 """
227 Private method to add a package to the diagram.
228
229 @param name package name to be shown (string)
230 @param modules list of module names contained in the package
231 (list of strings)
232 @param x x-coordinate (float)
233 @param y y-coordinate (float)
234 @return reference to the package item (PackageItem)
235 """
236 from .PackageItem import PackageItem, PackageModel
237 modules.sort()
238 pm = PackageModel(name, modules)
239 pw = PackageItem(pm, x, y, noModules=self.noModules, scene=self.scene)
240 pw.setId(self.umlView.getItemId())
241 return pw
242
243 def __createAssociations(self, shapes):
244 """
245 Private method to generate the associations between the package shapes.
246
247 @param shapes list of shapes
248 """
249 from .AssociationItem import AssociationItem, Imports
250 for package in shapes:
251 for rel in shapes[package][1]:
252 assoc = AssociationItem(
253 shapes[package][0], shapes[rel][0],
254 Imports)
255 self.scene.addItem(assoc)
256
257 def getPersistenceData(self):
258 """
259 Public method to get a string for data to be persisted.
260
261 @return persisted data string (string)
262 """
263 return "project={0}, no_modules={1}".format(
264 self.project.getProjectFile(), self.noModules)
265
266 def parsePersistenceData(self, version, data):
267 """
268 Public method to parse persisted data.
269
270 @param version version of the data (string)
271 @param data persisted data to be parsed (string)
272 @return flag indicating success (boolean)
273 """
274 parts = data.split(", ")
275 if len(parts) != 2 or \
276 not parts[0].startswith("project=") or \
277 not parts[1].startswith("no_modules="):
278 return False
279
280 projectFile = parts[0].split("=", 1)[1].strip()
281 if projectFile != self.project.getProjectFile():
282 res = E5MessageBox.yesNo(
283 None,
284 self.tr("Load Diagram"),
285 self.tr(
286 """<p>The diagram belongs to the project <b>{0}</b>."""
287 """ Shall this project be opened?</p>""").format(
288 projectFile))
289 if res:
290 self.project.openProject(projectFile)
291
292 self.noModules = Utilities.toBool(parts[1].split("=", 1)[1].strip())
293
294 self.initialize()
295
296 return True

eric ide

mercurial