20 |
20 |
21 class UMLDialog(QMainWindow): |
21 class UMLDialog(QMainWindow): |
22 """ |
22 """ |
23 Class implementing a dialog showing UML like diagrams. |
23 Class implementing a dialog showing UML like diagrams. |
24 """ |
24 """ |
|
25 NoDiagram = 255 |
25 ClassDiagram = 0 |
26 ClassDiagram = 0 |
26 PackageDiagram = 1 |
27 PackageDiagram = 1 |
27 ImportsDiagram = 2 |
28 ImportsDiagram = 2 |
28 ApplicationDiagram = 3 |
29 ApplicationDiagram = 3 |
29 |
30 |
|
31 FileVersions = ["1.0"] |
|
32 |
30 def __init__(self, diagramType, project, path="", parent=None, initBuilder=True, |
33 def __init__(self, diagramType, project, path="", parent=None, initBuilder=True, |
31 **kwargs): |
34 **kwargs): |
32 """ |
35 """ |
33 Constructor |
36 Constructor |
34 |
37 |
35 @param diagramType type of the diagram |
38 @param diagramType type of the diagram (one of ApplicationDiagram, ClassDiagram, |
36 (one of ApplicationDiagram, ClassDiagram, ImportsDiagram, PackageDiagram) |
39 ImportsDiagram, NoDiagram, PackageDiagram) |
37 @param project reference to the project object (Project) |
40 @param project reference to the project object (Project) |
38 @param path file or directory path to build the diagram from (string) |
41 @param path file or directory path to build the diagram from (string) |
39 @param parent parent widget of the dialog (QWidget) |
42 @param parent parent widget of the dialog (QWidget) |
40 @keyparam initBuilder flag indicating to initialize the diagram builder (boolean) |
43 @keyparam initBuilder flag indicating to initialize the diagram builder (boolean) |
41 @param kwargs diagram specific data |
44 @param kwargs diagram specific data |
42 """ |
45 """ |
43 super().__init__(parent) |
46 super().__init__(parent) |
44 self.setObjectName("UMLDialog") |
47 self.setObjectName("UMLDialog") |
45 |
48 |
46 self.__diagramType = diagramType |
49 self.__diagramType = diagramType |
|
50 self.__project = project |
47 |
51 |
48 self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0) |
52 self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0) |
49 self.umlView = UMLGraphicsView(self.scene, parent=self) |
53 self.umlView = UMLGraphicsView(self.scene, parent=self) |
50 self.builder = self.__diagramBuilder(self.__diagramType, project, path, **kwargs) |
54 self.builder = self.__diagramBuilder(self.__diagramType, path, **kwargs) |
51 if initBuilder: |
55 if self.builder and initBuilder: |
52 self.builder.initialize() |
56 self.builder.initialize() |
53 |
57 |
54 self.__fileName = "" |
58 self.__fileName = "" |
55 |
59 |
56 self.__initActions() |
60 self.__initActions() |
67 self.closeAct = \ |
71 self.closeAct = \ |
68 QAction(UI.PixmapCache.getIcon("close.png"), |
72 QAction(UI.PixmapCache.getIcon("close.png"), |
69 self.trUtf8("Close"), self) |
73 self.trUtf8("Close"), self) |
70 self.closeAct.triggered[()].connect(self.close) |
74 self.closeAct.triggered[()].connect(self.close) |
71 |
75 |
|
76 self.openAct = \ |
|
77 QAction(UI.PixmapCache.getIcon("open.png"), |
|
78 self.trUtf8("Load"), self) |
|
79 self.openAct.triggered[()].connect(self.load) |
|
80 |
72 self.saveAct = \ |
81 self.saveAct = \ |
73 QAction(UI.PixmapCache.getIcon("fileSave.png"), |
82 QAction(UI.PixmapCache.getIcon("fileSave.png"), |
74 self.trUtf8("Save"), self) |
83 self.trUtf8("Save"), self) |
75 self.saveAct.triggered[()].connect(self.__save) |
84 self.saveAct.triggered[()].connect(self.__save) |
76 |
85 |
102 self.windowToolBar.setIconSize(UI.Config.ToolBarIconSize) |
111 self.windowToolBar.setIconSize(UI.Config.ToolBarIconSize) |
103 self.windowToolBar.addAction(self.closeAct) |
112 self.windowToolBar.addAction(self.closeAct) |
104 |
113 |
105 self.fileToolBar = QToolBar(self.trUtf8("File"), self) |
114 self.fileToolBar = QToolBar(self.trUtf8("File"), self) |
106 self.fileToolBar.setIconSize(UI.Config.ToolBarIconSize) |
115 self.fileToolBar.setIconSize(UI.Config.ToolBarIconSize) |
|
116 self.fileToolBar.addAction(self.openAct) |
|
117 self.fileToolBar.addSeparator() |
107 self.fileToolBar.addAction(self.saveAct) |
118 self.fileToolBar.addAction(self.saveAct) |
108 self.fileToolBar.addAction(self.saveAsAct) |
119 self.fileToolBar.addAction(self.saveAsAct) |
109 self.fileToolBar.addAction(self.saveImageAct) |
120 self.fileToolBar.addAction(self.saveImageAct) |
110 self.fileToolBar.addSeparator() |
121 self.fileToolBar.addSeparator() |
111 self.fileToolBar.addAction(self.printPreviewAct) |
122 self.fileToolBar.addAction(self.printPreviewAct) |
115 |
126 |
116 self.addToolBar(Qt.TopToolBarArea, self.fileToolBar) |
127 self.addToolBar(Qt.TopToolBarArea, self.fileToolBar) |
117 self.addToolBar(Qt.TopToolBarArea, self.windowToolBar) |
128 self.addToolBar(Qt.TopToolBarArea, self.windowToolBar) |
118 self.addToolBar(Qt.TopToolBarArea, self.umlToolBar) |
129 self.addToolBar(Qt.TopToolBarArea, self.umlToolBar) |
119 |
130 |
120 def show(self): |
131 def show(self, fromFile=False): |
121 """ |
132 """ |
122 Overriden method to show the dialog. |
133 Public method to show the dialog. |
123 """ |
134 |
124 self.builder.buildDiagram() |
135 @keyparam fromFile flag indicating, that the diagram was loaded |
|
136 from file (boolean) |
|
137 """ |
|
138 if not fromFile and self.builder: |
|
139 self.builder.buildDiagram() |
125 super().show() |
140 super().show() |
126 |
141 |
127 def __relayout(self): |
142 def __relayout(self): |
128 """ |
143 """ |
129 Private method to relayout the diagram. |
144 Private method to relayout the diagram. |
130 """ |
145 """ |
131 self.builder.buildDiagram() |
146 if self.builder: |
132 |
147 self.builder.buildDiagram() |
133 def __diagramBuilder(self, diagramType, project, path, **kwargs): |
148 |
|
149 def __diagramBuilder(self, diagramType, path, **kwargs): |
134 """ |
150 """ |
135 Private method to instantiate a diagram builder object. |
151 Private method to instantiate a diagram builder object. |
136 |
152 |
137 @param diagramType type of the diagram |
153 @param diagramType type of the diagram |
138 (one of ApplicationDiagram, ClassDiagram, ImportsDiagram, PackageDiagram) |
154 (one of ApplicationDiagram, ClassDiagram, ImportsDiagram, PackageDiagram) |
139 @param project reference to the project object (Project) |
|
140 @param path file or directory path to build the diagram from (string) |
155 @param path file or directory path to build the diagram from (string) |
141 @param kwargs diagram specific data |
156 @param kwargs diagram specific data |
142 """ |
157 """ |
143 if diagramType == UMLDialog.ClassDiagram: |
158 if diagramType == UMLDialog.ClassDiagram: |
144 from .UMLClassDiagramBuilder import UMLClassDiagramBuilder |
159 from .UMLClassDiagramBuilder import UMLClassDiagramBuilder |
145 return UMLClassDiagramBuilder(self, self.umlView, project, path, **kwargs) |
160 return UMLClassDiagramBuilder(self, self.umlView, self.__project, path, |
|
161 **kwargs) |
146 elif diagramType == UMLDialog.PackageDiagram: |
162 elif diagramType == UMLDialog.PackageDiagram: |
147 from .PackageDiagramBuilder import PackageDiagramBuilder |
163 from .PackageDiagramBuilder import PackageDiagramBuilder |
148 return PackageDiagramBuilder(self, self.umlView, project, path, **kwargs) |
164 return PackageDiagramBuilder(self, self.umlView, self.__project, path, |
|
165 **kwargs) |
149 elif diagramType == UMLDialog.ImportsDiagram: |
166 elif diagramType == UMLDialog.ImportsDiagram: |
150 from .ImportsDiagramBuilder import ImportsDiagramBuilder |
167 from .ImportsDiagramBuilder import ImportsDiagramBuilder |
151 return ImportsDiagramBuilder(self, self.umlView, project, path, **kwargs) |
168 return ImportsDiagramBuilder(self, self.umlView, self.__project, path, |
|
169 **kwargs) |
152 elif diagramType == UMLDialog.ApplicationDiagram: |
170 elif diagramType == UMLDialog.ApplicationDiagram: |
153 from .ApplicationDiagramBuilder import ApplicationDiagramBuilder |
171 from .ApplicationDiagramBuilder import ApplicationDiagramBuilder |
154 return ApplicationDiagramBuilder(self, self.umlView, project, **kwargs) |
172 return ApplicationDiagramBuilder(self, self.umlView, self.__project, |
|
173 **kwargs) |
|
174 elif diagramType == UMLDialog.NoDiagram: |
|
175 return None |
155 else: |
176 else: |
156 raise ValueError( |
177 raise ValueError( |
157 self.trUtf8("Illegal diagram type '{0}' given.").format(diagramType)) |
178 self.trUtf8("Illegal diagram type '{0}' given.").format(diagramType)) |
158 |
179 |
159 def __diagramTypeString(self): |
180 def __diagramTypeString(self): |
227 f.close() |
248 f.close() |
228 except (IOError, OSError) as err: |
249 except (IOError, OSError) as err: |
229 E5MessageBox.critical(self, |
250 E5MessageBox.critical(self, |
230 self.trUtf8("Save Diagram"), |
251 self.trUtf8("Save Diagram"), |
231 self.trUtf8("""<p>The file <b>{0}</b> could not be saved.</p>""" |
252 self.trUtf8("""<p>The file <b>{0}</b> could not be saved.</p>""" |
232 """<p>Reason: {1}</p>""").format(fname, str(err))) |
253 """<p>Reason: {1}</p>""").format(filename, str(err))) |
|
254 return |
233 |
255 |
234 self.__fileName = filename |
256 self.__fileName = filename |
235 |
257 |
236 @classmethod |
258 def load(self): |
237 def generateDialogFromFile(cls, project, parent=None): |
259 """ |
238 """ |
260 Public method to load a diagram from a file. |
239 Class method to generate a dialog reading data from a file. |
261 |
240 |
262 @return flag indicating success (boolean) |
241 @param project reference to the project object (Project) |
263 """ |
242 @param parent parent widget of the dialog (QWidget) |
264 filename = E5FileDialog.getOpenFileName( |
243 @return generated dialog (UMLDialog) |
265 self, |
244 """ |
266 self.trUtf8("Load Diagram"), |
245 # TODO: implement this |
267 "", |
246 return None |
268 self.trUtf8("Eric5 Graphics File (*.e5g);;All Files (*)")) |
|
269 if not filename: |
|
270 # Cancelled by user |
|
271 return False |
|
272 |
|
273 try: |
|
274 f = open(filename, "r", encoding="utf-8") |
|
275 data = f.read() |
|
276 f.close() |
|
277 except (IOError, OSError) as err: |
|
278 E5MessageBox.critical(self, |
|
279 self.trUtf8("Load Diagram"), |
|
280 self.trUtf8("""<p>The file <b>{0}</b> could not be read.</p>""" |
|
281 """<p>Reason: {1}</p>""").format(filename, str(err))) |
|
282 return False |
|
283 |
|
284 lines = data.splitlines() |
|
285 if len(lines) < 3: |
|
286 self.__showInvalidDataMessage(filename) |
|
287 return False |
|
288 |
|
289 try: |
|
290 # step 1: check version |
|
291 linenum = 0 |
|
292 key, value = lines[linenum].split(": ", 1) |
|
293 if key.strip() != "version" or value.strip() not in UMLDialog.FileVersions: |
|
294 self.__showInvalidDataMessage(filename, linenum) |
|
295 return False |
|
296 else: |
|
297 version = value |
|
298 |
|
299 # step 2: extract diagram type |
|
300 linenum += 1 |
|
301 key, value = lines[linenum].split(": ", 1) |
|
302 if key.strip() != "diagram_type": |
|
303 self.__showInvalidDataMessage(filename, linenum) |
|
304 return False |
|
305 try: |
|
306 self.__diagramType = int(value.strip().split(None, 1)[0]) |
|
307 except ValueError: |
|
308 self.__showInvalidDataMessage(filename, linenum) |
|
309 return False |
|
310 self.scene.clear() |
|
311 self.builder = self.__diagramBuilder(self.__diagramType, "") |
|
312 |
|
313 # step 3: extract scene size |
|
314 linenum += 1 |
|
315 key, value = lines[linenum].split(": ", 1) |
|
316 if key.strip() != "scene_size": |
|
317 self.__showInvalidDataMessage(filename, linenum) |
|
318 return False |
|
319 try: |
|
320 width, height = [float(v.strip()) for v in value.split(";")] |
|
321 except ValueError: |
|
322 self.__showInvalidDataMessage(filename, linenum) |
|
323 return False |
|
324 self.umlView.setSceneSize(width, height) |
|
325 |
|
326 # step 4: extract builder data if available |
|
327 linenum += 1 |
|
328 key, value = lines[linenum].split(": ", 1) |
|
329 if key.strip() == "builder_data": |
|
330 ok = self.builder.parsePersistenceData(version, value) |
|
331 if not ok: |
|
332 self.__showInvalidDataMessage(filename, linenum) |
|
333 return False |
|
334 linenum += 1 |
|
335 |
|
336 # step 5: extract the graphics items |
|
337 ok, vlinenum = self.umlView.parsePersistenceData(version, lines[linenum:]) |
|
338 if not ok: |
|
339 self.__showInvalidDataMessage(filename, linenum + vlinenum) |
|
340 return False |
|
341 |
|
342 except IndexError: |
|
343 self.__showInvalidDataMessage(filename) |
|
344 return False |
|
345 |
|
346 # everything worked fine, so remember the file name |
|
347 self.__fileName = filename |
|
348 return True |
|
349 |
|
350 def __showInvalidDataMessage(self, filename, linenum=-1): |
|
351 """ |
|
352 Private slot to show a message dialog indicating an invalid data file. |
|
353 |
|
354 @param filename name of the file containing the invalid data (string) |
|
355 @param linenum number of the invalid line (integer) |
|
356 """ |
|
357 if linenum < 0: |
|
358 msg = self.trUtf8("""<p>The file <b>{0}</b> does not contain""" |
|
359 """ valid data.</p>""").format(filename) |
|
360 else: |
|
361 msg = self.trUtf8("""<p>The file <b>{0}</b> does not contain""" |
|
362 """ valid data.</p><p>Invalid line: {1}</p>""" |
|
363 ).format(filename, linenum + 1) |
|
364 E5MessageBox.critical(self, self.trUtf8("Load Diagram"), msg) |