6 """ |
6 """ |
7 Module implementing a dialog showing UML like diagrams. |
7 Module implementing a dialog showing UML like diagrams. |
8 """ |
8 """ |
9 |
9 |
10 import enum |
10 import enum |
11 |
11 import json |
12 from PyQt5.QtCore import pyqtSlot, Qt, QFileInfo |
12 |
|
13 from PyQt5.QtCore import pyqtSlot, Qt, QFileInfo, QCoreApplication |
13 from PyQt5.QtWidgets import QAction, QToolBar, QGraphicsScene |
14 from PyQt5.QtWidgets import QAction, QToolBar, QGraphicsScene |
14 |
15 |
15 from E5Gui import E5MessageBox, E5FileDialog |
16 from E5Gui import E5MessageBox, E5FileDialog |
16 from E5Gui.E5MainWindow import E5MainWindow |
17 from E5Gui.E5MainWindow import E5MainWindow |
17 |
18 |
34 """ |
35 """ |
35 Class implementing a dialog showing UML like diagrams. |
36 Class implementing a dialog showing UML like diagrams. |
36 """ |
37 """ |
37 FileVersions = ("1.0") |
38 FileVersions = ("1.0") |
38 |
39 |
|
40 UMLDialogType2String = { |
|
41 UMLDialogType.CLASS_DIAGRAM: |
|
42 QCoreApplication.translate("UMLDialog", "Class Diagram"), |
|
43 UMLDialogType.PACKAGE_DIAGRAM: |
|
44 QCoreApplication.translate("UMLDialog", "Package Diagram"), |
|
45 UMLDialogType.IMPORTS_DIAGRAM: |
|
46 QCoreApplication.translate("UMLDialog", "Imports Diagram"), |
|
47 UMLDialogType.APPLICATION_DIAGRAM: |
|
48 QCoreApplication.translate("UMLDialog", "Application Diagram"), |
|
49 } |
|
50 |
39 def __init__(self, diagramType, project, path="", parent=None, |
51 def __init__(self, diagramType, project, path="", parent=None, |
40 initBuilder=True, **kwargs): |
52 initBuilder=True, **kwargs): |
41 """ |
53 """ |
42 Constructor |
54 Constructor |
43 |
55 |
58 super().__init__(parent) |
70 super().__init__(parent) |
59 self.setObjectName("UMLDialog") |
71 self.setObjectName("UMLDialog") |
60 |
72 |
61 self.__project = project |
73 self.__project = project |
62 self.__diagramType = diagramType |
74 self.__diagramType = diagramType |
63 self.__diagramTypeString = { |
|
64 UMLDialogType.CLASS_DIAGRAM: "Class Diagram", |
|
65 UMLDialogType.PACKAGE_DIAGRAM: "Package Diagram", |
|
66 UMLDialogType.IMPORTS_DIAGRAM: "Imports Diagram", |
|
67 UMLDialogType.APPLICATION_DIAGRAM: "Application Diagram", |
|
68 }.get(diagramType, "Illegal Diagram Type") |
|
69 |
75 |
70 from .UMLGraphicsView import UMLGraphicsView |
76 from .UMLGraphicsView import UMLGraphicsView |
71 self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0) |
77 self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0) |
72 self.umlView = UMLGraphicsView(self.scene, parent=self) |
78 self.umlView = UMLGraphicsView(self.scene, parent=self) |
73 self.builder = self.__diagramBuilder( |
79 self.builder = self.__diagramBuilder( |
82 |
88 |
83 self.setCentralWidget(self.umlView) |
89 self.setCentralWidget(self.umlView) |
84 |
90 |
85 self.umlView.relayout.connect(self.__relayout) |
91 self.umlView.relayout.connect(self.__relayout) |
86 |
92 |
87 self.setWindowTitle(self.__diagramTypeString) |
93 self.setWindowTitle(self.__getDiagramTitel(self.__diagramType)) |
|
94 |
|
95 def __getDiagramTitel(self, diagramType): |
|
96 """ |
|
97 Private method to get a textual description for the diagram type. |
|
98 |
|
99 @param diagramType diagram type string |
|
100 @type str |
|
101 @return titel of the diagram |
|
102 @rtype str |
|
103 """ |
|
104 return UMLDialog.UMLDialogType2String.get( |
|
105 diagramType, self.tr("Illegal Diagram Type") |
|
106 ) |
88 |
107 |
89 def __initActions(self): |
108 def __initActions(self): |
90 """ |
109 """ |
91 Private slot to initialize the actions. |
110 Private slot to initialize the actions. |
92 """ |
111 """ |
206 """ |
225 """ |
207 Private slot to save the diagram with the current name. |
226 Private slot to save the diagram with the current name. |
208 """ |
227 """ |
209 self.__saveAs(self.__fileName) |
228 self.__saveAs(self.__fileName) |
210 |
229 |
211 # TODO: change this to save file in JSON format |
|
212 @pyqtSlot() |
230 @pyqtSlot() |
213 def __saveAs(self, filename=""): |
231 def __saveAs(self, filename=""): |
214 """ |
232 """ |
215 Private slot to save the diagram. |
233 Private slot to save the diagram. |
216 |
234 |
220 if not filename: |
238 if not filename: |
221 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
239 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
222 self, |
240 self, |
223 self.tr("Save Diagram"), |
241 self.tr("Save Diagram"), |
224 "", |
242 "", |
225 self.tr("Eric Graphics File (*.e5g);;All Files (*)"), |
243 self.tr("Eric Graphics File (*.egj);;" |
|
244 "Eric Text Graphics File (*.e5g);;" |
|
245 "All Files (*)"), |
226 "", |
246 "", |
227 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
247 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
228 if not fname: |
248 if not fname: |
229 return |
249 return |
230 ext = QFileInfo(fname).suffix() |
250 ext = QFileInfo(fname).suffix() |
241 icon=E5MessageBox.Warning) |
261 icon=E5MessageBox.Warning) |
242 if not res: |
262 if not res: |
243 return |
263 return |
244 filename = fname |
264 filename = fname |
245 |
265 |
246 lines = [ |
266 res = ( |
247 "version: 1.0", |
267 self.__writeLineBasedGraphicsFile(filename) |
248 "diagram_type: {0} ({1})".format( |
268 if filename.endswith(".e5g") else |
249 self.__diagramType.value, self.__diagramTypeString), |
269 # JSON format is the default |
250 "scene_size: {0};{1}".format(self.scene.width(), |
270 self.__writeJsonGraphicsFile(filename) |
251 self.scene.height()), |
271 ) |
252 ] |
272 |
253 persistenceData = self.builder.getPersistenceData() |
273 if res: |
254 if persistenceData: |
274 # save the file name only in case of success |
255 lines.append("builder_data: {0}".format(persistenceData)) |
275 self.__fileName = filename |
256 lines.extend(self.umlView.getPersistenceData()) |
|
257 |
|
258 try: |
|
259 with open(filename, "w", encoding="utf-8") as f: |
|
260 f.write("\n".join(lines)) |
|
261 except OSError as err: |
|
262 E5MessageBox.critical( |
|
263 self, |
|
264 self.tr("Save Diagram"), |
|
265 self.tr( |
|
266 """<p>The file <b>{0}</b> could not be saved.</p>""" |
|
267 """<p>Reason: {1}</p>""").format(filename, str(err))) |
|
268 return |
|
269 |
|
270 self.__fileName = filename |
|
271 |
276 |
272 # TODO: add loading of file in JSON format |
277 # TODO: add loading of file in JSON format |
273 # TODO: eric7: delete the current one |
278 # TODO: eric7: delete the current one |
274 def load(self, filename=""): |
279 def load(self, filename=""): |
275 """ |
280 """ |
288 self.tr("Eric Graphics File (*.e5g);;All Files (*)")) |
293 self.tr("Eric Graphics File (*.e5g);;All Files (*)")) |
289 if not filename: |
294 if not filename: |
290 # Canceled by user |
295 # Canceled by user |
291 return False |
296 return False |
292 |
297 |
|
298 if filename.endswith(".e5g"): |
|
299 return self.__readLineBasedGraphicsFile(filename) |
|
300 else: |
|
301 return False |
|
302 |
|
303 ####################################################################### |
|
304 ## Methods to read and write eric graphics files of the old line |
|
305 ## based file format. |
|
306 ####################################################################### |
|
307 |
|
308 def __readLineBasedGraphicsFile(self, filename): |
|
309 """ |
|
310 Private method to read an eric graphics file using the old line |
|
311 based file format. |
|
312 |
|
313 @param filename name of the file to be read |
|
314 @type str |
|
315 @return flag indicating success |
|
316 @rtype bool |
|
317 """ |
293 try: |
318 try: |
294 with open(filename, "r", encoding="utf-8") as f: |
319 with open(filename, "r", encoding="utf-8") as f: |
295 data = f.read() |
320 data = f.read() |
296 except OSError as err: |
321 except OSError as err: |
297 E5MessageBox.critical( |
322 E5MessageBox.critical( |
325 key, value = lines[linenum].split(": ", 1) |
350 key, value = lines[linenum].split(": ", 1) |
326 if key.strip() != "diagram_type": |
351 if key.strip() != "diagram_type": |
327 self.__showInvalidDataMessage(filename, linenum) |
352 self.__showInvalidDataMessage(filename, linenum) |
328 return False |
353 return False |
329 try: |
354 try: |
330 diagramType, diagramTypeString = value.strip().split(None, 1) |
355 diagramType = value.strip().split(None, 1)[0] |
331 self.__diagramType = UMLDialogType(int(diagramType)) |
356 self.__diagramType = UMLDialogType(int(diagramType)) |
332 self.__diagramTypeString = diagramTypeString[1:-1] |
|
333 # remove opening and closing bracket |
|
334 except ValueError: |
357 except ValueError: |
335 self.__showInvalidDataMessage(filename, linenum) |
358 self.__showInvalidDataMessage(filename, linenum) |
336 return False |
359 return False |
337 self.scene.clear() |
360 self.scene.clear() |
338 self.builder = self.__diagramBuilder(self.__diagramType, "") |
361 self.builder = self.__diagramBuilder(self.__diagramType, "") |
371 self.__showInvalidDataMessage(filename) |
394 self.__showInvalidDataMessage(filename) |
372 return False |
395 return False |
373 |
396 |
374 # everything worked fine, so remember the file name and set the |
397 # everything worked fine, so remember the file name and set the |
375 # window title |
398 # window title |
376 self.setWindowTitle(self.__diagramTypeString) |
399 self.setWindowTitle(self.__getDiagramTitel(self.__diagramType)) |
377 self.__fileName = filename |
400 self.__fileName = filename |
378 |
401 |
379 return True |
402 return True |
|
403 |
|
404 def __writeLineBasedGraphicsFile(self, filename): |
|
405 """ |
|
406 Private method to write an eric graphics file using the old line |
|
407 based file format. |
|
408 |
|
409 @param filename name of the file to write to |
|
410 @type str |
|
411 @return flag indicating a successful write |
|
412 @rtype bool |
|
413 """ |
|
414 lines = [ |
|
415 "version: 1.0", |
|
416 "diagram_type: {0} ({1})".format( |
|
417 self.__diagramType.value, |
|
418 self.__getDiagramTitel(self.__diagramType)), |
|
419 "scene_size: {0};{1}".format(self.scene.width(), |
|
420 self.scene.height()), |
|
421 ] |
|
422 persistenceData = self.builder.getPersistenceData() |
|
423 if persistenceData: |
|
424 lines.append("builder_data: {0}".format(persistenceData)) |
|
425 lines.extend(self.umlView.getPersistenceData()) |
|
426 |
|
427 try: |
|
428 with open(filename, "w", encoding="utf-8") as f: |
|
429 f.write("\n".join(lines)) |
|
430 return True |
|
431 except OSError as err: |
|
432 E5MessageBox.critical( |
|
433 self, |
|
434 self.tr("Save Diagram"), |
|
435 self.tr( |
|
436 """<p>The file <b>{0}</b> could not be saved.</p>""" |
|
437 """<p>Reason: {1}</p>""").format(filename, str(err))) |
|
438 return False |
380 |
439 |
381 def __showInvalidDataMessage(self, filename, linenum=-1): |
440 def __showInvalidDataMessage(self, filename, linenum=-1): |
382 """ |
441 """ |
383 Private slot to show a message dialog indicating an invalid data file. |
442 Private slot to show a message dialog indicating an invalid data file. |
384 |
443 |
394 self.tr("""<p>The file <b>{0}</b> does not contain""" |
453 self.tr("""<p>The file <b>{0}</b> does not contain""" |
395 """ valid data.</p><p>Invalid line: {1}</p>""" |
454 """ valid data.</p><p>Invalid line: {1}</p>""" |
396 ).format(filename, linenum + 1) |
455 ).format(filename, linenum + 1) |
397 ) |
456 ) |
398 E5MessageBox.critical(self, self.tr("Load Diagram"), msg) |
457 E5MessageBox.critical(self, self.tr("Load Diagram"), msg) |
|
458 |
|
459 ####################################################################### |
|
460 ## Methods to read and write eric graphics files of the JSON based |
|
461 ## file format. |
|
462 ####################################################################### |
|
463 |
|
464 def __writeJsonGraphicsFile(self, filename): |
|
465 """ |
|
466 Private method to write an eric graphics file using the JSON based |
|
467 file format. |
|
468 |
|
469 @param filename name of the file to write to |
|
470 @type str |
|
471 @return flag indicating a successful write |
|
472 @rtype bool |
|
473 """ |
|
474 data = { |
|
475 "version": "1.0", |
|
476 "type": self.__diagramType.value, |
|
477 "title": self.__getDiagramTitel(self.__diagramType), |
|
478 "width": self.scene.width(), |
|
479 "height": self.scene.height(), |
|
480 "builder": self.builder.toDict(), |
|
481 "view": self.umlView.toDict(), |
|
482 } |
|
483 |
|
484 try: |
|
485 jsonString = json.dumps(data, indent=2) |
|
486 with open(filename, "w") as f: |
|
487 f.write(jsonString) |
|
488 return True |
|
489 except (TypeError, OSError) as err: |
|
490 E5MessageBox.critical( |
|
491 self, |
|
492 self.tr("Save Diagram"), |
|
493 self.tr( |
|
494 """<p>The file <b>{0}</b> could not be saved.</p>""" |
|
495 """<p>Reason: {1}</p>""").format(filename, str(err))) |
|
496 return False |