diff -r 4b99609f6a87 -r 8de0fc1f7fef Graphics/UMLDialog.py --- a/Graphics/UMLDialog.py Sun Sep 09 19:38:07 2012 +0200 +++ b/Graphics/UMLDialog.py Mon Sep 10 18:42:28 2012 +0200 @@ -22,18 +22,21 @@ """ Class implementing a dialog showing UML like diagrams. """ + NoDiagram = 255 ClassDiagram = 0 PackageDiagram = 1 ImportsDiagram = 2 ApplicationDiagram = 3 + FileVersions = ["1.0"] + def __init__(self, diagramType, project, path="", parent=None, initBuilder=True, **kwargs): """ Constructor - @param diagramType type of the diagram - (one of ApplicationDiagram, ClassDiagram, ImportsDiagram, PackageDiagram) + @param diagramType type of the diagram (one of ApplicationDiagram, ClassDiagram, + ImportsDiagram, NoDiagram, PackageDiagram) @param project reference to the project object (Project) @param path file or directory path to build the diagram from (string) @param parent parent widget of the dialog (QWidget) @@ -44,11 +47,12 @@ self.setObjectName("UMLDialog") self.__diagramType = diagramType + self.__project = project self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0) self.umlView = UMLGraphicsView(self.scene, parent=self) - self.builder = self.__diagramBuilder(self.__diagramType, project, path, **kwargs) - if initBuilder: + self.builder = self.__diagramBuilder(self.__diagramType, path, **kwargs) + if self.builder and initBuilder: self.builder.initialize() self.__fileName = "" @@ -69,6 +73,11 @@ self.trUtf8("Close"), self) self.closeAct.triggered[()].connect(self.close) + self.openAct = \ + QAction(UI.PixmapCache.getIcon("open.png"), + self.trUtf8("Load"), self) + self.openAct.triggered[()].connect(self.load) + self.saveAct = \ QAction(UI.PixmapCache.getIcon("fileSave.png"), self.trUtf8("Save"), self) @@ -104,6 +113,8 @@ self.fileToolBar = QToolBar(self.trUtf8("File"), self) self.fileToolBar.setIconSize(UI.Config.ToolBarIconSize) + self.fileToolBar.addAction(self.openAct) + self.fileToolBar.addSeparator() self.fileToolBar.addAction(self.saveAct) self.fileToolBar.addAction(self.saveAsAct) self.fileToolBar.addAction(self.saveImageAct) @@ -117,41 +128,51 @@ self.addToolBar(Qt.TopToolBarArea, self.windowToolBar) self.addToolBar(Qt.TopToolBarArea, self.umlToolBar) - def show(self): + def show(self, fromFile=False): """ - Overriden method to show the dialog. + Public method to show the dialog. + + @keyparam fromFile flag indicating, that the diagram was loaded + from file (boolean) """ - self.builder.buildDiagram() + if not fromFile and self.builder: + self.builder.buildDiagram() super().show() def __relayout(self): """ Private method to relayout the diagram. """ - self.builder.buildDiagram() + if self.builder: + self.builder.buildDiagram() - def __diagramBuilder(self, diagramType, project, path, **kwargs): + def __diagramBuilder(self, diagramType, path, **kwargs): """ Private method to instantiate a diagram builder object. @param diagramType type of the diagram (one of ApplicationDiagram, ClassDiagram, ImportsDiagram, PackageDiagram) - @param project reference to the project object (Project) @param path file or directory path to build the diagram from (string) @param kwargs diagram specific data """ if diagramType == UMLDialog.ClassDiagram: from .UMLClassDiagramBuilder import UMLClassDiagramBuilder - return UMLClassDiagramBuilder(self, self.umlView, project, path, **kwargs) + return UMLClassDiagramBuilder(self, self.umlView, self.__project, path, + **kwargs) elif diagramType == UMLDialog.PackageDiagram: from .PackageDiagramBuilder import PackageDiagramBuilder - return PackageDiagramBuilder(self, self.umlView, project, path, **kwargs) + return PackageDiagramBuilder(self, self.umlView, self.__project, path, + **kwargs) elif diagramType == UMLDialog.ImportsDiagram: from .ImportsDiagramBuilder import ImportsDiagramBuilder - return ImportsDiagramBuilder(self, self.umlView, project, path, **kwargs) + return ImportsDiagramBuilder(self, self.umlView, self.__project, path, + **kwargs) elif diagramType == UMLDialog.ApplicationDiagram: from .ApplicationDiagramBuilder import ApplicationDiagramBuilder - return ApplicationDiagramBuilder(self, self.umlView, project, **kwargs) + return ApplicationDiagramBuilder(self, self.umlView, self.__project, + **kwargs) + elif diagramType == UMLDialog.NoDiagram: + return None else: raise ValueError( self.trUtf8("Illegal diagram type '{0}' given.").format(diagramType)) @@ -229,18 +250,115 @@ E5MessageBox.critical(self, self.trUtf8("Save Diagram"), self.trUtf8("""<p>The file <b>{0}</b> could not be saved.</p>""" - """<p>Reason: {1}</p>""").format(fname, str(err))) + """<p>Reason: {1}</p>""").format(filename, str(err))) + return self.__fileName = filename - @classmethod - def generateDialogFromFile(cls, project, parent=None): + def load(self): + """ + Public method to load a diagram from a file. + + @return flag indicating success (boolean) """ - Class method to generate a dialog reading data from a file. + filename = E5FileDialog.getOpenFileName( + self, + self.trUtf8("Load Diagram"), + "", + self.trUtf8("Eric5 Graphics File (*.e5g);;All Files (*)")) + if not filename: + # Cancelled by user + return False + + try: + f = open(filename, "r", encoding="utf-8") + data = f.read() + f.close() + except (IOError, OSError) as err: + E5MessageBox.critical(self, + self.trUtf8("Load Diagram"), + self.trUtf8("""<p>The file <b>{0}</b> could not be read.</p>""" + """<p>Reason: {1}</p>""").format(filename, str(err))) + return False + + lines = data.splitlines() + if len(lines) < 3: + self.__showInvalidDataMessage(filename) + return False - @param project reference to the project object (Project) - @param parent parent widget of the dialog (QWidget) - @return generated dialog (UMLDialog) + try: + # step 1: check version + linenum = 0 + key, value = lines[linenum].split(": ", 1) + if key.strip() != "version" or value.strip() not in UMLDialog.FileVersions: + self.__showInvalidDataMessage(filename, linenum) + return False + else: + version = value + + # step 2: extract diagram type + linenum += 1 + key, value = lines[linenum].split(": ", 1) + if key.strip() != "diagram_type": + self.__showInvalidDataMessage(filename, linenum) + return False + try: + self.__diagramType = int(value.strip().split(None, 1)[0]) + except ValueError: + self.__showInvalidDataMessage(filename, linenum) + return False + self.scene.clear() + self.builder = self.__diagramBuilder(self.__diagramType, "") + + # step 3: extract scene size + linenum += 1 + key, value = lines[linenum].split(": ", 1) + if key.strip() != "scene_size": + self.__showInvalidDataMessage(filename, linenum) + return False + try: + width, height = [float(v.strip()) for v in value.split(";")] + except ValueError: + self.__showInvalidDataMessage(filename, linenum) + return False + self.umlView.setSceneSize(width, height) + + # step 4: extract builder data if available + linenum += 1 + key, value = lines[linenum].split(": ", 1) + if key.strip() == "builder_data": + ok = self.builder.parsePersistenceData(version, value) + if not ok: + self.__showInvalidDataMessage(filename, linenum) + return False + linenum += 1 + + # step 5: extract the graphics items + ok, vlinenum = self.umlView.parsePersistenceData(version, lines[linenum:]) + if not ok: + self.__showInvalidDataMessage(filename, linenum + vlinenum) + return False + + except IndexError: + self.__showInvalidDataMessage(filename) + return False + + # everything worked fine, so remember the file name + self.__fileName = filename + return True + + def __showInvalidDataMessage(self, filename, linenum=-1): """ - # TODO: implement this - return None + Private slot to show a message dialog indicating an invalid data file. + + @param filename name of the file containing the invalid data (string) + @param linenum number of the invalid line (integer) + """ + if linenum < 0: + msg = self.trUtf8("""<p>The file <b>{0}</b> does not contain""" + """ valid data.</p>""").format(filename) + else: + msg = self.trUtf8("""<p>The file <b>{0}</b> does not contain""" + """ valid data.</p><p>Invalid line: {1}</p>""" + ).format(filename, linenum + 1) + E5MessageBox.critical(self, self.trUtf8("Load Diagram"), msg)