Tue, 20 Apr 2021 19:47:39 +0200
Applied some more code simplifications suggested by the new Simplify checker (Y108: use ternary operator).
# -*- coding: utf-8 -*- # Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a dialog showing UML like diagrams. """ from PyQt5.QtCore import pyqtSlot, Qt, QFileInfo from PyQt5.QtWidgets import QAction, QToolBar, QGraphicsScene from E5Gui import E5MessageBox, E5FileDialog from E5Gui.E5MainWindow import E5MainWindow import UI.Config import UI.PixmapCache class UMLDialog(E5MainWindow): """ 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, 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) @param initBuilder flag indicating to initialize the diagram builder (boolean) @keyparam kwargs diagram specific data """ super().__init__(parent) self.setObjectName("UMLDialog") self.__diagramType = diagramType self.__project = project from .UMLGraphicsView import UMLGraphicsView self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0) self.umlView = UMLGraphicsView(self.scene, parent=self) self.builder = self.__diagramBuilder( self.__diagramType, path, **kwargs) if self.builder and initBuilder: self.builder.initialize() self.__fileName = "" self.__initActions() self.__initToolBars() self.setCentralWidget(self.umlView) self.umlView.relayout.connect(self.__relayout) self.setWindowTitle(self.__diagramTypeString()) def __initActions(self): """ Private slot to initialize the actions. """ self.closeAct = QAction( UI.PixmapCache.getIcon("close"), self.tr("Close"), self) self.closeAct.triggered.connect(self.close) self.openAct = QAction( UI.PixmapCache.getIcon("open"), self.tr("Load"), self) self.openAct.triggered.connect(self.load) self.saveAct = QAction( UI.PixmapCache.getIcon("fileSave"), self.tr("Save"), self) self.saveAct.triggered.connect(self.__save) self.saveAsAct = QAction( UI.PixmapCache.getIcon("fileSaveAs"), self.tr("Save As..."), self) self.saveAsAct.triggered.connect(self.__saveAs) self.saveImageAct = QAction( UI.PixmapCache.getIcon("fileSavePixmap"), self.tr("Save as Image"), self) self.saveImageAct.triggered.connect(self.umlView.saveImage) self.printAct = QAction( UI.PixmapCache.getIcon("print"), self.tr("Print"), self) self.printAct.triggered.connect(self.umlView.printDiagram) self.printPreviewAct = QAction( UI.PixmapCache.getIcon("printPreview"), self.tr("Print Preview"), self) self.printPreviewAct.triggered.connect( self.umlView.printPreviewDiagram) def __initToolBars(self): """ Private slot to initialize the toolbars. """ self.windowToolBar = QToolBar(self.tr("Window"), self) self.windowToolBar.setIconSize(UI.Config.ToolBarIconSize) self.windowToolBar.addAction(self.closeAct) self.fileToolBar = QToolBar(self.tr("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) self.fileToolBar.addSeparator() self.fileToolBar.addAction(self.printPreviewAct) self.fileToolBar.addAction(self.printAct) self.umlToolBar = self.umlView.initToolBar() self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.fileToolBar) self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.windowToolBar) self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.umlToolBar) def show(self, fromFile=False): """ Public method to show the dialog. @param fromFile flag indicating, that the diagram was loaded from file (boolean) """ if not fromFile and self.builder: self.builder.buildDiagram() super().show() def __relayout(self): """ Private method to relayout the diagram. """ if self.builder: self.builder.buildDiagram() 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 path file or directory path to build the diagram from (string) @keyparam kwargs diagram specific data @return reference to the instantiated diagram builder @exception ValueError raised to indicate an illegal diagram type """ if diagramType not in ( UMLDialog.ClassDiagram, UMLDialog.PackageDiagram, UMLDialog.ImportsDiagram, UMLDialog.ApplicationDiagram, UMLDialog.NoDiagram ): raise ValueError(self.tr( "Illegal diagram type '{0}' given.").format(diagramType)) if diagramType == UMLDialog.ClassDiagram: from .UMLClassDiagramBuilder import UMLClassDiagramBuilder return UMLClassDiagramBuilder( self, self.umlView, self.__project, path, **kwargs) elif diagramType == UMLDialog.PackageDiagram: from .PackageDiagramBuilder import PackageDiagramBuilder return PackageDiagramBuilder( self, self.umlView, self.__project, path, **kwargs) elif diagramType == UMLDialog.ImportsDiagram: from .ImportsDiagramBuilder import ImportsDiagramBuilder return ImportsDiagramBuilder( self, self.umlView, self.__project, path, **kwargs) elif diagramType == UMLDialog.ApplicationDiagram: from .ApplicationDiagramBuilder import ApplicationDiagramBuilder return ApplicationDiagramBuilder( self, self.umlView, self.__project, **kwargs) else: return None def __diagramTypeString(self): """ Private method to generate a readable string for the diagram type. @return readable type string (string) """ if self.__diagramType == UMLDialog.ClassDiagram: return "Class Diagram" elif self.__diagramType == UMLDialog.PackageDiagram: return "Package Diagram" elif self.__diagramType == UMLDialog.ImportsDiagram: return "Imports Diagram" elif self.__diagramType == UMLDialog.ApplicationDiagram: return "Application Diagram" else: return "Illegal Diagram Type" def __save(self): """ Private slot to save the diagram with the current name. """ self.__saveAs(self.__fileName) @pyqtSlot() def __saveAs(self, filename=""): """ Private slot to save the diagram. @param filename name of the file to write to (string) """ if not filename: fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, self.tr("Save Diagram"), "", self.tr("Eric Graphics File (*.e5g);;All Files (*)"), "", E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if not fname: return ext = QFileInfo(fname).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fname += ex if QFileInfo(fname).exists(): res = E5MessageBox.yesNo( self, self.tr("Save Diagram"), self.tr("<p>The file <b>{0}</b> already exists." " Overwrite it?</p>").format(fname), icon=E5MessageBox.Warning) if not res: return filename = fname lines = [ "version: 1.0", "diagram_type: {0} ({1})".format( self.__diagramType, self.__diagramTypeString()), "scene_size: {0};{1}".format(self.scene.width(), self.scene.height()), ] persistenceData = self.builder.getPersistenceData() if persistenceData: lines.append("builder_data: {0}".format(persistenceData)) lines.extend(self.umlView.getPersistenceData()) try: with open(filename, "w", encoding="utf-8") as f: f.write("\n".join(lines)) except OSError as err: E5MessageBox.critical( self, self.tr("Save Diagram"), self.tr( """<p>The file <b>{0}</b> could not be saved.</p>""" """<p>Reason: {1}</p>""").format(filename, str(err))) return self.__fileName = filename def load(self): """ Public method to load a diagram from a file. @return flag indicating success (boolean) """ filename = E5FileDialog.getOpenFileName( self, self.tr("Load Diagram"), "", self.tr("Eric Graphics File (*.e5g);;All Files (*)")) if not filename: # Cancelled by user return False try: with open(filename, "r", encoding="utf-8") as f: data = f.read() except OSError as err: E5MessageBox.critical( self, self.tr("Load Diagram"), self.tr( """<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 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 and set the # window title self.setWindowTitle(self.__diagramTypeString()) self.__fileName = filename return True def __showInvalidDataMessage(self, filename, linenum=-1): """ 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) """ msg = ( self.tr("""<p>The file <b>{0}</b> does not contain""" """ valid data.</p>""").format(filename) if linenum < 0 else self.tr("""<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.tr("Load Diagram"), msg)