Continued refactoring of the project browser related code in order to extract some as plugins later on. eric7

Wed, 16 Nov 2022 18:11:52 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 16 Nov 2022 18:11:52 +0100
branch
eric7
changeset 9516
0f023e61a9b5
parent 9515
275334bc9607
child 9517
d73c3a1e432b

Continued refactoring of the project browser related code in order to extract some as plugins later on.

src/eric7/EricXML/ProjectReader.py file | annotate | diff | comparison | revisions
src/eric7/Project/AddDirectoryDialog.py file | annotate | diff | comparison | revisions
src/eric7/Project/AddFileDialog.py file | annotate | diff | comparison | revisions
src/eric7/Project/FiletypeAssociationDialog.py file | annotate | diff | comparison | revisions
src/eric7/Project/Project.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectBrowserModel.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectFormsBrowser.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectInterfacesBrowser.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectOthersBrowser.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectProtocolsBrowser.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectResourcesBrowser.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectSourcesBrowser.py file | annotate | diff | comparison | revisions
src/eric7/Project/QuickFindFileDialog.py file | annotate | diff | comparison | revisions
src/eric7/UI/FindFileWidget.py file | annotate | diff | comparison | revisions
--- a/src/eric7/EricXML/ProjectReader.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/EricXML/ProjectReader.py	Wed Nov 16 18:11:52 2022 +0100
@@ -38,6 +38,8 @@
         """
         Public method to read and parse the XML document.
         """
+        fileCategoryTags = [s.capitalize() for s in self.project.getFileCategories()]
+
         while not self.atEnd():
             self.readNext()
             if self.isStartElement():
@@ -47,87 +49,89 @@
                         self.raiseUnsupportedFormatVersion(self.version)
                 elif self.name() == "Language":
                     self.project.setProjectData(
-                        self.readElementText(), dataKey="SPELLLANGUAGE"
+                        self.readElementText(), dataKey="SPELLLANGUAGE", setDirty=False
                     )
                 elif self.name() == "ProjectWordList":
                     self.project.setProjectData(
                         Utilities.toNativeSeparators(self.readElementText()),
                         dataKey="SPELLWORDS",
+                        setDirty=False,
                     )
                 elif self.name() == "ProjectExcludeList":
                     self.project.setProjectData(
                         Utilities.toNativeSeparators(self.readElementText()),
                         dataKey="SPELLEXCLUDES",
+                        setDirty=False,
                     )
                 elif self.name() == "Hash":
-                    self.project.setProjectData(self.readElementText(), dataKey="HASH")
+                    self.project.setProjectData(
+                        self.readElementText(), dataKey="HASH", setDirty=False
+                    )
                 elif self.name() == "ProgLanguage":
                     self.project.setProjectData(
                         int(self.attribute("mixed", "0")),
                         dataKey="MIXEDLANGUAGE",
+                        setDirty=False,
                     )
                     self.project.setProjectData(
-                        self.readElementText(), dataKey="PROGLANGUAGE"
+                        self.readElementText(), dataKey="PROGLANGUAGE", setDirty=False
                     )
                     if self.project.getProjectData(dataKey="PROGLANGUAGE") == "Python":
                         # convert Python to the more specific Python3
-                        self.project.setProjectData("Python3", dataKey="PROGLANGUAGE")
+                        self.project.setProjectData(
+                            "Python3", dataKey="PROGLANGUAGE", setDirty=False
+                        )
                 elif self.name() == "ProjectType":
                     self.project.setProjectData(
-                        self.readElementText(), dataKey="PROJECTTYPE"
+                        self.readElementText(), dataKey="PROJECTTYPE", setDirty=False
                     )
                 elif self.name() == "Description":
                     self.project.setProjectData(
-                        self.readElementText(), dataKey="DESCRIPTION"
+                        self.readElementText(), dataKey="DESCRIPTION", setDirty=False
                     )
                 elif self.name() == "Version":
                     self.project.setProjectData(
-                        self.readElementText(), dataKey="VERSION"
+                        self.readElementText(), dataKey="VERSION", setDirty=False
                     )
                 elif self.name() == "Author":
                     self.project.setProjectData(
-                        self.readElementText(), dataKey="AUTHOR"
+                        self.readElementText(), dataKey="AUTHOR", setDirty=False
                     )
                 elif self.name() == "Email":
-                    self.project.setProjectData(self.readElementText(), dataKey="EMAIL")
+                    self.project.setProjectData(
+                        self.readElementText(), dataKey="EMAIL", setDirty=False
+                    )
                 elif self.name() == "TranslationPattern":
                     self.project.setProjectData(
                         Utilities.toNativeSeparators(self.readElementText()),
                         dataKey="TRANSLATIONPATTERN",
+                        setDirty=False,
                     )
                 elif self.name() == "TranslationsBinPath":
                     self.project.setProjectData(
                         Utilities.toNativeSeparators(self.readElementText()),
                         dataKey="TRANSLATIONSBINPATH",
+                        setDirty=False,
                     )
                 elif self.name() == "Eol":
                     self.project.setProjectData(
-                        int(self.attribute("index", "0")), dataKey="EOL"
+                        int(self.attribute("index", "0")), dataKey="EOL", setDirty=False
                     )
-                elif self.name() == "Sources":
-                    self.__readFiles("Sources", "Source", "SOURCES")
-                elif self.name() == "Forms":
-                    self.__readFiles("Forms", "Form", "FORMS")
-                elif self.name() == "Translations":
-                    self.__readFiles("Translations", "Translation", "TRANSLATIONS")
+                elif self.name() in fileCategoryTags:
+                    self.__readFiles(
+                        self.name(), self.name()[:-1], self.name().upper()
+                    )
                 elif self.name() == "TranslationExceptions":
                     self.__readFiles(
                         "TranslationExceptions",
                         "TranslationException",
                         "TRANSLATIONEXCEPTIONS",
                     )
-                elif self.name() == "Resources":
-                    self.__readFiles("Resources", "Resource", "RESOURCES")
-                elif self.name() == "Interfaces":
-                    self.__readFiles("Interfaces", "Interface", "INTERFACES")
-                elif self.name() == "Protocols":
-                    self.__readFiles("Protocols", "Protocol", "PROTOCOLS")
-                elif self.name() == "Others":
-                    self.__readFiles("Others", "Other", "OTHERS")
                 elif self.name() == "MainScript":
                     self.project.setProjectData(
                         Utilities.toNativeSeparators(self.readElementText()),
                         dataKey="MAINSCRIPT",
+                        setDirty=False,
                     )
                 elif self.name() == "Vcs":
                     self.__readVcs()
@@ -151,7 +155,7 @@
                     )
                 elif self.name() == "DocstringStyle":
                     self.project.setProjectData(
-                        self.readElementText(), dataKey="DOCSTRING"
+                        self.readElementText(), dataKey="DOCSTRING", setDirty=False
                     )
                 elif self.name() == "ProjectTypeSpecific":
                     self.__readBasicDataField(
@@ -199,7 +203,9 @@
                     fileList.append(
                         Utilities.toNativeSeparators(self.readElementText())
                     )
-                    self.project.setProjectData(fileList, dataKey=dataKey)
+                    self.project.setProjectData(
+                        fileList, dataKey=dataKey, setDirty=False
+                    )
                 else:
                     self.raiseUnexpectedStartTag(self.name())
 
@@ -218,7 +224,9 @@
 
             if self.isStartElement():
                 if self.name() == dataTag:
-                    self.project.setProjectData(self._readBasics(), dataKey=dataKey)
+                    self.project.setProjectData(
+                        self._readBasics(), dataKey=dataKey, setDirty=False
+                    )
                 else:
                     self.raiseUnexpectedStartTag(self.name())
 
@@ -233,14 +241,16 @@
 
             if self.isStartElement():
                 if self.name() == "VcsType":
-                    self.project.setProjectData(self.readElementText(), dataKey="VCS")
+                    self.project.setProjectData(
+                        self.readElementText(), dataKey="VCS", setDirty=False
+                    )
                 elif self.name() == "VcsOptions":
                     self.project.setProjectData(
-                        self._readBasics(), dataKey="VCSOPTIONS"
+                        self._readBasics(), dataKey="VCSOPTIONS", setDirty=False
                     )
                 elif self.name() == "VcsOtherData":
                     self.project.setProjectData(
-                        self._readBasics(), dataKey="VCSOTHERDATA"
+                        self._readBasics(), dataKey="VCSOTHERDATA", setDirty=False
                     )
                 else:
                     self.raiseUnexpectedStartTag(self.name())
@@ -261,7 +271,9 @@
                     if pattern:
                         fileTypes = self.project.getProjectData(dataKey="FILETYPES")
                         fileTypes[pattern] = filetype
-                        self.project.setProjectData(fileTypes, dataKey="FILETYPES")
+                        self.project.setProjectData(
+                            fileTypes, dataKey="FILETYPES", setDirty=False
+                        )
                 else:
                     self.raiseUnexpectedStartTag(self.name())
 
@@ -281,6 +293,8 @@
                     if pattern:
                         assocs = self.project.getProjectData(dataKey="LEXERASSOCS")
                         assocs[pattern] = lexer
-                        self.project.setProjectData(assocs, dataKey="LEXERASSOCS")
+                        self.project.setProjectData(
+                            assocs, dataKey="LEXERASSOCS", setDirty=False
+                        )
                 else:
                     self.raiseUnexpectedStartTag(self.name())
--- a/src/eric7/Project/AddDirectoryDialog.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/AddDirectoryDialog.py	Wed Nov 16 18:11:52 2022 +0100
@@ -21,7 +21,7 @@
     """
 
     def __init__(
-        self, pro, fileTypeFilter="source", parent=None, name=None, startdir=None
+        self, pro, fileTypeFilter="SOURCES", parent=None, name=None, startdir=None
     ):
         """
         Constructor
@@ -42,37 +42,30 @@
         self.targetDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.targetDirPicker.setDefaultDirectory(startdir)
 
-        self.ppath = pro.ppath
-        self.targetDirPicker.setText(self.ppath)
-        self.on_filterComboBox_highlighted(0)
-        # enable all dialog elements
-        if fileTypeFilter == "source":  # it is a source file
-            self.filterComboBox.addItem(self.tr("Source Files"), "SOURCES")
-        elif fileTypeFilter == "form":
-            self.filterComboBox.addItem(self.tr("Forms Files"), "FORMS")
-        elif fileTypeFilter == "resource":
-            self.filterComboBox.addItem(self.tr("Resource Files"), "RESOURCES")
-        elif fileTypeFilter == "interface":
-            self.filterComboBox.addItem(self.tr("Interface Files"), "INTERFACES")
-        elif fileTypeFilter == "protocol":
-            self.filterComboBox.addItem(self.tr("Protocol Files"), "PROTOCOLS")
-        elif fileTypeFilter == "others":
-            self.filterComboBox.addItem(self.tr("Other Files (*)"), "OTHERS")
-            self.on_filterComboBox_highlighted(self.filterComboBox.count() - 1)
+        self.__project = pro
+        self.targetDirPicker.setText(self.__project.getProjectPath())
+
+        if fileTypeFilter and fileTypeFilter != "TRANSLATIONS":
+            self.filterComboBox.addItem(
+                self.__project.getFileCategoryString(fileTypeFilter),
+                fileTypeFilter,
+            )
         else:
-            self.filterComboBox.addItem(self.tr("Source Files"), "SOURCES")
-            self.filterComboBox.addItem(self.tr("Forms Files"), "FORMS")
-            self.filterComboBox.addItem(self.tr("Resource Files"), "RESOURCES")
-            self.filterComboBox.addItem(self.tr("Interface Files"), "INTERFACES")
-            self.filterComboBox.addItem(self.tr("Protocol Files"), "PROTOCOLS")
-            self.filterComboBox.addItem(self.tr("Other Files (*)"), "OTHERS")
+            for fileCategory in sorted(
+                c for c in self.__project.getFileCategories()
+                if c != "TRANSLATIONS"
+            ):
+                self.filterComboBox.addItem(
+                    self.__project.getFileCategoryString(fileCategory),
+                    fileCategory,
+                )
         self.filterComboBox.setCurrentIndex(0)
 
         msh = self.minimumSizeHint()
         self.resize(max(self.width(), msh.width()), msh.height())
 
     @pyqtSlot(int)
-    def on_filterComboBox_highlighted(self, index):
+    def on_filterComboBox_currentIndexChanged(self, index):
         """
         Private slot to handle the selection of a file type.
 
@@ -102,7 +95,7 @@
 
         @param directory the text of the source directory line edit (string)
         """
-        if directory.startswith(self.ppath):
+        if directory.startswith(self.__project.getProjectPath()):
             self.targetDirPicker.setText(directory)
 
     def getData(self):
--- a/src/eric7/Project/AddFileDialog.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/AddFileDialog.py	Wed Nov 16 18:11:52 2022 +0100
@@ -27,10 +27,15 @@
         Constructor
 
         @param pro reference to the project object
-        @param parent parent widget of this dialog (QWidget)
-        @param fileTypeFilter filter specification for the file to add (string)
-        @param name name of this dialog (string)
+        @type Project
+        @param parent parent widget of this dialog
+        @type QWidget
+        @param fileTypeFilter filter specification for the file to add
+        @type str
+        @param name name of this dialog
+        @type str
         @param startdir start directory for the selection dialog
+        @type str
         """
         super().__init__(parent)
         if name:
@@ -44,12 +49,10 @@
         if startdir:
             self.targetDirPicker.setText(startdir)
         else:
-            self.targetDirPicker.setText(pro.ppath)
+            self.targetDirPicker.setText(pro.getProjectPath())
         self.fileTypeFilter = fileTypeFilter
-        self.ppath = pro.ppath
+        self.__project = pro
         self.startdir = startdir
-        self.filetypes = pro.getProjectData(dataKey="FILETYPES")
-        # save a reference to the filetypes dict
 
         if self.fileTypeFilter is not None:
             self.sourcecodeCheckBox.hide()
@@ -68,82 +71,18 @@
             path = self.startdir
         self.sourceFilesPicker.setDefaultDirectory(path)
 
+        caption = self.tr("Select Files")
         if self.fileTypeFilter is None:
-            patterns = {
-                "SOURCES": [],
-                "FORMS": [],
-                "RESOURCES": [],
-                "INTERFACES": [],
-                "PROTOCOLS": [],
-                "TRANSLATIONS": [],
-            }
-            for pattern, filetype in list(self.filetypes.items()):
-                if filetype in patterns:
-                    patterns[filetype].append(pattern)
-            dfilter = self.tr(
-                "Source Files ({0});;"
-                "Forms Files ({1});;"
-                "Resource Files ({2});;"
-                "Interface Files ({3});;"
-                "Protocol Files ({4});;"
-                "Translation Files ({5});;"
-                "All Files (*)"
-            ).format(
-                " ".join(patterns["SOURCES"]),
-                " ".join(patterns["FORMS"]),
-                " ".join(patterns["RESOURCES"]),
-                " ".join(patterns["INTERFACES"]),
-                " ".join(patterns["PROTOCOLS"]),
-                " ".join(patterns["TRANSLATIONS"]),
+            dfilter = self.__project.getFileCategoryFilterString(withAll=True)
+        elif (
+            self.fileTypeFilter != "OTHERS"
+            and self.fileTypeFilter in self.__project.getFileCategories()
+        ):
+            dfilter = self.__project.getFileCategoryFilterString(
+                [self.fileTypeFilter], withAll=False
             )
-            caption = self.tr("Select Files")
-        elif self.fileTypeFilter == "form":
-            patterns = []
-            for pattern, filetype in list(self.filetypes.items()):
-                if filetype == "FORMS":
-                    patterns.append(pattern)
-            dfilter = self.tr("Forms Files ({0})").format(" ".join(patterns))
-            caption = self.tr("Select user-interface files")
-        elif self.fileTypeFilter == "resource":
-            patterns = []
-            for pattern, filetype in list(self.filetypes.items()):
-                if filetype == "RESOURCES":
-                    patterns.append(pattern)
-            dfilter = self.tr("Resource Files ({0})").format(" ".join(patterns))
-            caption = self.tr("Select resource files")
-        elif self.fileTypeFilter == "source":
-            patterns = []
-            for pattern, filetype in list(self.filetypes.items()):
-                if filetype == "SOURCES":
-                    patterns.append(pattern)
-            dfilter = self.tr("Source Files ({0});;All Files (*)").format(
-                " ".join(patterns)
-            )
-            caption = self.tr("Select source files")
-        elif self.fileTypeFilter == "interface":
-            patterns = []
-            for pattern, filetype in list(self.filetypes.items()):
-                if filetype == "INTERFACES":
-                    patterns.append(pattern)
-            dfilter = self.tr("Interface Files ({0})").format(" ".join(patterns))
-            caption = self.tr("Select interface files")
-        elif self.fileTypeFilter == "protocol":
-            patterns = []
-            for pattern, filetype in list(self.filetypes.items()):
-                if filetype == "PROTOCOLS":
-                    patterns.append(pattern)
-            dfilter = self.tr("Protocol Files ({0})").format(" ".join(patterns))
-            caption = self.tr("Select protocol files")
-        elif self.fileTypeFilter == "translation":
-            patterns = []
-            for pattern, filetype in list(self.filetypes.items()):
-                if filetype == "TRANSLATIONS":
-                    patterns.append(pattern)
-            dfilter = self.tr("Translation Files ({0})").format(" ".join(patterns))
-            caption = self.tr("Select translation files")
-        elif self.fileTypeFilter == "others":
+        elif self.fileTypeFilter == "OTHERS":
             dfilter = self.tr("All Files (*)")
-            caption = self.tr("Select files")
         else:
             dfilter = ""
             caption = ""
@@ -161,10 +100,11 @@
         It is assumed, that the user wants to add a bunch of files to
         the project in place.
 
-        @param sfile the text of the source file picker (string)
+        @param sfile the text of the source file picker
+        @type str
         """
         sfile = str(self.sourceFilesPicker.firstPath())
-        if sfile.startswith(self.ppath):
+        if sfile.startswith(self.__project.getProjectPath()):
             if os.path.isdir(sfile):
                 directory = sfile
             else:
@@ -175,9 +115,9 @@
         """
         Public slot to retrieve the dialogs data.
 
-        @return tuple of three values (list of string, string, boolean)
-            giving the source files, the target directory and a flag
+        @return tuple containing the source files, the target directory and a flag
             telling, whether the files shall be added as source code
+        @rtype tuple of (list of string, string, boolean)
         """
         return (
             [str(p) for p in self.sourceFilesPicker.paths()],
--- a/src/eric7/Project/FiletypeAssociationDialog.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/FiletypeAssociationDialog.py	Wed Nov 16 18:11:52 2022 +0100
@@ -7,8 +7,6 @@
 Module implementing a dialog to enter filetype associations for the project.
 """
 
-import contextlib
-
 from PyQt6.QtCore import Qt, pyqtSlot
 from PyQt6.QtWidgets import QDialog, QHeaderView, QTreeWidgetItem
 
@@ -37,36 +35,27 @@
             0, Qt.SortOrder.AscendingOrder
         )
 
-        # keep these lists in sync
-        self.filetypes = [
-            "SOURCES",
-            "FORMS",
-            "TRANSLATIONS",
-            "RESOURCES",
-            "INTERFACES",
-            "PROTOCOLS",
-            "OTHERS",
-            "__IGNORE__",
-        ]
-        self.filetypeStrings = [
-            self.tr("Sources"),
-            self.tr("Forms"),
-            self.tr("Translations"),
-            self.tr("Resources"),
-            self.tr("Interfaces"),
-            self.tr("Protocols"),
-            self.tr("Others"),
-            self.tr("Ignore"),
-        ]
-        self.filetypeCombo.addItems(self.filetypeStrings)
+        self.__project = project
+
+        self.filetypeCombo.addItem("", "")
+        for fileCategory in sorted(self.__project.getFileCategories()):
+            self.filetypeCombo.addItem(
+                self.__project.getFileCategoryType(fileCategory), fileCategory
+            )
+        self.filetypeCombo.addItem(self.tr("Ignore"), "__IGNORE__")
 
-        self.project = project
-        for pattern, filetype in list(
-            self.project.getProjectData(dataKey="FILETYPES").items()
-        ):
-            with contextlib.suppress(ValueError):
-                index = self.filetypes.index(filetype)
-                self.__createItem(pattern, self.filetypeStrings[index])
+        for pattern, filetype in self.__project.getProjectData(
+            dataKey="FILETYPES"
+        ).items():
+            try:
+                self.__createItem(
+                    pattern, self.__project.getFileCategoryType(filetype), filetype
+                )
+            except KeyError:
+                if filetype == "__IGNORE__":
+                    self.__createItem(pattern, self.tr("Ignore"), "__IGNORE__")
+                else:
+                    raise  # re-raise the error if the type is not __IGNORE__
 
         self.__resort()
         self.__reformat()
@@ -89,15 +78,21 @@
         )
         self.filetypeAssociationList.header().setStretchLastSection(True)
 
-    def __createItem(self, pattern, filetype):
+    def __createItem(self, pattern, filetypeStr, fileCategory):
         """
         Private slot to create a new entry in the association list.
 
-        @param pattern pattern of the entry (string)
-        @param filetype file type of the entry (string)
-        @return reference to the newly generated entry (QTreeWidgetItem)
+        @param pattern pattern of the entry
+        @type str
+        @param filetypeStr file type user string of the entry
+        @type str
+        @param fileCategory category of the file
+        @type str
+        @return reference to the newly generated entry
+        @rtype QTreeWidgetItem
         """
-        itm = QTreeWidgetItem(self.filetypeAssociationList, [pattern, filetype])
+        itm = QTreeWidgetItem(self.filetypeAssociationList, [pattern, filetypeStr])
+        itm.setData(1, Qt.ItemDataRole.UserRole, fileCategory)
         return itm
 
     def on_filetypeAssociationList_currentItemChanged(self, itm, prevItm):
@@ -114,7 +109,7 @@
             self.deleteAssociationButton.setEnabled(False)
         else:
             self.filePatternEdit.setText(itm.text(0))
-            self.filetypeCombo.setCurrentIndex(self.filetypeCombo.findText(itm.text(1)))
+            self.filetypeCombo.setCurrentText(itm.text(1))
             self.deleteAssociationButton.setEnabled(True)
 
     @pyqtSlot()
@@ -124,6 +119,7 @@
         """
         pattern = self.filePatternEdit.text()
         filetype = self.filetypeCombo.currentText()
+        fileCategory = self.filetypeCombo.currentData()
         if pattern:
             items = self.filetypeAssociationList.findItems(
                 pattern, Qt.MatchFlag.MatchExactly, 0
@@ -133,7 +129,7 @@
                     self.filetypeAssociationList.indexOfTopLevelItem(itm)
                 )
                 del itm
-            itm = self.__createItem(pattern, filetype)
+            itm = self.__createItem(pattern, filetype, fileCategory)
             self.__resort()
             self.__reformat()
             self.filePatternEdit.clear()
@@ -160,30 +156,44 @@
         """
         Private slot to handle the textChanged signal of the pattern lineedit.
 
-        @param txt text of the lineedit (string)
+        @param txt text of the line edit (string)
         """
         if not txt:
-            self.addAssociationButton.setEnabled(False)
             self.deleteAssociationButton.setEnabled(False)
         else:
-            self.addAssociationButton.setEnabled(True)
             if len(self.filetypeAssociationList.selectedItems()) == 0:
                 self.deleteAssociationButton.setEnabled(False)
             else:
                 self.deleteAssociationButton.setEnabled(
                     self.filetypeAssociationList.selectedItems()[0].text(0) == txt
                 )
+        self.__updateAddButton()
+
+    @pyqtSlot(int)
+    def on_filetypeCombo_currentIndexChanged(self, index):
+        """
+        Private slot handling the selection of a file type.
+
+        @param index index of the selected entry
+        @type int
+        """
+        self.__updateAddButton()
+
+    def __updateAddButton(self):
+        """
+        Private method to update the enabled state of the 'add' button.
+        """
+        self.addAssociationButton.setEnabled(
+            bool(self.filePatternEdit.text()) and bool(self.filetypeCombo.currentText())
+        )
 
     def transferData(self):
         """
         Public slot to transfer the associations into the projects data
         structure.
         """
-        self.project.setProjectData({}, dataKey="FILETYPES")
+        fileTypes = {}
         for index in range(self.filetypeAssociationList.topLevelItemCount()):
             itm = self.filetypeAssociationList.topLevelItem(index)
-            pattern = itm.text(0)
-            index = self.filetypeStrings.index(itm.text(1))
-            fileTypes = self.project.getProjectData(dataKey="FILETYPES")
-            fileTypes[pattern] = self.filetypes[index]
-            self.project.setProjectData(fileTypes, dataKey="FILETYPES")
+            fileTypes[itm.text(0)] = itm.data(1, Qt.ItemDataRole.UserRole)
+        self.__project.setProjectData(fileTypes, dataKey="FILETYPES")
--- a/src/eric7/Project/Project.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/Project.py	Wed Nov 16 18:11:52 2022 +0100
@@ -7,6 +7,7 @@
 Module implementing the project management functionality.
 """
 
+import collections
 import contextlib
 import copy
 import fnmatch
@@ -547,6 +548,7 @@
             "LICENSE": "",
             "EMBEDDED_VENV": False,
         }
+        # TODO: Move these to a file categories repository
         self.__knownFileCategories = [
             "FORMS",
             "OTHERS",
@@ -556,6 +558,33 @@
             "INTERFACES",
             "PROTOCOLS",
         ]
+        self.__fileCategoryFilterTemplates = {
+            "FORMS": self.tr("Form Files ({0})"),
+            "OTHERS": self.tr("Other Files ({0})"),
+            "RESOURCES": self.tr("Resource Files ({0})"),
+            "SOURCES": self.tr("Source Files ({0})"),
+            "TRANSLATIONS": self.tr("Translation Files ({0})"),
+            "INTERFACES": self.tr("Interface Files ({0})"),
+            "PROTOCOLS": self.tr("Protocol Files ({0})"),
+        }
+        self.__fileCategoryUserStrings = {
+            "FORMS": self.tr("Form Files"),
+            "OTHERS": self.tr("Other Files"),
+            "RESOURCES": self.tr("Resource Files"),
+            "SOURCES": self.tr("Source Files"),
+            "TRANSLATIONS": self.tr("Translation Files"),
+            "INTERFACES": self.tr("Interface Files"),
+            "PROTOCOLS": self.tr("Protocol Files"),
+        }
+        self.__fileCategoryTyeStrings = {
+            "FORMS": self.tr("Forms"),
+            "OTHERS": self.tr("Others"),
+            "RESOURCES": self.tr("Resources"),
+            "SOURCES": self.tr("Sources"),
+            "TRANSLATIONS": self.tr("Translations"),
+            "INTERFACES": self.tr("Interfaces"),
+            "PROTOCOLS": self.tr("Protocols"),
+        }
 
         self.__initDebugProperties()
 
@@ -591,7 +620,7 @@
         except KeyError:
             return default
 
-    def setProjectData(self, data, dataKey=None):
+    def setProjectData(self, data, dataKey=None, setDirty=True):
         """
         Public method to set data associated with the given data key in the project
         dictionary
@@ -603,11 +632,14 @@
         @type Any
         @param dataKey key of the data to set (defaults to None)
         @type str (optional)
+        @param setDirty flag indicating to set the dirty flag if the data is different
+            from the current one (defaults to True)
+        @type bool (optional)
         """
         if dataKey is None:
             self.__pdata.update(data)
         else:
-            if self.__pdata[dataKey] == data:
+            if self.__pdata[dataKey] != data and setDirty:
                 self.setDirty(True)
             self.__pdata[dataKey] = data
 
@@ -686,6 +718,69 @@
         """
         return self.__knownFileCategories[:]
 
+    def getFileCategoryFilterString(
+        self, categories=None, withOthers=False, withAll=True
+    ):
+        """
+        Public method to get a file selection string for the given categories.
+
+        @param categories list of file type categories (defaults to None).
+            A value of None means all categories except 'OTHERS'.
+        @type list of str (optional)
+        @param withOthers flag indicating to include the 'OTHERS' category
+            (defaults to False)
+        @type bool (optional)
+        @param withAll flag indicating to include a filter for 'All Files'
+            (defaults to True)
+        @type bool (optional)
+        @return file selection filter string
+        @rtype str
+        """
+        if categories is None:
+            categories = [c for c in self.__knownFileCategories if c != "OTHERS"]
+            if withOthers:
+                categories.append("OTHERS")
+
+        patterns = collections.defaultdict(list)
+        for pattern, filetype in self.__pdata["FILETYPES"].items():
+            if filetype in categories and filetype in self.__knownFileCategories:
+                patterns[filetype].append(pattern)
+
+        filters = []
+        for filetype in patterns:
+            filters.append(
+                self.__fileCategoryFilterTemplates[filetype].format(
+                    " ".join(sorted(patterns[filetype]))
+                )
+            )
+        filterString = ";;".join(sorted(filters))
+        if withAll:
+            filterString += ";;" + self.tr("All Files (*)")
+
+        return filterString
+
+    def getFileCategoryString(self, category):
+        """
+        Public method to get a user string for the given category.
+
+        @param category file type category
+        @type str
+        @return user string for the category
+        @rtype str
+        """
+        return self.__fileCategoryUserStrings[category]
+
+    def getFileCategoryType(self, category):
+        """
+        Public method to get a user type string for the given category.
+
+        @param category file type category
+        @type str
+        @return user type string for the category
+        @rtype str
+        """
+        return self.__fileCategoryTyeStrings[category]
+
     def initFileTypes(self):
         """
         Public method to initialize the filetype associations with default
@@ -2026,82 +2121,82 @@
             if os.path.isdir(fn) and fn not in self.otherssubdirs:
                 self.otherssubdirs.append(fn)
 
-    def addSourceFiles(self):
-        """
-        Public slot to add source files to the current project.
-        """
-        self.addFiles("source")
-
-    def addUiFiles(self):
-        """
-        Public slot to add forms to the current project.
-        """
-        self.addFiles("form")
-
-    def addIdlFiles(self):
-        """
-        Public slot to add IDL interfaces to the current project.
-        """
-        self.addFiles("interface")
-
-    def addProtoFiles(self):
-        """
-        Public slot to add protocol files to the current project.
-        """
-        self.addFiles("protocol")
-
-    def addResourceFiles(self):
-        """
-        Public slot to add Qt resources to the current project.
-        """
-        self.addFiles("resource")
-
-    def addOthersFiles(self):
-        """
-        Public slot to add files to the OTHERS project data.
-        """
-        self.addFiles("others")
-
-    def addSourceDir(self):
-        """
-        Public slot to add all source files of a directory to the current
-        project.
-        """
-        self.addDirectory("source")
-
-    def addUiDir(self):
-        """
-        Public slot to add all forms of a directory to the current project.
-        """
-        self.addDirectory("form")
-
-    def addIdlDir(self):
-        """
-        Public slot to add all IDL interfaces of a directory to the current
-        project.
-        """
-        self.addDirectory("interface")
-
-    def addProtoDir(self):
-        """
-        Public slot to add all protocol files of a directory to the current
-        project.
-        """
-        self.addDirectory("protocol")
-
-    def addResourceDir(self):
-        """
-        Public slot to add all Qt resource files of a directory to the current
-        project.
-        """
-        self.addDirectory("resource")
-
-    def addOthersDir(self):
-        """
-        Public slot to add a directory to the OTHERS project data.
-        """
-        self.addDirectory("others")
-
+    ##def addSourceFiles(self):
+        ##"""
+        ##Public slot to add source files to the current project.
+        ##"""
+        ##self.addFiles("source")
+##
+    ##def addUiFiles(self):
+        ##"""
+        ##Public slot to add forms to the current project.
+        ##"""
+        ##self.addFiles("form")
+##
+    ##def addIdlFiles(self):
+        ##"""
+        ##Public slot to add IDL interfaces to the current project.
+        ##"""
+        ##self.addFiles("interface")
+##
+    ##def addProtoFiles(self):
+        ##"""
+        ##Public slot to add protocol files to the current project.
+        ##"""
+        ##self.addFiles("protocol")
+##
+    ##def addResourceFiles(self):
+        ##"""
+        ##Public slot to add Qt resources to the current project.
+        ##"""
+        ##self.addFiles("resource")
+##
+    ##def addOthersFiles(self):
+        ##"""
+        ##Public slot to add files to the OTHERS project data.
+        ##"""
+        ##self.addFiles("others")
+##
+    ##def addSourceDir(self):
+        ##"""
+        ##Public slot to add all source files of a directory to the current
+        ##project.
+        ##"""
+        ##self.addDirectory("source")
+##
+    ##def addUiDir(self):
+        ##"""
+        ##Public slot to add all forms of a directory to the current project.
+        ##"""
+        ##self.addDirectory("form")
+##
+    ##def addIdlDir(self):
+        ##"""
+        ##Public slot to add all IDL interfaces of a directory to the current
+        ##project.
+        ##"""
+        ##self.addDirectory("interface")
+##
+    ##def addProtoDir(self):
+        ##"""
+        ##Public slot to add all protocol files of a directory to the current
+        ##project.
+        ##"""
+        ##self.addDirectory("protocol")
+##
+    ##def addResourceDir(self):
+        ##"""
+        ##Public slot to add all Qt resource files of a directory to the current
+        ##project.
+        ##"""
+        ##self.addDirectory("resource")
+##
+    ##def addOthersDir(self):
+        ##"""
+        ##Public slot to add a directory to the OTHERS project data.
+        ##"""
+        ##self.addDirectory("others")
+##
     def renameMainScript(self, oldfn, newfn):
         """
         Public method to rename the main script.
@@ -3106,9 +3201,8 @@
         Public method to get the list of file type associations for
         the given association type.
 
-        @param associationType type of the association (one of FORMS,
-            INTERFACES, OTHERS, PROTOCOLS, RESOURCES, SOURCES,
-            TRANSLATIONS or __IGNORE__)
+        @param associationType type of the association (one of the known file categories
+            or __IGNORE__)
         @type str
         @return list of file patterns for the given type
         @rtype list of str
@@ -3670,8 +3764,7 @@
         """
         Public method to get the file entries of the given type.
 
-        @param fileType project file type (one of SOURCES, FORMS, RESOURCES,
-            INTERFACES, PROTOCOLS, OTHERS, TRANSLATIONS)
+        @param fileType project file type (one of the known file categories)
         @type str
         @param normalized flag indicating normalized file names are wanted
         @type boolean
@@ -4167,7 +4260,7 @@
 
         return False
 
-    # TODO: change the following methods to a more generic logix using fileCategories
+    # TODO: change the following methods to a more generic logic using fileCategories
     def isProjectSource(self, fn):
         """
         Public method used to check, if the passed in filename belongs to the
--- a/src/eric7/Project/ProjectBrowserModel.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/ProjectBrowserModel.py	Wed Nov 16 18:11:52 2022 +0100
@@ -28,6 +28,7 @@
 ProjectBrowserItemDirectory = 101
 ProjectBrowserItemFile = 102
 
+# TODO: change to project browser provided IDs
 ProjectBrowserNoType = 0
 ProjectBrowserSourceType = 1
 ProjectBrowserFormType = 2
--- a/src/eric7/Project/ProjectFormsBrowser.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/ProjectFormsBrowser.py	Wed Nov 16 18:11:52 2022 +0100
@@ -256,9 +256,12 @@
                     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.tr("Add forms..."), lambda: self.project.addFiles("FORMS")
+        )
+        self.backMenu.addAction(
+            self.tr("Add forms directory..."),
+            lambda: self.project.addDirectory("FORMS"),
         )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -374,9 +377,12 @@
                     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.tr("Add forms..."), lambda: self.project.addFiles("FORMS")
+        )
+        self.dirMultiMenu.addAction(
+            self.tr("Add forms directory..."),
+            lambda: self.project.addDirectory("FORMS"),
         )
         self.dirMultiMenu.addSeparator()
         self.dirMultiMenu.addAction(
--- a/src/eric7/Project/ProjectInterfacesBrowser.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/ProjectInterfacesBrowser.py	Wed Nov 16 18:11:52 2022 +0100
@@ -193,9 +193,12 @@
                 self.tr("Configure IDL compiler"), self.__configureIdlCompiler
             )
             self.backMenu.addSeparator()
-        self.backMenu.addAction(self.tr("Add interfaces..."), self.project.addIdlFiles)
         self.backMenu.addAction(
-            self.tr("Add interfaces directory..."), self.project.addIdlDir
+            self.tr("Add interfaces..."), lambda: self.project.addFiles("INTERFACES")
+        )
+        self.backMenu.addAction(
+            self.tr("Add interfaces directory..."),
+            lambda: self.project.addDirectory("INTERFACES"),
         )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -274,10 +277,11 @@
             )
             self.dirMultiMenu.addSeparator()
         self.dirMultiMenu.addAction(
-            self.tr("Add interfaces..."), self.project.addIdlFiles
+            self.tr("Add interfaces..."), lambda: self.project.addFiles("INTERFACES")
         )
         self.dirMultiMenu.addAction(
-            self.tr("Add interfaces directory..."), self.project.addIdlDir
+            self.tr("Add interfaces directory..."),
+            lambda: self.project.addDirectory("INTERFACES"),
         )
         self.dirMultiMenu.addSeparator()
         self.dirMultiMenu.addAction(
--- a/src/eric7/Project/ProjectOthersBrowser.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/ProjectOthersBrowser.py	Wed Nov 16 18:11:52 2022 +0100
@@ -148,8 +148,12 @@
         self.dirMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.backMenu = QMenu(self)
-        self.backMenu.addAction(self.tr("Add files..."), self.project.addOthersFiles)
-        self.backMenu.addAction(self.tr("Add directory..."), self.project.addOthersDir)
+        self.backMenu.addAction(
+            self.tr("Add files..."), lambda: self.project.addFiles("OTHERS")
+        )
+        self.backMenu.addAction(
+            self.tr("Add directory..."), lambda: self.project.addDirectory("OTHERS")
+        )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
         self.backMenu.addAction(
--- a/src/eric7/Project/ProjectProtocolsBrowser.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/ProjectProtocolsBrowser.py	Wed Nov 16 18:11:52 2022 +0100
@@ -193,9 +193,12 @@
             lambda: self.__compileAllProtocols(grpc=True),
         )
         self.backMenu.addSeparator()
-        self.backMenu.addAction(self.tr("Add protocols..."), self.project.addProtoFiles)
         self.backMenu.addAction(
-            self.tr("Add protocols directory..."), self.project.addProtoDir
+            self.tr("Add protocols..."), lambda: self.project.addFiles("PROTOCOLS")
+        )
+        self.backMenu.addAction(
+            self.tr("Add protocols directory..."),
+            lambda: self.project.addDirectory("PROTOCOLS"),
         )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -278,10 +281,11 @@
             lambda: self.__compileAllProtocols(grpc=True),
         )
         self.dirMultiMenu.addAction(
-            self.tr("Add protocols..."), self.project.addProtoFiles
+            self.tr("Add protocols..."), lambda: self.project.addFiles("PROTOCOLS")
         )
         self.dirMultiMenu.addAction(
-            self.tr("Add protocols directory..."), self.project.addProtoDir
+            self.tr("Add protocols directory..."),
+            lambda: self.project.addDirectory("PROTOCOLS"),
         )
         self.dirMultiMenu.addSeparator()
         self.dirMultiMenu.addAction(
--- a/src/eric7/Project/ProjectResourcesBrowser.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/ProjectResourcesBrowser.py	Wed Nov 16 18:11:52 2022 +0100
@@ -210,10 +210,11 @@
                     self.__newResource,
                 )
         self.backMenu.addAction(
-            self.tr("Add resources..."), self.project.addResourceFiles
+            self.tr("Add resources..."), lambda: self.project.addFiles("RECOURCES")
         )
         self.backMenu.addAction(
-            self.tr("Add resources directory..."), self.project.addResourceDir
+            self.tr("Add resources directory..."),
+            lambda: self.project.addDirectory("RESOURCES"),
         )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -338,10 +339,11 @@
                 )
                 self.dirMultiMenu.addSeparator()
         self.dirMultiMenu.addAction(
-            self.tr("Add resources..."), self.project.addResourceFiles
+            self.tr("Add resources..."), lambda: self.project.addFiles("RECOURCES")
         )
         self.dirMultiMenu.addAction(
-            self.tr("Add resources directory..."), self.project.addResourceDir
+            self.tr("Add resources directory..."),
+            lambda: self.project.addDirectory("RESOURCES"),
         )
         self.dirMultiMenu.addSeparator()
         self.dirMultiMenu.addAction(
--- a/src/eric7/Project/ProjectSourcesBrowser.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/ProjectSourcesBrowser.py	Wed Nov 16 18:11:52 2022 +0100
@@ -296,10 +296,12 @@
         self.attributeMenu.addSeparator()
         self.attributeMenu.addAction(self.tr("New package..."), self.__addNewPackage)
         self.attributeMenu.addAction(
-            self.tr("Add source files..."), self.project.addSourceFiles
+            self.tr("Add source files..."),
+            lambda: self.project.addFiles("SOURCES"),
         )
         self.attributeMenu.addAction(
-            self.tr("Add source directory..."), self.project.addSourceDir
+            self.tr("Add source directory..."),
+            lambda: self.project.addDirectory("SOURCES"),
         )
         self.attributeMenu.addSeparator()
         self.attributeMenu.addAction(
@@ -314,10 +316,12 @@
         self.backMenu = QMenu(self)
         self.backMenu.addAction(self.tr("New package..."), self.__addNewPackage)
         self.backMenu.addAction(
-            self.tr("Add source files..."), self.project.addSourceFiles
+            self.tr("Add source files..."),
+            lambda: self.project.addFiles("SOURCES"),
         )
         self.backMenu.addAction(
-            self.tr("Add source directory..."), self.project.addSourceDir
+            self.tr("Add source directory..."),
+            lambda: self.project.addDirectory("SOURCES"),
         )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -453,10 +457,12 @@
         self.attributeMenu.addMenu(self.gotoMenu)
         self.attributeMenu.addSeparator()
         self.attributeMenu.addAction(
-            self.tr("Add source files..."), self.project.addSourceFiles
+            self.tr("Add source files..."),
+            lambda: self.project.addFiles("SOURCES"),
         )
         self.attributeMenu.addAction(
-            self.tr("Add source directory..."), self.project.addSourceDir
+            self.tr("Add source directory..."),
+            lambda: self.project.addDirectory("SOURCES"),
         )
         self.attributeMenu.addSeparator()
         self.attributeMenu.addAction(
@@ -470,10 +476,12 @@
 
         self.backMenu = QMenu(self)
         self.backMenu.addAction(
-            self.tr("Add source files..."), self.project.addSourceFiles
+            self.tr("Add source files..."),
+            lambda: self.project.addFiles("SOURCES"),
         )
         self.backMenu.addAction(
-            self.tr("Add source directory..."), self.project.addSourceDir
+            self.tr("Add source directory..."),
+            lambda: self.project.addDirectory("SOURCES"),
         )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -589,10 +597,12 @@
         self.attributeMenu.addMenu(self.gotoMenu)
         self.attributeMenu.addSeparator()
         self.attributeMenu.addAction(
-            self.tr("Add source files..."), self.project.addSourceFiles
+            self.tr("Add source files..."),
+            lambda: self.project.addFiles("SOURCES"),
         )
         self.attributeMenu.addAction(
-            self.tr("Add source directory..."), self.project.addSourceDir
+            self.tr("Add source directory..."),
+            lambda: self.project.addDirectory("SOURCES"),
         )
         self.attributeMenu.addSeparator()
         self.attributeMenu.addAction(
@@ -606,10 +616,12 @@
 
         self.backMenu = QMenu(self)
         self.backMenu.addAction(
-            self.tr("Add source files..."), self.project.addSourceFiles
+            self.tr("Add source files..."),
+            lambda: self.project.addFiles("SOURCES"),
         )
         self.backMenu.addAction(
-            self.tr("Add source directory..."), self.project.addSourceDir
+            self.tr("Add source directory..."),
+            lambda: self.project.addDirectory("SOURCES"),
         )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
--- a/src/eric7/Project/QuickFindFileDialog.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/Project/QuickFindFileDialog.py	Wed Nov 16 18:11:52 2022 +0100
@@ -145,17 +145,17 @@
         @yield set of files in our project
         @ytype str
         """
-        # TODO: change this to use fileType
-        for typ in [
-            "SOURCES",
-            "FORMS",
-            "INTERFACES",
-            "PROTOCOLS",
-            "RESOURCES",
-            "TRANSLATIONS",
-            "OTHERS",
-        ]:
-            entries = self.project.getProjectData(dataKey=typ, default=[])
+        ##for typ in [
+            ##"SOURCES",
+            ##"FORMS",
+            ##"INTERFACES",
+            ##"PROTOCOLS",
+            ##"RESOURCES",
+            ##"TRANSLATIONS",
+            ##"OTHERS",
+        ##]:
+        for fileCategory in self.project.getFileCategories():
+            entries = self.project.getProjectData(dataKey=fileCategory, default=[])
             yield from entries[:]
 
     def __sortedMatches(self, items, searchTerm):
--- a/src/eric7/UI/FindFileWidget.py	Wed Nov 16 11:04:18 2022 +0100
+++ b/src/eric7/UI/FindFileWidget.py	Wed Nov 16 18:11:52 2022 +0100
@@ -158,6 +158,7 @@
         self.__section0Size = self.findList.header().sectionSize(0)
         self.findList.setExpandsOnDoubleClick(False)
 
+        # TODO: move these to a project browser file category repository
         # Qt Designer form files
         self.filterForms = r".*\.ui$"
         self.formsExt = ["*.ui"]
@@ -523,6 +524,7 @@
                             ]
                         )
                 else:
+                    # TODO: make this more generic (use project browser type repository
                     if self.sourcesCheckBox.isChecked():
                         filters.extend(
                             [

eric ide

mercurial