src/eric7/Project/ProjectResourcesBrowser.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
diff -r 3fc8dfeb6ebe -r b99e7fd55fd3 src/eric7/Project/ProjectResourcesBrowser.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Project/ProjectResourcesBrowser.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,956 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a class used to display the resources part of the project.
+"""
+
+import os
+import contextlib
+import pathlib
+
+from PyQt6.QtCore import QThread, pyqtSignal, QProcess
+from PyQt6.QtWidgets import QDialog, QApplication, QMenu
+
+from EricWidgets.EricApplication import ericApp
+from EricWidgets import EricMessageBox, EricFileDialog
+from EricWidgets.EricProgressDialog import EricProgressDialog
+
+from .ProjectBrowserModel import (
+    ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem,
+    ProjectBrowserDirectoryItem, ProjectBrowserResourceType
+)
+from .ProjectBaseBrowser import ProjectBaseBrowser
+
+import UI.PixmapCache
+from UI.NotificationWidget import NotificationTypes
+
+import Preferences
+import Utilities
+
+
+class ProjectResourcesBrowser(ProjectBaseBrowser):
+    """
+    A class used to display the resources part of the project.
+    
+    @signal appendStderr(str) emitted after something was received from
+        a QProcess on stderr
+    @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.
+    """
+    appendStderr = pyqtSignal(str)
+    showMenu = pyqtSignal(str, QMenu)
+    
+    RCFilenameFormatPython = "{0}_rc.py"
+    RCFilenameFormatRuby = "{0}_rc.rb"
+    
+    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, ProjectBrowserResourceType,
+                                    parent)
+        
+        self.selectedItemsFilter = [ProjectBrowserFileItem,
+                                    ProjectBrowserSimpleDirectoryItem]
+        
+        self.setWindowTitle(self.tr('Resources'))
+
+        self.setWhatsThis(self.tr(
+            """<b>Project Resources Browser</b>"""
+            """<p>This allows to easily see all resources contained in the"""
+            """ current project. Several actions can be executed via the"""
+            """ context menu.</p>"""
+        ))
+        
+        self.compileProc = None
+        
+    def _createPopupMenus(self):
+        """
+        Protected overloaded method to generate the popup menu.
+        """
+        self.menuActions = []
+        self.multiMenuActions = []
+        self.dirMenuActions = []
+        self.dirMultiMenuActions = []
+        
+        self.menu = QMenu(self)
+        if self.project.getProjectType() in [
+            "PyQt5", "PyQt5C",
+            "PySide2", "PySide2C", "PySide6", "PySide6C"
+        ]:
+            self.menu.addAction(
+                self.tr('Compile resource'),
+                self.__compileResource)
+            self.menu.addAction(
+                self.tr('Compile all resources'),
+                self.__compileAllResources)
+            self.menu.addSeparator()
+            self.menu.addAction(
+                self.tr('Configure rcc Compiler'),
+                self.__configureRccCompiler)
+            self.menu.addSeparator()
+        else:
+            if self.hooks["compileResource"] is not None:
+                self.menu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileResource",
+                        self.tr('Compile resource')),
+                    self.__compileResource)
+            if self.hooks["compileAllResources"] is not None:
+                self.menu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileAllResources",
+                        self.tr('Compile all resources')),
+                    self.__compileAllResources)
+            if (
+                self.hooks["compileResource"] is not None or
+                self.hooks["compileAllResources"] is not None
+            ):
+                self.menu.addSeparator()
+        self.menu.addAction(self.tr('Open'), self.__openFile)
+        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 self.project.getProjectType() in [
+            "PyQt5", "PyQt5C",
+            "PySide2", "PySide2C", "PySide6", "PySide6C"
+        ]:
+            self.menu.addAction(
+                self.tr('New resource...'), self.__newResource)
+        else:
+            if self.hooks["newResource"] is not None:
+                self.menu.addAction(
+                    self.hooksMenuEntries.get(
+                        "newResource",
+                        self.tr('New resource...')), self.__newResource)
+        self.menu.addAction(
+            self.tr('Add resources...'), self.__addResourceFiles)
+        self.menu.addAction(
+            self.tr('Add resources directory...'),
+            self.__addResourcesDirectory)
+        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 self.project.getProjectType() in [
+            "PyQt5", "PyQt5C",
+            "PySide2", "PySide2C", "PySide6", "PySide6C"
+        ]:
+            self.backMenu.addAction(
+                self.tr('Compile all resources'),
+                self.__compileAllResources)
+            self.backMenu.addSeparator()
+            self.backMenu.addAction(
+                self.tr('Configure rcc Compiler'),
+                self.__configureRccCompiler)
+            self.backMenu.addSeparator()
+            self.backMenu.addAction(
+                self.tr('New resource...'), self.__newResource)
+        else:
+            if self.hooks["compileAllResources"] is not None:
+                self.backMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileAllResources",
+                        self.tr('Compile all resources')),
+                    self.__compileAllResources)
+                self.backMenu.addSeparator()
+            if self.hooks["newResource"] is not None:
+                self.backMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "newResource",
+                        self.tr('New resource...')), self.__newResource)
+        self.backMenu.addAction(
+            self.tr('Add resources...'), self.project.addResourceFiles)
+        self.backMenu.addAction(
+            self.tr('Add resources directory...'),
+            self.project.addResourceDir)
+        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 self.project.getProjectType() in [
+            "PyQt5", "PyQt5C",
+            "PySide2", "PySide2C", "PySide6", "PySide6C"
+        ]:
+            act = self.multiMenu.addAction(
+                self.tr('Compile resources'),
+                self.__compileSelectedResources)
+            self.multiMenu.addSeparator()
+            self.multiMenu.addAction(
+                self.tr('Configure rcc Compiler'),
+                self.__configureRccCompiler)
+            self.multiMenu.addSeparator()
+        else:
+            if self.hooks["compileSelectedResources"] is not None:
+                act = self.multiMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileSelectedResources",
+                        self.tr('Compile resources')),
+                    self.__compileSelectedResources)
+                self.multiMenu.addSeparator()
+        self.multiMenu.addAction(self.tr('Open'), self.__openFile)
+        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 self.project.getProjectType() in [
+            "PyQt5", "PyQt5C",
+            "PySide2", "PySide2C", "PySide6", "PySide6C"
+        ]:
+            self.dirMenu.addAction(
+                self.tr('Compile all resources'),
+                self.__compileAllResources)
+            self.dirMenu.addSeparator()
+            self.dirMenu.addAction(
+                self.tr('Configure rcc Compiler'),
+                self.__configureRccCompiler)
+            self.dirMenu.addSeparator()
+        else:
+            if self.hooks["compileAllResources"] is not None:
+                self.dirMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileAllResources",
+                        self.tr('Compile all resources')),
+                    self.__compileAllResources)
+                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()
+        self.dirMenu.addAction(
+            self.tr('New resource...'), self.__newResource)
+        self.dirMenu.addAction(
+            self.tr('Add resources...'), self.__addResourceFiles)
+        self.dirMenu.addAction(
+            self.tr('Add resources directory...'),
+            self.__addResourcesDirectory)
+        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 self.project.getProjectType() in [
+            "PyQt5", "PyQt5C",
+            "PySide2", "PySide2C", "PySide6", "PySide6C"
+        ]:
+            self.dirMultiMenu.addAction(
+                self.tr('Compile all resources'),
+                self.__compileAllResources)
+            self.dirMultiMenu.addSeparator()
+            self.dirMultiMenu.addAction(
+                self.tr('Configure rcc Compiler'),
+                self.__configureRccCompiler)
+            self.dirMultiMenu.addSeparator()
+        else:
+            if self.hooks["compileAllResources"] is not None:
+                self.dirMultiMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileAllResources",
+                        self.tr('Compile all resources')),
+                    self.__compileAllResources)
+                self.dirMultiMenu.addSeparator()
+        self.dirMultiMenu.addAction(
+            self.tr('Add resources...'),
+            self.project.addResourceFiles)
+        self.dirMultiMenu.addAction(
+            self.tr('Add resources directory...'),
+            self.project.addResourceDir)
+        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
+        
+        with contextlib.suppress(Exception):
+            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))
+        
+    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 __addResourceFiles(self):
+        """
+        Private method to add resource 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('resource', dn)
+        
+    def __addResourcesDirectory(self):
+        """
+        Private method to add resource 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('resource', dn)
+        
+    def _openItem(self):
+        """
+        Protected slot to handle the open popup menu entry.
+        """
+        self.__openFile()
+        
+    def __openFile(self):
+        """
+        Private slot to handle the Open menu action.
+        """
+        itmList = self.getSelectedItems()
+        for itm in itmList[:]:
+            if isinstance(itm, ProjectBrowserFileItem):
+                self.sourceFile.emit(itm.fileName())
+        
+    def __newResource(self):
+        """
+        Private slot to handle the New Resource 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["newResource"] is not None:
+            self.hooks["newResource"](path)
+        else:
+            fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+                self,
+                self.tr("New Resource"),
+                path,
+                self.tr("Qt Resource Files (*.qrc)"),
+                "",
+                EricFileDialog.DontConfirmOverwrite)
+            
+            if not fname:
+                # user aborted or didn't enter a filename
+                return
+            
+            fpath = pathlib.Path(fname)
+            if not fpath.suffix:
+                ex = selectedFilter.split("(*")[1].split(")")[0]
+                if ex:
+                    fpath = fpath.with_suffix(ex)
+            if fpath.exists():
+                res = EricMessageBox.yesNo(
+                    self,
+                    self.tr("New Resource"),
+                    self.tr("The file already exists! Overwrite it?"),
+                    icon=EricMessageBox.Warning)
+                if not res:
+                    # user selected to not overwrite
+                    return
+            
+            try:
+                newline = (None if self.project.useSystemEol()
+                           else self.project.getEolString())
+                with fpath.open('w', encoding="utf-8",
+                                newline=newline) as rcfile:
+                    rcfile.write('<!DOCTYPE RCC>\n')
+                    rcfile.write('<RCC version="1.0">\n')
+                    rcfile.write('<qresource>\n')
+                    rcfile.write('</qresource>\n')
+                    rcfile.write('</RCC>\n')
+            except OSError as e:
+                EricMessageBox.critical(
+                    self,
+                    self.tr("New Resource"),
+                    self.tr(
+                        "<p>The new resource file <b>{0}</b> could not"
+                        " be created.<br>Problem: {1}</p>")
+                    .format(fpath, str(e)))
+                return
+            
+            self.project.appendFile(str(fpath))
+            self.sourceFile.emit(str(fpath))
+        
+    def __deleteFile(self):
+        """
+        Private method to delete a resource 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 resources"),
+            self.tr(
+                "Do you really want to delete these resources 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 __readStdout(self):
+        """
+        Private slot to handle the readyReadStandardOutput signal of the
+        pyrcc5/pyside2-rcc/pyside6-rcc 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(),
+                            Preferences.getSystem("IOEncoding"),
+                            'replace')
+        
+    def __readStderr(self):
+        """
+        Private slot to handle the readyReadStandardError signal of the
+        pyrcc5/pyside2-rcc/pyside6-rcc 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.rccCompiler + ': '
+            error = str(self.compileProc.readLine(),
+                        ioEncoding, 'replace')
+            s += error
+            self.appendStderr.emit(s)
+        
+    def __compileQRCDone(self, exitCode, exitStatus):
+        """
+        Private slot to handle the finished signal of the compile process.
+        
+        @param exitCode exit code of the process (integer)
+        @param exitStatus exit status of the process (QProcess.ExitStatus)
+        """
+        self.compileRunning = False
+        ericApp().getObject("ViewManager").enableEditorsCheckFocusIn(True)
+        ui = ericApp().getObject("UserInterface")
+        if (
+            exitStatus == QProcess.ExitStatus.NormalExit and
+            exitCode == 0 and
+            self.buf
+        ):
+            ofn = os.path.join(self.project.ppath, self.compiledFile)
+            try:
+                newline = (None if self.project.useSystemEol()
+                           else 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("resourcesCompiler48"),
+                    self.tr("Resource Compilation"),
+                    self.tr("The compilation of the resource file"
+                            " was successful."))
+            except OSError as msg:
+                if not self.noDialog:
+                    EricMessageBox.information(
+                        self,
+                        self.tr("Resource Compilation"),
+                        self.tr(
+                            "<p>The compilation of the resource file"
+                            " failed.</p><p>Reason: {0}</p>").format(str(msg)))
+        else:
+            ui.showNotification(
+                UI.PixmapCache.getPixmap("resourcesCompiler48"),
+                self.tr("Resource Compilation"),
+                self.tr(
+                    "The compilation of the resource file failed."),
+                kind=NotificationTypes.CRITICAL,
+                timeout=0)
+        self.compileProc = None
+        
+    def __compileQRC(self, fn, noDialog=False, progress=None):
+        """
+        Private method to compile a .qrc file to a .py 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 = ""
+        
+        if self.project.getProjectLanguage() == "Python3":
+            if self.project.getProjectType() in ["PyQt5", "PyQt5C"]:
+                self.rccCompiler = Utilities.generatePyQtToolPath('pyrcc5')
+            elif self.project.getProjectType() in ["PySide2", "PySide2C"]:
+                self.rccCompiler = Utilities.generatePySideToolPath(
+                    'pyside2-rcc', variant=2)
+            elif self.project.getProjectType() in ["PySide6", "PySide6C"]:
+                self.rccCompiler = Utilities.generatePySideToolPath(
+                    'pyside6-rcc', variant=6)
+            else:
+                return None
+            defaultParameters = self.project.getDefaultRccCompilerParameters()
+            rccParameters = self.project.pdata["RCCPARAMS"]
+            if (
+                rccParameters["CompressionThreshold"] !=
+                    defaultParameters["CompressionThreshold"]
+            ):
+                args.append("-threshold")
+                args.append(str(rccParameters["CompressionThreshold"]))
+            if (
+                rccParameters["CompressLevel"] !=
+                    defaultParameters["CompressLevel"]
+            ):
+                args.append("-compress")
+                args.append(str(rccParameters["CompressLevel"]))
+            if (
+                rccParameters["CompressionDisable"] !=
+                    defaultParameters["CompressionDisable"]
+            ):
+                args.append("-no-compress")
+            if rccParameters["PathPrefix"] != defaultParameters["PathPrefix"]:
+                args.append("-root")
+                args.append(rccParameters["PathPrefix"])
+        else:
+            return None
+        
+        rcc = self.rccCompiler
+        
+        ofn, ext = os.path.splitext(fn)
+        fn = os.path.join(self.project.ppath, fn)
+        
+        dirname, filename = os.path.split(ofn)
+        if self.project.getProjectLanguage() == "Python3":
+            self.compiledFile = os.path.join(
+                dirname, self.RCFilenameFormatPython.format(filename))
+        elif self.project.getProjectLanguage() == "Ruby":
+            self.compiledFile = os.path.join(
+                dirname, self.RCFilenameFormatRuby.format(filename))
+        
+        args.append(fn)
+        self.compileProc.finished.connect(self.__compileQRCDone)
+        self.compileProc.readyReadStandardOutput.connect(self.__readStdout)
+        self.compileProc.readyReadStandardError.connect(self.__readStderr)
+        
+        self.noDialog = noDialog
+        self.compileProc.start(rcc, args)
+        procStarted = self.compileProc.waitForStarted(5000)
+        if procStarted:
+            self.compileRunning = True
+            ericApp().getObject("ViewManager").enableEditorsCheckFocusIn(False)
+            return self.compileProc
+        else:
+            self.compileRunning = False
+            if progress is not None:
+                progress.cancel()
+            EricMessageBox.critical(
+                self,
+                self.tr('Process Generation Error'),
+                self.tr(
+                    'Could not start {0}.<br>'
+                    'Ensure that it is in the search path.'
+                ).format(self.rccCompiler))
+            return None
+        
+    def __compileResource(self):
+        """
+        Private method to compile a resource to a source file.
+        """
+        itm = self.model().item(self.currentIndex())
+        fn2 = itm.fileName()
+        fn = self.project.getRelativePath(fn2)
+        if self.hooks["compileResource"] is not None:
+            self.hooks["compileResource"](fn)
+        else:
+            self.__compileQRC(fn)
+        
+    def __compileAllResources(self):
+        """
+        Private method to compile all resources to source files.
+        """
+        if self.hooks["compileAllResources"] is not None:
+            self.hooks["compileAllResources"](self.project.pdata["RESOURCES"])
+        else:
+            numResources = len(self.project.pdata["RESOURCES"])
+            progress = EricProgressDialog(
+                self.tr("Compiling resources..."),
+                self.tr("Abort"), 0, numResources,
+                self.tr("%v/%m Resources"), self)
+            progress.setModal(True)
+            progress.setMinimumDuration(0)
+            progress.setWindowTitle(self.tr("Resources"))
+            
+            for prog, fn in enumerate(self.project.pdata["RESOURCES"]):
+                progress.setValue(prog)
+                if progress.wasCanceled():
+                    break
+                proc = self.__compileQRC(fn, True, progress)
+                if proc is not None:
+                    while proc.state() == QProcess.ProcessState.Running:
+                        QThread.msleep(100)
+                        QApplication.processEvents()
+                else:
+                    break
+            progress.setValue(numResources)
+        
+    def __compileSelectedResources(self):
+        """
+        Private method to compile selected resources to source files.
+        """
+        items = self.getSelectedItems()
+        files = [self.project.getRelativePath(itm.fileName())
+                 for itm in items]
+        
+        if self.hooks["compileSelectedResources"] is not None:
+            self.hooks["compileSelectedResources"](files)
+        else:
+            numResources = len(files)
+            progress = EricProgressDialog(
+                self.tr("Compiling resources..."),
+                self.tr("Abort"), 0, numResources,
+                self.tr("%v/%m Resources"), self)
+            progress.setModal(True)
+            progress.setMinimumDuration(0)
+            progress.setWindowTitle(self.tr("Resources"))
+            
+            for prog, fn in enumerate(files):
+                progress.setValue(prog)
+                if progress.wasCanceled():
+                    break
+                if not fn.endswith('.ui.h'):
+                    proc = self.__compileQRC(fn, True, progress)
+                    if proc is not None:
+                        while proc.state() == QProcess.ProcessState.Running:
+                            QThread.msleep(100)
+                            QApplication.processEvents()
+                    else:
+                        break
+            progress.setValue(numResources)
+        
+    def __checkResourcesNewer(self, filename, mtime):
+        """
+        Private method to check, if any file referenced in a resource
+        file is newer than a given time.
+        
+        @param filename filename of the resource file (string)
+        @param mtime modification time to check against
+        @return flag indicating some file is newer (boolean)
+        """
+        try:
+            with open(filename, "r", encoding="utf-8") as f:
+                buf = f.read()
+        except OSError:
+            return False
+        
+        qrcDirName = os.path.dirname(filename)
+        lbuf = ""
+        for line in buf.splitlines():
+            line = line.strip()
+            if (
+                line.lower().startswith("<file>") or
+                line.lower().startswith("<file ")
+            ):
+                lbuf = line
+            elif lbuf:
+                lbuf = "{0}{1}".format(lbuf, line)
+            if lbuf.lower().endswith("</file>"):
+                rfile = lbuf.split(">", 1)[1].split("<", 1)[0]
+                if not os.path.isabs(rfile):
+                    rfile = os.path.join(qrcDirName, rfile)
+                if (
+                    os.path.exists(rfile) and
+                    os.stat(rfile).st_mtime > mtime
+                ):
+                    return True
+                
+                lbuf = ""
+        
+        return False
+        
+    def compileChangedResources(self):
+        """
+        Public method to compile all changed resources to source files.
+        """
+        if self.hooks["compileChangedResources"] is not None:
+            self.hooks["compileChangedResources"](
+                self.project.pdata["RESOURCES"])
+        else:
+            if len(self.project.pdata["RESOURCES"]) == 0:
+                # The project does not contain resource files
+                return
+            
+            progress = EricProgressDialog(
+                self.tr("Determining changed resources..."),
+                self.tr("Abort"), 0, 100, self.tr("%v/%m Resources"), self)
+            progress.setMinimumDuration(0)
+            progress.setWindowTitle(self.tr("Resources"))
+            
+            # get list of changed resources
+            changedResources = []
+            progress.setMaximum(len(self.project.pdata["RESOURCES"]))
+            for prog, fn in enumerate(self.project.pdata["RESOURCES"]):
+                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, self.RCFilenameFormatPython.format(filename))
+                elif self.project.getProjectLanguage() == "Ruby":
+                    dirname, filename = os.path.split(os.path.splitext(ifn)[0])
+                    ofn = os.path.join(
+                        dirname, self.RCFilenameFormatRuby.format(filename))
+                else:
+                    return
+                if (
+                    not os.path.exists(ofn) or
+                    os.stat(ifn).st_mtime > os.stat(ofn).st_mtime or
+                    self.__checkResourcesNewer(ifn, os.stat(ofn).st_mtime)
+                ):
+                    changedResources.append(fn)
+            progress.setValue(len(self.project.pdata["RESOURCES"]))
+            QApplication.processEvents()
+            
+            if changedResources:
+                progress.setLabelText(
+                    self.tr("Compiling changed resources..."))
+                progress.setMaximum(len(changedResources))
+                progress.setValue(0)
+                QApplication.processEvents()
+                for prog, fn in enumerate(changedResources):
+                    progress.setValue(prog)
+                    if progress.wasCanceled():
+                        break
+                    proc = self.__compileQRC(fn, True, progress)
+                    if proc is not None:
+                        while proc.state() == QProcess.ProcessState.Running:
+                            QThread.msleep(100)
+                            QApplication.processEvents()
+                    else:
+                        break
+                progress.setValue(len(changedResources))
+                QApplication.processEvents()
+        
+    def handlePreferencesChanged(self):
+        """
+        Public slot used to handle the preferencesChanged signal.
+        """
+        ProjectBaseBrowser.handlePreferencesChanged(self)
+    
+    def __configureRccCompiler(self):
+        """
+        Private slot to configure some non-common rcc compiler options.
+        """
+        from .RccCompilerOptionsDialog import RccCompilerOptionsDialog
+        
+        params = self.project.pdata["RCCPARAMS"]
+        
+        dlg = RccCompilerOptionsDialog(params)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            threshold, compression, noCompression, root = dlg.getData()
+            if threshold != params["CompressionThreshold"]:
+                params["CompressionThreshold"] = threshold
+                self.project.setDirty(True)
+            if compression != params["CompressLevel"]:
+                params["CompressLevel"] = compression
+                self.project.setDirty(True)
+            if noCompression != params["CompressionDisable"]:
+                params["CompressionDisable"] = noCompression
+                self.project.setDirty(True)
+            if root != params["PathPrefix"]:
+                params["PathPrefix"] = 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>compileResource: takes filename as parameter</li>
+        <li>compileAllResources: takes list of filenames as parameter</li>
+        <li>compileChangedResources: takes list of filenames as parameter</li>
+        <li>compileSelectedResources: takes list of all form filenames as
+            parameter</li>
+        <li>newResource: takes full directory path of new file as
+            parameter</li>
+        </ul>
+        
+        <b>Note</b>: Filenames are relative to the project directory, if not
+        specified differently.
+        """
+        self.hooks = {
+            "compileResource": None,
+            "compileAllResources": None,
+            "compileChangedResources": None,
+            "compileSelectedResources": None,
+            "newResource": None,
+        }

eric ide

mercurial