Mon, 12 Apr 2021 19:21:40 +0200
Applied some more code simplifications suggested by the new Simplify checker (Y114: use logical or for multiple if).
# -*- coding: utf-8 -*- # Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a class used to display the forms part of the project. """ import os import sys import shutil from PyQt5.QtCore import QThread, QFileInfo, pyqtSignal, QProcess from PyQt5.QtWidgets import QDialog, QInputDialog, QApplication, QMenu from E5Gui.E5Application import e5App from E5Gui import E5MessageBox, E5FileDialog from E5Gui.E5ProgressDialog import E5ProgressDialog from .ProjectBrowserModel import ( ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem, ProjectBrowserFormType ) from .ProjectBaseBrowser import ProjectBaseBrowser import UI.PixmapCache from UI.NotificationWidget import NotificationTypes import Preferences import Utilities from eric6config import getConfig class ProjectFormsBrowser(ProjectBaseBrowser): """ A class used to display the forms part of the project. @signal appendStderr(str) emitted after something was received from a QProcess on stderr @signal uipreview(str) emitted to preview a forms file @signal showMenu(str, QMenu) emitted when a menu is about to be shown. The name of the menu and a reference to the menu are given. @signal menusAboutToBeCreated() emitted when the context menus are about to be created. This is the right moment to add or remove hook methods. """ appendStderr = pyqtSignal(str) uipreview = pyqtSignal(str) showMenu = pyqtSignal(str, QMenu) menusAboutToBeCreated = pyqtSignal() Pyuic5IndentDefault = 4 Pyuic6IndentDefault = 4 def __init__(self, project, parent=None): """ Constructor @param project reference to the project object @param parent parent widget of this browser (QWidget) """ ProjectBaseBrowser.__init__(self, project, ProjectBrowserFormType, parent) self.selectedItemsFilter = [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem] self.setWindowTitle(self.tr('Forms')) self.setWhatsThis(self.tr( """<b>Project Forms Browser</b>""" """<p>This allows to easily see all forms contained in the""" """ current project. Several actions can be executed via the""" """ context menu.</p>""" )) # templates for Qt # these two lists have to stay in sync self.templates4 = [ 'dialog4.tmpl', 'widget4.tmpl', 'mainwindow4.tmpl', 'dialogbuttonboxbottom4.tmpl', 'dialogbuttonboxright4.tmpl', 'dialogbuttonsbottom4.tmpl', 'dialogbuttonsbottomcenter4.tmpl', 'dialogbuttonsright4.tmpl', '', 'wizard4.tmpl', 'wizardpage4.tmpl', 'qdockwidget4.tmpl', 'qframe4.tmpl', 'qgroupbox4.tmpl', 'qscrollarea4.tmpl', 'qmdiarea4.tmpl', 'qtabwidget4.tmpl', 'qtoolbox4.tmpl', 'qstackedwidget4.tmpl' ] self.templateTypes4 = [ self.tr("Dialog"), self.tr("Widget"), self.tr("Main Window"), self.tr("Dialog with Buttonbox (Bottom)"), self.tr("Dialog with Buttonbox (Right)"), self.tr("Dialog with Buttons (Bottom)"), self.tr("Dialog with Buttons (Bottom-Center)"), self.tr("Dialog with Buttons (Right)"), '', self.tr("QWizard"), self.tr("QWizardPage"), self.tr("QDockWidget"), self.tr("QFrame"), self.tr("QGroupBox"), self.tr("QScrollArea"), self.tr("QMdiArea"), self.tr("QTabWidget"), self.tr("QToolBox"), self.tr("QStackedWidget"), ] self.compileProc = None self.__uicompiler = "" self.project.projectClosed.connect(self.__resetUiCompiler) self.project.projectPropertiesChanged.connect(self.__resetUiCompiler) def _createPopupMenus(self): """ Protected overloaded method to generate the popup menu. """ self.menuActions = [] self.multiMenuActions = [] self.dirMenuActions = [] self.dirMultiMenuActions = [] self.menusAboutToBeCreated.emit() projectType = self.project.getProjectType() self.menu = QMenu(self) if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: self.menu.addAction( self.tr('Compile form'), self.__compileForm) self.menu.addAction( self.tr('Compile all forms'), self.__compileAllForms) self.menu.addAction( self.tr('Generate Dialog Code...'), self.__generateDialogCode) self.menu.addSeparator() self.__pyuicConfigAct = self.menu.addAction( self.tr('Configure uic Compiler'), self.__configureUicCompiler) self.menu.addSeparator() self.menu.addAction( self.tr('Open in Qt-Designer'), self.__openFile) self.menu.addAction( self.tr('Open in Editor'), self.__openFileInEditor) self.menu.addSeparator() self.menu.addAction(self.tr('Preview form'), self.__UIPreview) self.menu.addAction( self.tr('Preview translations'), self.__TRPreview) else: if self.hooks["compileForm"] is not None: self.menu.addAction( self.hooksMenuEntries.get( "compileForm", self.tr('Compile form')), self.__compileForm) if self.hooks["compileAllForms"] is not None: self.menu.addAction( self.hooksMenuEntries.get( "compileAllForms", self.tr('Compile all forms')), self.__compileAllForms) if self.hooks["generateDialogCode"] is not None: self.menu.addAction( self.hooksMenuEntries.get( "generateDialogCode", self.tr('Generate Dialog Code...')), self.__generateDialogCode) if ( self.hooks["compileForm"] is not None or self.hooks["compileAllForms"] is not None or self.hooks["generateDialogCode"] is not None ): self.menu.addSeparator() if self.hooks["open"] is not None: self.menu.addAction( self.hooksMenuEntries.get("open", self.tr('Open')), self.__openFile) self.menu.addAction(self.tr('Open'), self.__openFileInEditor) self.menu.addSeparator() act = self.menu.addAction(self.tr('Rename file'), self._renameFile) self.menuActions.append(act) act = self.menu.addAction( self.tr('Remove from project'), self._removeFile) self.menuActions.append(act) act = self.menu.addAction(self.tr('Delete'), self.__deleteFile) self.menuActions.append(act) self.menu.addSeparator() if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: self.menu.addAction(self.tr('New form...'), self.__newForm) else: if self.hooks["newForm"] is not None: self.menu.addAction( self.hooksMenuEntries.get( "newForm", self.tr('New form...')), self.__newForm) self.menu.addAction(self.tr('Add forms...'), self.__addFormFiles) self.menu.addAction( self.tr('Add forms directory...'), self.__addFormsDirectory) self.menu.addSeparator() self.menu.addAction( self.tr('Copy Path to Clipboard'), self._copyToClipboard) self.menu.addSeparator() self.menu.addAction( self.tr('Expand all directories'), self._expandAllDirs) self.menu.addAction( self.tr('Collapse all directories'), self._collapseAllDirs) self.menu.addSeparator() self.menu.addAction(self.tr('Configure...'), self._configure) self.backMenu = QMenu(self) if ( projectType in [ "PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6" ] or self.hooks["compileAllForms"] is not None ): self.backMenu.addAction( self.tr('Compile all forms'), self.__compileAllForms) self.backMenu.addSeparator() self.__pyuicBackConfigAct = self.backMenu.addAction( self.tr('Configure uic Compiler'), self.__configureUicCompiler) self.backMenu.addSeparator() self.backMenu.addAction(self.tr('New form...'), self.__newForm) else: if self.hooks["newForm"] is not None: self.backMenu.addAction( self.hooksMenuEntries.get( "newForm", self.tr('New form...')), self.__newForm) self.backMenu.addAction( self.tr('Add forms...'), self.project.addUiFiles) self.backMenu.addAction( self.tr('Add forms directory...'), self.project.addUiDir) self.backMenu.addSeparator() self.backMenu.addAction( self.tr('Expand all directories'), self._expandAllDirs) self.backMenu.addAction( self.tr('Collapse all directories'), self._collapseAllDirs) self.backMenu.addSeparator() self.backMenu.addAction(self.tr('Configure...'), self._configure) self.backMenu.setEnabled(False) # create the menu for multiple selected files self.multiMenu = QMenu(self) if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: self.multiMenu.addAction( self.tr('Compile forms'), self.__compileSelectedForms) self.multiMenu.addSeparator() self.__pyuicMultiConfigAct = self.multiMenu.addAction( self.tr('Configure uic Compiler'), self.__configureUicCompiler) self.multiMenu.addSeparator() self.multiMenu.addAction( self.tr('Open in Qt-Designer'), self.__openFile) self.multiMenu.addAction( self.tr('Open in Editor'), self.__openFileInEditor) self.multiMenu.addSeparator() self.multiMenu.addAction( self.tr('Preview translations'), self.__TRPreview) else: if self.hooks["compileSelectedForms"] is not None: act = self.multiMenu.addAction( self.hooksMenuEntries.get( "compileSelectedForms", self.tr('Compile forms')), self.__compileSelectedForms) self.multiMenu.addSeparator() if self.hooks["open"] is not None: self.multiMenu.addAction( self.hooksMenuEntries.get("open", self.tr('Open')), self.__openFile) self.multiMenu.addAction( self.tr('Open'), self.__openFileInEditor) self.multiMenu.addSeparator() act = self.multiMenu.addAction( self.tr('Remove from project'), self._removeFile) self.multiMenuActions.append(act) act = self.multiMenu.addAction( self.tr('Delete'), self.__deleteFile) self.multiMenuActions.append(act) self.multiMenu.addSeparator() self.multiMenu.addAction( self.tr('Expand all directories'), self._expandAllDirs) self.multiMenu.addAction( self.tr('Collapse all directories'), self._collapseAllDirs) self.multiMenu.addSeparator() self.multiMenu.addAction(self.tr('Configure...'), self._configure) self.dirMenu = QMenu(self) if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: self.dirMenu.addAction( self.tr('Compile all forms'), self.__compileAllForms) self.dirMenu.addSeparator() self.__pyuicDirConfigAct = self.dirMenu.addAction( self.tr('Configure uic Compiler'), self.__configureUicCompiler) self.dirMenu.addSeparator() else: if self.hooks["compileAllForms"] is not None: self.dirMenu.addAction( self.hooksMenuEntries.get( "compileAllForms", self.tr('Compile all forms')), self.__compileAllForms) self.dirMenu.addSeparator() act = self.dirMenu.addAction( self.tr('Remove from project'), self._removeDir) self.dirMenuActions.append(act) act = self.dirMenu.addAction( self.tr('Delete'), self._deleteDirectory) self.dirMenuActions.append(act) self.dirMenu.addSeparator() if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: self.dirMenu.addAction(self.tr('New form...'), self.__newForm) else: if self.hooks["newForm"] is not None: self.dirMenu.addAction( self.hooksMenuEntries.get( "newForm", self.tr('New form...')), self.__newForm) self.dirMenu.addAction( self.tr('Add forms...'), self.__addFormFiles) self.dirMenu.addAction( self.tr('Add forms directory...'), self.__addFormsDirectory) self.dirMenu.addSeparator() self.dirMenu.addAction( self.tr('Copy Path to Clipboard'), self._copyToClipboard) self.dirMenu.addSeparator() self.dirMenu.addAction( self.tr('Expand all directories'), self._expandAllDirs) self.dirMenu.addAction( self.tr('Collapse all directories'), self._collapseAllDirs) self.dirMenu.addSeparator() self.dirMenu.addAction(self.tr('Configure...'), self._configure) self.dirMultiMenu = QMenu(self) if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: self.dirMultiMenu.addAction( self.tr('Compile all forms'), self.__compileAllForms) self.dirMultiMenu.addSeparator() self.__pyuicDirMultiConfigAct = self.dirMultiMenu.addAction( self.tr('Configure uic Compiler'), self.__configureUicCompiler) self.dirMultiMenu.addSeparator() else: if self.hooks["compileAllForms"] is not None: self.dirMultiMenu.addAction( self.hooksMenuEntries.get( "compileAllForms", self.tr('Compile all forms')), self.__compileAllForms) self.dirMultiMenu.addSeparator() self.dirMultiMenu.addAction( self.tr('Add forms...'), self.project.addUiFiles) self.dirMultiMenu.addAction( self.tr('Add forms directory...'), self.project.addUiDir) self.dirMultiMenu.addSeparator() self.dirMultiMenu.addAction( self.tr('Expand all directories'), self._expandAllDirs) self.dirMultiMenu.addAction( self.tr('Collapse all directories'), self._collapseAllDirs) self.dirMultiMenu.addSeparator() self.dirMultiMenu.addAction( self.tr('Configure...'), self._configure) self.menu.aboutToShow.connect(self.__showContextMenu) self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti) self.dirMenu.aboutToShow.connect(self.__showContextMenuDir) self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti) self.backMenu.aboutToShow.connect(self.__showContextMenuBack) self.mainMenu = self.menu def _contextMenuRequested(self, coord): """ Protected slot to show the context menu. @param coord the position of the mouse pointer (QPoint) """ if not self.project.isOpen(): return enable = ( self.project.getProjectType() in ("PyQt5", "PyQt6", "E6Plugin") ) self.__pyuicConfigAct.setEnabled(enable) self.__pyuicMultiConfigAct.setEnabled(enable) self.__pyuicDirConfigAct.setEnabled(enable) self.__pyuicDirMultiConfigAct.setEnabled(enable) self.__pyuicBackConfigAct.setEnabled(enable) try: categories = self.getSelectedItemsCountCategorized( [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem]) cnt = categories["sum"] if cnt <= 1: index = self.indexAt(coord) if index.isValid(): self._selectSingleItem(index) categories = self.getSelectedItemsCountCategorized( [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem]) cnt = categories["sum"] bfcnt = categories[str(ProjectBrowserFileItem)] sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)] if cnt > 1 and cnt == bfcnt: self.multiMenu.popup(self.mapToGlobal(coord)) elif cnt > 1 and cnt == sdcnt: self.dirMultiMenu.popup(self.mapToGlobal(coord)) else: index = self.indexAt(coord) if cnt == 1 and index.isValid(): if bfcnt == 1: self.menu.popup(self.mapToGlobal(coord)) elif sdcnt == 1: self.dirMenu.popup(self.mapToGlobal(coord)) else: self.backMenu.popup(self.mapToGlobal(coord)) else: self.backMenu.popup(self.mapToGlobal(coord)) except Exception: # secok pass def __showContextMenu(self): """ Private slot called by the menu aboutToShow signal. """ ProjectBaseBrowser._showContextMenu(self, self.menu) self.showMenu.emit("Main", self.menu) def __showContextMenuMulti(self): """ Private slot called by the multiMenu aboutToShow signal. """ ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu) self.showMenu.emit("MainMulti", self.multiMenu) def __showContextMenuDir(self): """ Private slot called by the dirMenu aboutToShow signal. """ ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu) self.showMenu.emit("MainDir", self.dirMenu) def __showContextMenuDirMulti(self): """ Private slot called by the dirMultiMenu aboutToShow signal. """ ProjectBaseBrowser._showContextMenuDirMulti(self, self.dirMultiMenu) self.showMenu.emit("MainDirMulti", self.dirMultiMenu) def __showContextMenuBack(self): """ Private slot called by the backMenu aboutToShow signal. """ ProjectBaseBrowser._showContextMenuBack(self, self.backMenu) self.showMenu.emit("MainBack", self.backMenu) def __addFormFiles(self): """ Private method to add form files to the project. """ itm = self.model().item(self.currentIndex()) if isinstance(itm, ProjectBrowserFileItem): dn = os.path.dirname(itm.fileName()) elif isinstance( itm, (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem) ): dn = itm.dirName() else: dn = None self.project.addFiles('form', dn) def __addFormsDirectory(self): """ Private method to add form files of a directory to the project. """ itm = self.model().item(self.currentIndex()) if isinstance(itm, ProjectBrowserFileItem): dn = os.path.dirname(itm.fileName()) elif isinstance( itm, (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem) ): dn = itm.dirName() else: dn = None self.project.addDirectory('form', dn) def __openFile(self): """ Private slot to handle the Open menu action. """ itmList = self.getSelectedItems() for itm in itmList[:]: try: if isinstance(itm, ProjectBrowserFileItem): # hook support if self.hooks["open"] is not None: self.hooks["open"](itm.fileName()) else: self.designerFile.emit(itm.fileName()) except Exception: # secok pass def __openFileInEditor(self): """ Private slot to handle the Open in Editor menu action. """ itmList = self.getSelectedItems() for itm in itmList[:]: self.sourceFile.emit(itm.fileName()) def _openItem(self): """ Protected slot to handle the open popup menu entry. """ itmList = self.getSelectedItems() for itm in itmList: if isinstance(itm, ProjectBrowserFileItem): if itm.isDesignerFile(): self.designerFile.emit(itm.fileName()) else: self.sourceFile.emit(itm.fileName()) def __UIPreview(self): """ Private slot to handle the Preview menu action. """ itmList = self.getSelectedItems() self.uipreview.emit(itmList[0].fileName()) def __TRPreview(self): """ Private slot to handle the Preview translations action. """ fileNames = [] for itm in self.getSelectedItems(): fileNames.append(itm.fileName()) trfiles = sorted(self.project.pdata["TRANSLATIONS"][:]) fileNames.extend([os.path.join(self.project.ppath, trfile) for trfile in trfiles if trfile.endswith('.qm')]) self.trpreview[list].emit(fileNames) def __newForm(self): """ Private slot to handle the New Form menu action. """ itm = self.model().item(self.currentIndex()) if itm is None: path = self.project.ppath else: try: path = os.path.dirname(itm.fileName()) except AttributeError: try: path = itm.dirName() except AttributeError: path = os.path.join(self.project.ppath, itm.data(0)) if self.hooks["newForm"] is not None: self.hooks["newForm"](path) else: if self.project.getProjectType() in [ "PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6" ]: self.__newUiForm(path) def __newUiForm(self, path): """ Private slot to handle the New Form menu action for Qt-related projects. @param path full directory path for the new form file (string) """ selectedForm, ok = QInputDialog.getItem( None, self.tr("New Form"), self.tr("Select a form type:"), self.templateTypes4, 0, False) if not ok or not selectedForm: # user pressed cancel return templateIndex = self.templateTypes4.index(selectedForm) templateFile = os.path.join( getConfig('ericTemplatesDir'), self.templates4[templateIndex]) fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, self.tr("New Form"), path, self.tr("Qt User-Interface Files (*.ui);;All Files (*)"), "", E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if not fname: # user aborted or didn't enter a filename return ext = QFileInfo(fname).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fname += ex if os.path.exists(fname): res = E5MessageBox.yesNo( self, self.tr("New Form"), self.tr("The file already exists! Overwrite it?"), icon=E5MessageBox.Warning) if not res: # user selected to not overwrite return try: shutil.copy(templateFile, fname) except OSError as e: E5MessageBox.critical( self, self.tr("New Form"), self.tr( "<p>The new form file <b>{0}</b> could not be created.<br>" "Problem: {1}</p>").format(fname, str(e))) return self.project.appendFile(fname) self.designerFile.emit(fname) def __deleteFile(self): """ Private method to delete a form file from the project. """ itmList = self.getSelectedItems() files = [] fullNames = [] for itm in itmList: fn2 = itm.fileName() fullNames.append(fn2) fn = self.project.getRelativePath(fn2) files.append(fn) from UI.DeleteFilesConfirmationDialog import ( DeleteFilesConfirmationDialog ) dlg = DeleteFilesConfirmationDialog( self.parent(), self.tr("Delete forms"), self.tr( "Do you really want to delete these forms from the project?"), files) if dlg.exec() == QDialog.DialogCode.Accepted: for fn2, fn in zip(fullNames, files): self.closeSourceWindow.emit(fn2) self.project.deleteFile(fn) ########################################################################### ## Methods to handle the various compile commands ########################################################################### def __resetUiCompiler(self): """ Private slot to reset the determined UI compiler executable. """ self.__uicompiler = "" def __determineUiCompiler(self): """ Private method to determine the UI compiler for the project. """ self.__resetUiCompiler() if self.project.getProjectLanguage() == "Python3": if self.project.getProjectType() in ["PyQt5", "E6Plugin"]: self.__uicompiler = Utilities.generatePyQtToolPath( 'pyuic5', ["py3uic5"]) elif self.project.getProjectType() in ["PyQt6"]: self.__uicompiler = Utilities.generatePyQtToolPath( 'pyuic6') elif self.project.getProjectType() == "PySide2": self.__uicompiler = Utilities.generatePySideToolPath( 'pyside2-uic', variant=2) elif self.project.getProjectType() == "PySide6": self.__uicompiler = Utilities.generatePySideToolPath( 'pyside6-uic', variant=6) def getUiCompiler(self): """ Public method to get the UI compiler executable of the project. @return UI compiler executable @rtype str """ if not self.__uicompiler: self.__determineUiCompiler() return self.__uicompiler def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal of the pyuic5/pyuic6/pyside2-uic/pyside6-uic process. """ if self.compileProc is None: return self.compileProc.setReadChannel(QProcess.ProcessChannel.StandardOutput) while self.compileProc and self.compileProc.canReadLine(): self.buf += str(self.compileProc.readLine(), "utf-8", 'replace') def __readStderr(self): """ Private slot to handle the readyReadStandardError signal of the pyuic5/pyuic6/pyside2-uic/pyside6-uic process. """ if self.compileProc is None: return ioEncoding = Preferences.getSystem("IOEncoding") self.compileProc.setReadChannel(QProcess.ProcessChannel.StandardError) while self.compileProc and self.compileProc.canReadLine(): s = self.__uicompiler + ': ' error = str(self.compileProc.readLine(), ioEncoding, 'replace') s += error self.appendStderr.emit(s) def __compileUIDone(self, exitCode, exitStatus): """ Private slot to handle the finished signal of the pyuic/rbuic process. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.compileRunning = False e5App().getObject("ViewManager").enableEditorsCheckFocusIn(True) ui = e5App().getObject("UserInterface") if ( exitStatus == QProcess.ExitStatus.NormalExit and exitCode == 0 and self.buf ): ofn = os.path.join(self.project.ppath, self.compiledFile) try: if self.project.useSystemEol(): newline = None else: newline = self.project.getEolString() with open(ofn, "w", encoding="utf-8", newline=newline) as f: for line in self.buf.splitlines(): f.write(line + "\n") if self.compiledFile not in self.project.pdata["SOURCES"]: self.project.appendFile(ofn) ui.showNotification( UI.PixmapCache.getPixmap("designer48"), self.tr("Form Compilation"), self.tr("The compilation of the form file" " was successful.")) self.project.projectFormCompiled.emit(self.compiledFile) except OSError as msg: ui.showNotification( UI.PixmapCache.getPixmap("designer48"), self.tr("Form Compilation"), self.tr( "<p>The compilation of the form file failed.</p>" "<p>Reason: {0}</p>").format(str(msg)), kind=NotificationTypes.Critical, timeout=0) else: ui.showNotification( UI.PixmapCache.getPixmap("designer48"), self.tr("Form Compilation"), self.tr("The compilation of the form file failed."), kind=NotificationTypes.Critical, timeout=0) self.compileProc = None def __compileUI(self, fn, noDialog=False, progress=None): """ Private method to compile a .ui file to a .py/.rb file. @param fn filename of the .ui file to be compiled @param noDialog flag indicating silent operations @param progress reference to the progress dialog @return reference to the compile process (QProcess) """ self.compileProc = QProcess() args = [] self.buf = "" uicompiler = self.getUiCompiler() if not uicompiler: return None ofn, ext = os.path.splitext(fn) fn = os.path.join(self.project.ppath, fn) if self.project.getProjectLanguage() == "Python3": dirname, filename = os.path.split(ofn) self.compiledFile = os.path.join(dirname, "Ui_" + filename + ".py") if self.project.getProjectType() == "PySide2": # PySide2 if Preferences.getQt("PySide2FromImports"): args.append("--from-imports") elif self.project.getProjectType() == "PySide6": # PySide6 if Preferences.getQt("PySide6FromImports"): args.append("--from-imports") elif self.project.getProjectType() == "PyQt6": # PyQt6 if Preferences.getQt("Pyuic6Execute"): args.append("-x") indentWidth = Preferences.getQt("Pyuic6Indent") if indentWidth != self.Pyuic6IndentDefault: args.append("--indent={0}".format(indentWidth)) else: # PyQt5 if Preferences.getQt("PyuicExecute"): args.append("-x") indentWidth = Preferences.getQt("PyuicIndent") if indentWidth != self.Pyuic5IndentDefault: args.append("--indent={0}".format(indentWidth)) if ( 'uic5' in uicompiler and self.project.pdata["UICPARAMS"]["Package"] ): args.append("--import-from={0}".format( self.project.pdata["UICPARAMS"]["Package"])) elif Preferences.getQt("PyuicFromImports"): args.append("--from-imports") if self.project.pdata["UICPARAMS"]["RcSuffix"]: args.append("--resource-suffix={0}".format( self.project.pdata["UICPARAMS"]["RcSuffix"])) elif self.project.getProjectLanguage() == "Ruby": self.compiledFile = ofn + '.rb' args.append('-x') args.append(fn) self.compileProc.finished.connect(self.__compileUIDone) self.compileProc.readyReadStandardOutput.connect(self.__readStdout) self.compileProc.readyReadStandardError.connect(self.__readStderr) self.noDialog = noDialog self.compileProc.setWorkingDirectory(self.project.getProjectPath()) self.compileProc.start(uicompiler, args) procStarted = self.compileProc.waitForStarted(5000) if procStarted: self.compileRunning = True e5App().getObject("ViewManager").enableEditorsCheckFocusIn(False) return self.compileProc else: self.compileRunning = False if progress is not None: progress.cancel() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'Could not start {0}.<br>' 'Ensure that it is in the search path.' ).format(uicompiler)) return None def __generateDialogCode(self): """ Private method to generate dialog code for the form (Qt only). """ itm = self.model().item(self.currentIndex()) fn = itm.fileName() if self.hooks["generateDialogCode"] is not None: self.hooks["generateDialogCode"](fn) else: from .CreateDialogCodeDialog import CreateDialogCodeDialog # change environment sys.path.insert(0, self.project.getProjectPath()) cwd = os.getcwd() os.chdir(os.path.dirname(os.path.abspath(fn))) dlg = CreateDialogCodeDialog(fn, self.project, self) if not dlg.initError(): dlg.exec() # reset the environment os.chdir(cwd) del sys.path[0] def __compileForm(self): """ Private method to compile a form to a source file. """ itm = self.model().item(self.currentIndex()) fn2 = itm.fileName() fn = self.project.getRelativePath(fn2) if self.hooks["compileForm"] is not None: self.hooks["compileForm"](fn) else: self.__compileUI(fn) def __compileAllForms(self): """ Private method to compile all forms to source files. """ if self.hooks["compileAllForms"] is not None: self.hooks["compileAllForms"](self.project.pdata["FORMS"]) else: numForms = len(self.project.pdata["FORMS"]) progress = E5ProgressDialog( self.tr("Compiling forms..."), self.tr("Abort"), 0, numForms, self.tr("%v/%m Forms"), self) progress.setModal(True) progress.setMinimumDuration(0) progress.setWindowTitle(self.tr("Forms")) for prog, fn in enumerate(self.project.pdata["FORMS"]): progress.setValue(prog) if progress.wasCanceled(): break proc = self.__compileUI(fn, True, progress) if proc is not None: while proc.state() == QProcess.ProcessState.Running: QApplication.processEvents() QThread.msleep(300) QApplication.processEvents() else: break progress.setValue(numForms) def __compileSelectedForms(self): """ Private method to compile selected forms to source files. """ items = self.getSelectedItems() files = [self.project.getRelativePath(itm.fileName()) for itm in items] if self.hooks["compileSelectedForms"] is not None: self.hooks["compileSelectedForms"](files) else: numForms = len(files) progress = E5ProgressDialog( self.tr("Compiling forms..."), self.tr("Abort"), 0, numForms, self.tr("%v/%m Forms"), self) progress.setModal(True) progress.setMinimumDuration(0) progress.setWindowTitle(self.tr("Forms")) for prog, fn in enumerate(files): progress.setValue(prog) if progress.wasCanceled(): break proc = self.__compileUI(fn, True, progress) if proc is not None: while proc.state() == QProcess.ProcessState.Running: QApplication.processEvents() QThread.msleep(300) QApplication.processEvents() else: break progress.setValue(numForms) def compileChangedForms(self): """ Public method to compile all changed forms to source files. """ if self.hooks["compileChangedForms"] is not None: self.hooks["compileChangedForms"](self.project.pdata["FORMS"]) else: if self.project.getProjectType() not in [ "PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6" ]: # ignore the request for non Qt GUI projects return progress = E5ProgressDialog( self.tr("Determining changed forms..."), self.tr("Abort"), 0, 100, self.tr("%v/%m Forms")) progress.setMinimumDuration(0) progress.setWindowTitle(self.tr("Forms")) # get list of changed forms changedForms = [] progress.setMaximum(len(self.project.pdata["FORMS"])) for prog, fn in enumerate(self.project.pdata["FORMS"]): progress.setValue(prog) QApplication.processEvents() ifn = os.path.join(self.project.ppath, fn) if self.project.getProjectLanguage() == "Python3": dirname, filename = os.path.split(os.path.splitext(ifn)[0]) ofn = os.path.join(dirname, "Ui_" + filename + ".py") elif self.project.getProjectLanguage() == "Ruby": ofn = os.path.splitext(ifn)[0] + '.rb' if ( not os.path.exists(ofn) or os.stat(ifn).st_mtime > os.stat(ofn).st_mtime ): changedForms.append(fn) progress.setValue(len(self.project.pdata["FORMS"])) QApplication.processEvents() if changedForms: progress.setLabelText( self.tr("Compiling changed forms...")) progress.setMaximum(len(changedForms)) progress.setValue(prog) QApplication.processEvents() for prog, fn in enumerate(changedForms): progress.setValue(prog) if progress.wasCanceled(): break proc = self.__compileUI(fn, True, progress) if proc is not None: while proc.state() == QProcess.ProcessState.Running: QApplication.processEvents() QThread.msleep(300) QApplication.processEvents() else: break progress.setValue(len(changedForms)) QApplication.processEvents() def handlePreferencesChanged(self): """ Public slot used to handle the preferencesChanged signal. """ ProjectBaseBrowser.handlePreferencesChanged(self) self.__resetUiCompiler() def __configureUicCompiler(self): """ Private slot to configure some non-common uic compiler options. """ from .UicCompilerOptionsDialog import UicCompilerOptionsDialog params = self.project.pdata["UICPARAMS"] if self.project.getProjectType() in ["PyQt5", "PyQt6", "E6Plugin"]: dlg = UicCompilerOptionsDialog(params, self.getUiCompiler()) if dlg.exec() == QDialog.DialogCode.Accepted: package, suffix, root = dlg.getData() if package != params["Package"]: params["Package"] = package self.project.setDirty(True) if suffix != params["RcSuffix"]: params["RcSuffix"] = suffix self.project.setDirty(True) if root != params["PackagesRoot"]: params["PackagesRoot"] = root self.project.setDirty(True) ########################################################################### ## Support for hooks below ########################################################################### def _initHookMethods(self): """ Protected method to initialize the hooks dictionary. Supported hook methods are: <ul> <li>compileForm: takes filename as parameter</li> <li>compileAllForms: takes list of filenames as parameter</li> <li>compileSelectedForms: takes list of filenames as parameter</li> <li>compileChangedForms: takes list of filenames as parameter</li> <li>generateDialogCode: takes filename as parameter</li> <li>newForm: takes full directory path of new file as parameter</li> <li>open: takes a filename as parameter</li> </ul> <b>Note</b>: Filenames are relative to the project directory, if not specified differently. """ self.hooks = { "compileForm": None, "compileAllForms": None, "compileChangedForms": None, "compileSelectedForms": None, "generateDialogCode": None, "newForm": None, "open": None, }