src/eric7/Project/Project.py

branch
eric7-maintenance
changeset 9654
7328efba128b
parent 9553
1e1a5cf4fafc
parent 9653
e67609152c5e
child 9725
b9a29a7aa820
--- a/src/eric7/Project/Project.py	Thu Dec 01 10:18:07 2022 +0100
+++ b/src/eric7/Project/Project.py	Mon Jan 02 11:16:03 2023 +0100
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+# Copyright (c) 2002 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 
 """
@@ -33,7 +33,7 @@
 from PyQt6.QtGui import QAction, QKeySequence
 from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu, QToolBar
 
-from eric7 import Globals, Preferences, Utilities
+from eric7 import Preferences, Utilities
 from eric7.CodeFormatting.BlackFormattingAction import BlackFormattingAction
 from eric7.CodeFormatting.BlackUtilities import aboutBlack
 from eric7.CodeFormatting.IsortFormattingAction import IsortFormattingAction
@@ -52,6 +52,12 @@
 from eric7.EricXML.UserProjectReader import UserProjectReader
 from eric7.Globals import recentNameProject
 from eric7.Sessions.SessionFile import SessionFile
+from eric7.SystemUtilities import (
+    FileSystemUtilities,
+    OSUtilities,
+    PythonUtilities,
+    QtUtilities,
+)
 from eric7.Tasks.TasksFile import TasksFile
 from eric7.UI import Config
 from eric7.UI.NotificationWidget import NotificationTypes
@@ -179,7 +185,7 @@
 
         self.__dbgFilters = {
             "Python3": self.tr(
-                "Python3 Files (*.py *.py3);;" "Python3 GUI Files (*.pyw *.pyw3);;"
+                "Python3 Files (*.py *.py3);;Python3 GUI Files (*.pyw *.pyw3);;"
             ),
         }
 
@@ -347,12 +353,12 @@
             "JavaScript": ["Other"],
         }
 
-        if Utilities.checkPyside(variant=2):
+        if QtUtilities.checkPyside(variant=2):
             self.__projectTypes["PySide2"] = self.tr("PySide2 GUI")
             self.__projectTypes["PySide2C"] = self.tr("PySide2 Console")
             self.__projectProgLanguages["Python3"].extend(["PySide2", "PySide2C"])
 
-        if Utilities.checkPyside(variant=6):
+        if QtUtilities.checkPyside(variant=6):
             self.__projectTypes["PySide6"] = self.tr("PySide6 GUI")
             self.__projectTypes["PySide6C"] = self.tr("PySide6 Console")
             self.__projectProgLanguages["Python3"].extend(["PySide6", "PySide6C"])
@@ -792,10 +798,30 @@
 
     def initFileTypes(self):
         """
-        Public method to initialize the filetype associations with default
+        Public method to initialize the file type associations with default
         values.
         """
-        self.__pdata["FILETYPES"] = {
+        self.__pdata["FILETYPES"] = self.defaultFileTypes(
+            self.__pdata["PROGLANGUAGE"],
+            self.__pdata["MIXEDLANGUAGE"],
+            self.__pdata["PROJECTTYPE"],
+        )
+        self.setDirty(True)
+
+    def defaultFileTypes(self, progLanguage, isMixed, projectType):
+        """
+        Public method to get a dictionary containing the default file type associations.
+
+        @param progLanguage programming language (main language)
+        @type str
+        @param isMixed flag indicating a project with multiple programming languages
+        @type bool
+        @param projectType type of the project
+        @type str
+        @return dictionary containing the default file type associations
+        @rtype dict
+        """
+        fileTypesDict = {
             "*.txt": "OTHERS",
             "*.md": "OTHERS",
             "*.rst": "OTHERS",
@@ -806,33 +832,31 @@
             "GNUmakefile": "OTHERS",
             "makefile": "OTHERS",
             "Makefile": "OTHERS",
+            "*.ini": "OTHERS",
+            "*.cfg": "OTHERS",
+            "*.toml": "OTHERS",
+            "*.json": "OTHERS",
+            "*.yml": "OTHERS",
+            "*.yaml": "OTHERS",
         }
 
         # Sources
-        sourceKey = (
-            "Mixed" if self.__pdata["MIXEDLANGUAGE"] else self.__pdata["PROGLANGUAGE"]
-        )
+        sourceKey = "Mixed" if isMixed else progLanguage
         for ext in self.__sourceExtensions(sourceKey):
-            self.__pdata["FILETYPES"]["*{0}".format(ext)] = "SOURCES"
-
-        # IDL interfaces
-        self.__pdata["FILETYPES"]["*.idl"] = "INTERFACES"
-
-        # Protobuf Files
-        self.__pdata["FILETYPES"]["*.proto"] = "PROTOCOLS"
+            fileTypesDict["*{0}".format(ext)] = "SOURCES"
 
         # Forms
-        if self.__pdata["PROJECTTYPE"] in [
+        if projectType in [
             "E7Plugin",
             "PyQt5",
             "PyQt6",
             "PySide2",
             "PySide6",
         ]:
-            self.__pdata["FILETYPES"]["*.ui"] = "FORMS"
+            fileTypesDict["*.ui"] = "FORMS"
 
         # Resources
-        if self.__pdata["PROJECTTYPE"] in [
+        if projectType in [
             "PyQt5",
             "PyQt5C",
             "PySide2",
@@ -840,10 +864,10 @@
             "PySide6",
             "PySide6C",
         ]:
-            self.__pdata["FILETYPES"]["*.qrc"] = "RESOURCES"
+            fileTypesDict["*.qrc"] = "RESOURCES"
 
         # Translations
-        if self.__pdata["PROJECTTYPE"] in [
+        if projectType in [
             "E7Plugin",
             "PyQt5",
             "PyQt5C",
@@ -854,16 +878,26 @@
             "PySide6",
             "PySide6C",
         ]:
-            self.__pdata["FILETYPES"]["*.ts"] = "TRANSLATIONS"
-            self.__pdata["FILETYPES"]["*.qm"] = "TRANSLATIONS"
-
+            fileTypesDict["*.ts"] = "TRANSLATIONS"
+            fileTypesDict["*.qm"] = "TRANSLATIONS"
+
+        # File categories handled by activated plugin project browsers
+        for fileCategory in [
+            f
+            for f in self.__fileCategoriesRepository.keys()
+            if f not in ["SOURCES", "FORMS", "RESOURCES", "TRANSLATIONS", "OTHERS"]
+        ]:
+            for ext in self.__fileCategoriesRepository[
+                fileCategory
+            ].fileCategoryExtensions:
+                fileTypesDict[ext] = fileCategory
         # Project type specific ones
         with contextlib.suppress(KeyError):
-            if self.__fileTypeCallbacks[self.__pdata["PROJECTTYPE"]] is not None:
-                ftypes = self.__fileTypeCallbacks[self.__pdata["PROJECTTYPE"]]()
-                self.__pdata["FILETYPES"].update(ftypes)
-
-        self.setDirty(True)
+            if self.__fileTypeCallbacks[projectType] is not None:
+                ftypes = self.__fileTypeCallbacks[projectType]()
+                fileTypesDict.update(ftypes)
+
+        return fileTypesDict
 
     def updateFileTypes(self):
         """
@@ -951,6 +985,15 @@
         """
         return self.vcs
 
+    def isVcsControlled(self):
+        """
+        Public method to check, if the project is controlled by a VCS.
+
+        @return flag indicating a VCS controlled project
+        @rtype bool
+        """
+        return self.vcs is not None
+
     def handlePreferencesChanged(self):
         """
         Public slot used to handle the preferencesChanged signal.
@@ -1055,7 +1098,7 @@
                     self.ui,
                     self.tr("Read Project File"),
                     self.tr(
-                        "<p>The project file <b>{0}</b> could not be read." "</p>"
+                        "<p>The project file <b>{0}</b> could not be read.</p>"
                     ).format(fn),
                 )
                 res = False
@@ -1338,7 +1381,7 @@
                         self.ui,
                         self.tr("Read Tasks"),
                         self.tr(
-                            "<p>The tasks file <b>{0}</b> could not be read." "</p>"
+                            "<p>The tasks file <b>{0}</b> could not be read.</p>"
                         ).format(fn),
                     )
 
@@ -1730,18 +1773,13 @@
 
         @param langFile the translation file to be removed (string)
         """
-        try:
-            from send2trash import send2trash as s2t  # __IGNORE_WARNING_I10__
-        except ImportError:
-            s2t = os.remove
-
         langFile = self.getRelativePath(langFile)
         qmFile = self.__binaryTranslationFile(langFile)
 
         try:
             fn = os.path.join(self.ppath, langFile)
             if os.path.exists(fn):
-                s2t(fn)
+                os.remove(fn)
         except OSError as err:
             EricMessageBox.critical(
                 self.ui,
@@ -1767,7 +1805,7 @@
                     )
                 fn = os.path.join(self.ppath, qmFile)
                 if os.path.exists(fn):
-                    s2t(fn)
+                    os.remove(fn)
             except OSError as err:
                 EricMessageBox.critical(
                     self.ui,
@@ -1868,7 +1906,7 @@
             if target != "":
                 for fn in fnames:
                     targetfile = os.path.join(target, os.path.basename(fn))
-                    if not Utilities.samepath(os.path.dirname(fn), target):
+                    if not FileSystemUtilities.samepath(os.path.dirname(fn), target):
                         try:
                             if not os.path.isdir(target):
                                 os.makedirs(target)
@@ -1943,7 +1981,9 @@
                 )
             return
 
-        if not Utilities.samepath(target, source) and not os.path.isdir(target):
+        if not FileSystemUtilities.samepath(target, source) and not os.path.isdir(
+            target
+        ):
             try:
                 os.makedirs(target)
             except OSError as why:
@@ -1963,7 +2003,7 @@
                     continue
 
             targetfile = os.path.join(target, os.path.basename(file))
-            if not Utilities.samepath(target, source):
+            if not FileSystemUtilities.samepath(target, source):
                 try:
                     if os.path.exists(targetfile):
                         res = EricMessageBox.yesNo(
@@ -1999,25 +2039,29 @@
         self.__addSingleDirectory(filetype, source, target, True)
 
         ignore_patterns = [
+            ".svn",
+            ".hg",
+            ".git",
+            ".ropeproject",
+            ".eric7project",
+            ".jedi",
+            "__pycache__",
+        ] + [
             pattern
             for pattern, filetype in self.__pdata["FILETYPES"].items()
             if filetype == "__IGNORE__"
         ]
 
         # now recurse into subdirectories
-        for name in os.listdir(source):
-            ns = os.path.join(source, name)
-            if os.path.isdir(ns):
-                skip = False
-                for ignore_pattern in ignore_patterns:
-                    if fnmatch.fnmatch(name, ignore_pattern):
-                        skip = True
-                        break
-                if skip:
-                    continue
-
-                nt = os.path.join(target, name)
-                self.__addRecursiveDirectory(filetype, ns, nt)
+        with os.scandir(source) as dirEntriesIterator:
+            for dirEntry in dirEntriesIterator:
+                if dirEntry.is_dir() and not any(
+                    fnmatch.fnmatch(dirEntry.name, ignore_pattern)
+                    for ignore_pattern in ignore_patterns
+                ):
+                    self.__addRecursiveDirectory(
+                        filetype, dirEntry.path, os.path.join(target, dirEntry.name)
+                    )
 
     @pyqtSlot()
     def addDirectory(self, fileTypeFilter=None, startdir=None):
@@ -2121,7 +2165,7 @@
             )
             if not newfn:
                 return False
-            newfn = Utilities.toNativeSeparators(newfn)
+            newfn = FileSystemUtilities.toNativeSeparators(newfn)
 
         if os.path.exists(newfn):
             res = EricMessageBox.yesNo(
@@ -2221,15 +2265,6 @@
 
         if reorganized:
             # copy the reorganized files back to the project
-            ##for key in [
-            ##"SOURCES",
-            ##"FORMS",
-            ##"INTERFACES",
-            ##"PROTOCOLS",
-            ##"RESOURCES",
-            ##"OTHERS",
-            ##"TRANSLATIONS",
-            ##]:
             for fileCategory in self.getFileCategories():
                 self.__pdata[fileCategory] = newPdata[fileCategory][:]
 
@@ -2246,14 +2281,6 @@
         """
         olddn = self.getRelativePath(olddn)
         newdn = self.getRelativePath(newdn)
-        ##for key in [
-        ##"SOURCES",
-        ##"FORMS",
-        ##"INTERFACES",
-        ##"PROTOCOLS",
-        ##"RESOURCES",
-        ##"OTHERS",
-        ##]:
         for fileCategory in [
             c for c in self.getFileCategories() if c != "TRANSLATIONS"
         ]:
@@ -2275,14 +2302,6 @@
         olddn = self.getRelativePath(olddn)
         newdn = self.getRelativePath(newdn)
         typeStrings = []
-        ##for key in [
-        ##"SOURCES",
-        ##"FORMS",
-        ##"INTERFACES",
-        ##"PROTOCOLS",
-        ##"RESOURCES",
-        ##"OTHERS",
-        ##]:
         for fileCategory in [
             c for c in self.getFileCategories() if c != "TRANSLATIONS"
         ]:
@@ -2363,27 +2382,22 @@
         @return flag indicating success (boolean)
         """
         try:
-            from send2trash import send2trash as s2t  # __IGNORE_WARNING_I10__
-        except ImportError:
-            s2t = os.remove
-
-        try:
-            s2t(os.path.join(self.ppath, fn))
+            os.remove(os.path.join(self.ppath, fn))
             path, ext = os.path.splitext(fn)
             if ext == ".ui":
                 fn2 = os.path.join(self.ppath, "{0}.h".format(fn))
                 if os.path.isfile(fn2):
-                    s2t(fn2)
+                    os.remove(fn2)
             head, tail = os.path.split(path)
             for ext in [".pyc", ".pyo"]:
                 fn2 = os.path.join(self.ppath, path + ext)
                 if os.path.isfile(fn2):
-                    s2t(fn2)
+                    os.remove(fn2)
                 pat = os.path.join(
                     self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext)
                 )
                 for f in glob.glob(pat):
-                    s2t(f)
+                    os.remove(f)
         except OSError as err:
             EricMessageBox.critical(
                 self.ui,
@@ -2410,12 +2424,7 @@
         if not os.path.isabs(dn):
             dn = os.path.join(self.ppath, dn)
         try:
-            try:
-                from send2trash import send2trash  # __IGNORE_WARNING_I10__
-
-                send2trash(dn)
-            except ImportError:
-                shutil.rmtree(dn, True)
+            shutil.rmtree(dn, True)
         except OSError as err:
             EricMessageBox.critical(
                 self.ui,
@@ -2458,7 +2467,7 @@
         if not self.checkDirty():
             return
 
-        dlg = PropertiesDialog(self, True)
+        dlg = PropertiesDialog(self, new=True)
         if dlg.exec() == QDialog.DialogCode.Accepted:
             self.closeProject()
 
@@ -2803,6 +2812,17 @@
                 self.__createEmbeddedEnvironment()
             self.menuEnvironmentAct.setEnabled(self.__pdata["EMBEDDED_VENV"])
 
+            self.projectOpenedHooks.emit()
+            self.projectOpened.emit()
+
+            # open the main script
+            if self.__pdata["MAINSCRIPT"]:
+                if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
+                    ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                else:
+                    ms = self.__pdata["MAINSCRIPT"]
+                self.sourceFile.emit(ms)
+
     def newProjectAddFiles(self, mainscript):
         """
         Public method to add files to a new project.
@@ -2812,11 +2832,22 @@
         # Show the file type associations for the user to change
         self.__showFiletypeAssociations()
 
+        ignoreList = []
+        if self.__pdata["EMBEDDED_VENV"]:
+            environmentPath = self.__findEmbeddedEnvironment()
+            if environmentPath:
+                # there is already an embeded venv; ignore thie whenn adding files
+                ignoreList = [os.path.split(environmentPath)[-1]]
         with EricOverrideCursor():
             # search the project directory for files with known extensions
             filespecs = list(self.__pdata["FILETYPES"].keys())
             for filespec in filespecs:
-                files = Utilities.direntries(self.ppath, True, filespec)
+                files = FileSystemUtilities.direntries(
+                    self.ppath,
+                    filesonly=True,
+                    pattern=filespec,
+                    ignore=ignoreList,
+                )
                 for file in files:
                     self.appendFile(file)
 
@@ -2836,10 +2867,10 @@
                     tpd = self.__pdata["TRANSLATIONPATTERN"].split("%language%")[0]
             else:
                 pattern = "*.ts"
-            tslist.extend(Utilities.direntries(tpd, True, pattern))
+            tslist.extend(FileSystemUtilities.direntries(tpd, True, pattern))
             pattern = self.__binaryTranslationFile(pattern)
             if pattern:
-                tslist.extend(Utilities.direntries(tpd, True, pattern))
+                tslist.extend(FileSystemUtilities.direntries(tpd, True, pattern))
             if tslist:
                 if "_" in os.path.basename(tslist[0]):
                     # the first entry determines the mainscript name
@@ -2888,7 +2919,7 @@
                             self.__pdata["TRANSLATIONPATTERN"]
                         ).replace("%language%", "*")
                         pattern = self.__binaryTranslationFile(pattern)
-                        qmlist = Utilities.direntries(tpd, True, pattern)
+                        qmlist = FileSystemUtilities.direntries(tpd, True, pattern)
                         for qm in qmlist:
                             self.__pdata["TRANSLATIONS"].append(qm)
                             self.projectFileAdded.emit(qm, "TRANSLATIONS")
@@ -2905,9 +2936,9 @@
         """
         from .PropertiesDialog import PropertiesDialog
 
-        dlg = PropertiesDialog(self, False)
+        dlg = PropertiesDialog(self, new=False)
         if dlg.exec() == QDialog.DialogCode.Accepted:
-            projectType = self.__pdata["PROJECTTYPE"]
+            fileTypesDict = copy.copy(self.__pdata["FILETYPES"])
             dlg.storeData()
             self.setDirty(True)
             if self.__pdata["MAINSCRIPT"]:
@@ -2940,10 +2971,6 @@
                         )
                 self.appendFile(mf)
 
-            if self.__pdata["PROJECTTYPE"] != projectType:
-                # reinitialize filetype associations
-                self.initFileTypes()
-
             if self.translationsRoot:
                 tp = os.path.join(self.ppath, self.translationsRoot)
                 if not self.translationsRoot.endswith(os.sep):
@@ -2967,7 +2994,7 @@
             self.__model.projectPropertiesChanged()
             self.projectPropertiesChanged.emit()
 
-            if self.__pdata["PROJECTTYPE"] != projectType:
+            if self.__pdata["FILETYPES"] != fileTypesDict:
                 self.__reorganizeFiles()
 
             if self.__pdata["EMBEDDED_VENV"] and not self.__findEmbeddedEnvironment():
@@ -3020,9 +3047,10 @@
         """
         from .FiletypeAssociationDialog import FiletypeAssociationDialog
 
-        dlg = FiletypeAssociationDialog(self)
+        dlg = FiletypeAssociationDialog(self, self.getProjectData(dataKey="FILETYPES"))
         if dlg.exec() == QDialog.DialogCode.Accepted:
-            dlg.transferData()
+            fileTypes = dlg.getData()
+            self.setProjectData(fileTypes, dataKey="FILETYPES")
             self.setDirty(True)
             self.__reorganizeFiles()
 
@@ -3105,7 +3133,7 @@
             fn = EricFileDialog.getOpenFileName(
                 self.parent(),
                 self.tr("Open project"),
-                Preferences.getMultiProject("Workspace") or Utilities.getHomeDir(),
+                Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir(),
                 self.tr("Project Files (*.epj);;XML Project Files (*.e4p)"),
             )
 
@@ -3155,7 +3183,7 @@
                                         res, vcs_ok = QInputDialog.getItem(
                                             None,
                                             self.tr("New Project"),
-                                            self.tr("Select Version Control" " System"),
+                                            self.tr("Select Version Control System"),
                                             vcsList,
                                             0,
                                             False,
@@ -3261,6 +3289,8 @@
 
                 # start the VCS monitor thread
                 self.__vcsConnectStatusMonitor()
+            else:
+                self.__initData()  # delete all invalid data read
 
     def reopenProject(self):
         """
@@ -3301,7 +3331,7 @@
         defaultPath = (
             self.ppath
             if self.ppath
-            else (Preferences.getMultiProject("Workspace") or Utilities.getHomeDir())
+            else (Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir())
         )
         fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
             self.parent(),
@@ -3739,8 +3769,12 @@
         """
         return bool(self.ppath) and (
             path == self.ppath
-            or Utilities.normcasepath(Utilities.toNativeSeparators(path)).startswith(
-                Utilities.normcasepath(Utilities.toNativeSeparators(self.ppath + "/"))
+            or FileSystemUtilities.normcasepath(
+                FileSystemUtilities.toNativeSeparators(path)
+            ).startswith(
+                FileSystemUtilities.normcasepath(
+                    FileSystemUtilities.toNativeSeparators(self.ppath + "/")
+                )
             )
         )
 
@@ -3816,7 +3850,7 @@
         @return project relative path or unchanged path, if path doesn't
             belong to the project (string)
         """
-        return Utilities.fromNativeSeparators(self.getRelativePath(path))
+        return FileSystemUtilities.fromNativeSeparators(self.getRelativePath(path))
 
     def getAbsolutePath(self, fn):
         """
@@ -3839,7 +3873,7 @@
         @return absolute path (string)
         """
         if not os.path.isabs(fn):
-            fn = os.path.join(self.ppath, Utilities.toNativeSeparators(fn))
+            fn = os.path.join(self.ppath, FileSystemUtilities.toNativeSeparators(fn))
         return fn
 
     def getEolString(self):
@@ -3956,7 +3990,7 @@
                     .getVirtualenvInterpreter(venvName)
                 )
         if not interpreter and resolveGlobal:
-            interpreter = Globals.getPythonExecutable()
+            interpreter = PythonUtilities.getPythonExecutable()
 
         return interpreter
 
@@ -4051,7 +4085,7 @@
         ):
             return True
 
-        if Utilities.isWindowsPlatform():
+        if OSUtilities.isWindowsPlatform():
             # try the above case-insensitive
             newfn = newfn.lower()
             if any(entry.lower() == newfn for entry in self.__pdata[group]):
@@ -4279,9 +4313,8 @@
         act.setWhatsThis(
             self.tr(
                 """<b>Search new files...</b>"""
-                """<p>This searches for new files (sources, *.ui, *.idl,"""
-                """ *.proto) in the project directory and registered"""
-                """ subdirectories.</p>"""
+                """<p>This searches for new files (sources, forms, ...) in the"""
+                """ project directory and registered subdirectories.</p>"""
             )
         )
         act.triggered.connect(self.__searchNewFiles)
@@ -5141,7 +5174,9 @@
                 " is cleared first.</p>"
             )
         )
-        self.recreateVenvAct.triggered.connect(self.__createEmbeddedEnvironment)
+        self.recreateVenvAct.triggered.connect(
+            lambda: self.__createEmbeddedEnvironment(force=True)
+        )
         self.actions.append(self.recreateVenvAct)
 
         self.reloadAct.setEnabled(False)
@@ -5380,7 +5415,7 @@
         with the central store.
         """
         for recent in self.recent[:]:
-            if Utilities.samepath(self.pfile, recent):
+            if FileSystemUtilities.samepath(self.pfile, recent):
                 self.recent.remove(recent)
         self.recent.insert(0, self.pfile)
         maxRecent = Preferences.getProject("RecentNumber")
@@ -5400,7 +5435,7 @@
             formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}"
             act = self.recentMenu.addAction(
                 formatStr.format(
-                    idx, Utilities.compactPath(rp, self.ui.maxMenuFilePathLen)
+                    idx, FileSystemUtilities.compactPath(rp, self.ui.maxMenuFilePathLen)
                 )
             )
             act.setData(rp)
@@ -5510,7 +5545,7 @@
                 if ns.startswith("."):
                     continue
                 if (
-                    Utilities.isWindowsPlatform()
+                    OSUtilities.isWindowsPlatform()
                     and os.path.isdir(os.path.join(curpath, ns))
                     and ns.startswith("_")
                 ):
@@ -6129,7 +6164,9 @@
                 lst.extend(
                     [
                         self.getRelativePath(p)
-                        for p in Utilities.direntries(self.getAbsolutePath(entry), True)
+                        for p in FileSystemUtilities.direntries(
+                            self.getAbsolutePath(entry), True
+                        )
                     ]
                 )
                 continue
@@ -6153,7 +6190,9 @@
             with open(pkglist, "w", encoding="utf-8", newline=newline) as pkglistFile:
                 pkglistFile.write("\n".join(header) + "\n")
                 pkglistFile.write(
-                    "\n".join([Utilities.fromNativeSeparators(f) for f in lst])
+                    "\n".join(
+                        [FileSystemUtilities.fromNativeSeparators(f) for f in lst]
+                    )
                 )
                 pkglistFile.write("\n")
                 # ensure the file ends with an empty line
@@ -6364,7 +6403,7 @@
                 EricPixmapCache.getPixmap("pluginArchive48"),
                 self.tr("Create Plugin Archive"),
                 self.tr(
-                    "<p>The eric plugin archive files were " "created successfully.</p>"
+                    "<p>The eric plugin archive files were created successfully.</p>"
                 ),
             )
 
@@ -6667,24 +6706,6 @@
         self.__makeProcess = None
 
     #########################################################################
-    ## Below are methods implementing some 'IDL' support functions
-    #########################################################################
-
-    def hasDefaultIdlCompilerParameters(self):
-        """
-        Public method to test, if the project contains the default IDL compiler
-        parameters.
-
-        @return flag indicating default parameter set
-        @rtype bool
-        """
-        return self.__pdata["IDLPARAMS"] == {
-            "IncludeDirs": [],
-            "DefinedNames": [],
-            "UndefinedNames": [],
-        }
-
-    #########################################################################
     ## Below are methods implementing some 'UIC' support functions
     #########################################################################
 
@@ -6782,7 +6803,7 @@
         """
         Private slot to create a SBOM file of the project dependencies.
         """
-        import CycloneDXInterface  # __IGNORE_WARNING_I102__
+        from eric7 import CycloneDXInterface
 
         CycloneDXInterface.createCycloneDXFile("<project>")
 
@@ -6914,6 +6935,15 @@
         @return path of the embedded virtual environment (empty if not found)
         @rtype str
         """
+        with os.scandir(self.getProjectPath()) as ppathDirEntriesIterator:
+            for dirEntry in ppathDirEntriesIterator:
+                # potential venv directory; check for 'pyvenv.cfg'
+                if dirEntry.is_dir() and os.path.exists(
+                    os.path.join(dirEntry.path, "pyvenv.cfg")
+                ):
+                    return dirEntry.path
+
+        # check for some common names in case 'pyvenv.cfg' is missing
         for venvPathName in (".venv", "venv", ".env", "env"):
             venvPath = os.path.join(self.getProjectPath(), venvPathName)
             if os.path.isdir(venvPath):
@@ -6943,12 +6973,14 @@
             "system_site_packages": False,
         }
 
-    def __createEmbeddedEnvironment(self, upgrade=False):
+    def __createEmbeddedEnvironment(self, upgrade=False, force=False):
         """
         Private method to create the embedded virtual environment.
 
         @param upgrade flag indicating an upgrade operation (defaults to False)
         @type bool (optional)
+        @param force flag indicating to force the creation (defaults to False)
+        @type bool (optional)
         """
         from eric7.VirtualEnv.VirtualenvExecDialog import VirtualenvExecDialog
 
@@ -6956,52 +6988,60 @@
             ProjectVenvCreationParametersDialog,
         )
 
-        dlg = ProjectVenvCreationParametersDialog(
-            withSystemSitePackages=self.__venvConfiguration["system_site_packages"]
-        )
-        if dlg.exec() != QDialog.DialogCode.Accepted:
-            # user canceled the environment creation
-            self.__setEmbeddedEnvironmentProjectConfig(False)
-            return
-
-        pythonPath, withSystemSitePackages = dlg.getData()
-        configuration = {
-            "envType": "pyvenv",
-            "targetDirectory": os.path.join(self.getProjectPath(), ".venv"),
-            "openTarget": False,
-            "createLog": True,
-            "createScript": True,
-            "logicalName": self.__venvConfiguration["name"],
-            "pythonExe": pythonPath,
-        }
-
-        args = []
-        if upgrade:
-            args.append("--upgrade")
-        else:
-            if os.path.exists(os.path.join(self.getProjectPath(), ".venv")):
-                args.append("--clear")
-        if withSystemSitePackages:
-            args.append("--system-site-packages")
-        args.append(configuration["targetDirectory"])
-        dia = VirtualenvExecDialog(configuration, None)
-        dia.show()
-        dia.start(args)
-        dia.exec()
-
-        self.__venvConfiguration["system_site_packages"] = withSystemSitePackages
-
-        self.__configureEnvironment()
-        if not self.__venvConfiguration["interpreter"]:
-            # user canceled the environment creation, delete the created directory
-            shutil.rmtree(configuration["targetDirectory"], True)
-            self.__setEmbeddedEnvironmentProjectConfig(False)
-            return
-
-        if upgrade and not withSystemSitePackages:
-            # re-install the project into the upgraded environment
-            # Note: seems to fail on some systems with access to system site-packages
-            self.__installProjectIntoEnvironment()
+        environmentPath = self.__findEmbeddedEnvironment()
+        if force or upgrade or not environmentPath:
+            dlg = ProjectVenvCreationParametersDialog(
+                withSystemSitePackages=self.__venvConfiguration["system_site_packages"]
+            )
+            if dlg.exec() != QDialog.DialogCode.Accepted:
+                # user canceled the environment creation
+                self.__setEmbeddedEnvironmentProjectConfig(False)
+                return
+
+            pythonPath, withSystemSitePackages = dlg.getData()
+            configuration = {
+                "envType": "pyvenv",
+                "targetDirectory": os.path.join(self.getProjectPath(), ".venv"),
+                "openTarget": False,
+                "createLog": True,
+                "createScript": True,
+                "logicalName": self.__venvConfiguration["name"],
+                "pythonExe": pythonPath,
+            }
+
+            args = []
+            if upgrade:
+                args.append("--upgrade")
+            else:
+                if os.path.exists(os.path.join(self.getProjectPath(), ".venv")):
+                    args.append("--clear")
+            if withSystemSitePackages:
+                args.append("--system-site-packages")
+            args.append(configuration["targetDirectory"])
+            dia = VirtualenvExecDialog(configuration, None)
+            dia.show()
+            dia.start(args)
+            dia.exec()
+
+            self.__venvConfiguration["system_site_packages"] = withSystemSitePackages
+
+            self.__configureEnvironment()
+            if not self.__venvConfiguration["interpreter"]:
+                # user canceled the environment creation, delete the created directory
+                shutil.rmtree(configuration["targetDirectory"], True)
+                self.__setEmbeddedEnvironmentProjectConfig(False)
+                return
+
+            if upgrade and not withSystemSitePackages:
+                # re-install the project into the upgraded environment
+                # Note: seems to fail on some systems with access to system
+                #       site-packages
+                self.__installProjectIntoEnvironment()
+
+        if environmentPath and not self.__venvConfiguration["interpreter"].startswith(
+            environmentPath
+        ):
+            self.__configureEnvironment(environmentPath)
 
     @pyqtSlot()
     def __configureEnvironment(self, environmentPath=""):
@@ -7014,7 +7054,9 @@
         from .ProjectVenvConfigurationDialog import ProjectVenvConfigurationDialog
 
         if not environmentPath:
-            environmentPath = os.path.join(self.getProjectPath(), ".venv")
+            environmentPath = self.__findEmbeddedEnvironment()
+            if not environmentPath:
+                environmentPath = os.path.join(self.getProjectPath(), ".venv")
 
         dlg = ProjectVenvConfigurationDialog(
             self.__venvConfiguration["name"],

eric ide

mercurial