eric7/Plugins/WizardPlugins/SetupWizard/SetupWizardDialog.py

branch
eric7
changeset 9201
2f1ccadee231
parent 9188
d137adc02a0f
child 9202
81388c6065e8
--- a/eric7/Plugins/WizardPlugins/SetupWizard/SetupWizardDialog.py	Sat Jul 02 17:22:06 2022 +0200
+++ b/eric7/Plugins/WizardPlugins/SetupWizard/SetupWizardDialog.py	Sat Jul 02 18:53:56 2022 +0200
@@ -7,8 +7,13 @@
 Module implementing the setup.py wizard dialog.
 """
 
+import configparser
+import datetime
+import io
 import os
-import datetime
+import pathlib
+
+import tomlkit
 
 import trove_classifiers
 
@@ -21,6 +26,8 @@
 from EricWidgets import EricFileDialog
 from EricWidgets.EricPathPicker import EricPathPickerModes
 
+from .AddEntryPointDialog import AddEntryPointDialog
+from .AddProjectUrlDialog import AddProjectUrlDialog
 from .Ui_SetupWizardDialog import Ui_SetupWizardDialog
 
 import Utilities
@@ -34,17 +41,29 @@
     It displays a dialog for entering the parameters for the setup.py code
     generator.
     """
-    def __init__(self, parent=None):
+    def __init__(self, category, parent=None):
         """
         Constructor
         
-        @param parent reference to the parent widget
-        @type QWidget
+        @param category category of setup file to create
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        @exception ValueError raised for an illegal setup file category
         """
+        if category not in ("setup.py", "setup.cfg", "pyproject.toml"):
+            raise ValueError("illegal setup file category given")
+        
         super().__init__(parent)
         self.setupUi(self)
         
         self.__replies = []
+        self.__category = category
+        
+        if category != "setup.py":
+            self.introCheckBox.setVisible(False)
+            self.importCheckBox.setVisible(False)
+            self.metaDataCheckBox.setVisible(False)
         
         self.dataTabWidget.setCurrentIndex(0)
         
@@ -71,6 +90,18 @@
         projectOpen = ericApp().getObject("Project").isOpen()
         self.projectButton.setEnabled(projectOpen)
         
+        self.projectUrlsList.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder)
+        self.entryPointsList.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder)
+        
+        self.descriptionContentTypeComboBox.addItem("", "")
+        for contentType, mimetype in sorted([
+            (self.tr("Plain Text"), "text/plain"),
+            (self.tr("Markdown"), "text/markdown"),
+            (self.tr("reStructuredText"), "text/x-rst")
+            
+        ]):
+            self.descriptionContentTypeComboBox.addItem(contentType, mimetype)
+        
         self.homePageUrlEdit.textChanged.connect(self.__enableOkButton)
         self.nameEdit.textChanged.connect(self.__enableOkButton)
         self.versionEdit.textChanged.connect(self.__enableOkButton)
@@ -168,9 +199,9 @@
                 lic = lic.rsplit("(", 1)[1].split(")", 1)[0]
             return lic
     
-    def getCode(self, indLevel, indString):
+    def __getSetupPyCode(self, indLevel, indString):
         """
-        Public method to get the source code.
+        Private method to get the source code for a 'setup.py' file.
         
         @param indLevel indentation level
         @type int
@@ -229,14 +260,15 @@
         if self.descriptionFromFilesCheckBox.isChecked():
             sourceCode += 'def get_long_description():{0}'.format(os.linesep)
             sourceCode += '{0}descr = []{1}'.format(istring, os.linesep)
-            sourceCode += '{0}for fname in "{1}":{2}'.format(
+            sourceCode += '{0}for fname in ("{1})":{2}'.format(
                 istring,
                 '", "'.join(self.descriptionEdit.toPlainText().splitlines()),
                 os.linesep)
-            sourceCode += '{0}{0}with open(fname) as f:{1}'.format(
-                istring, os.linesep)
-            sourceCode += '{0}{0}{0}descr.append(f.read()){1}'.format(
-                istring, os.linesep)
+            sourceCode += (
+                '{0}with open(fname, "r", encoding="utf-8") as f:{1}'
+            ).format(i1string, os.linesep)
+            sourceCode += '{0}descr.append(f.read()){1}'.format(
+                i2string, os.linesep)
             sourceCode += '{0}return "\\n\\n".join(descr){1}'.format(
                 istring, os.linesep)
             sourceCode += "{0}{0}".format(os.linesep)
@@ -258,6 +290,10 @@
             sourceCode += '{0}long_description="""{1}""",{2}'.format(
                 istring, self.descriptionEdit.toPlainText(), os.linesep)
         
+        if self.descriptionContentTypeComboBox.currentData():
+            sourceCode += '{0}long_description_content_type="{1}",{2}'.format(
+                istring, self.descriptionContentTypeComboBox.currentData(), os.linesep)
+        
         if self.authorEdit.text():
             sourceCode += '{0}author="{1}",{2}'.format(
                 istring, self.authorEdit.text(), os.linesep)
@@ -272,6 +308,17 @@
         
         sourceCode += '{0}url="{1}",{2}'.format(
             istring, self.homePageUrlEdit.text(), os.linesep)
+        if self.downloadUrlEdit.text():
+            sourceCode += '{0}download_url="{1}",{2}'.format(
+                istring, self.downloadUrlEdit.text(), os.linesep)
+        
+        if self.projectUrlsList.topLevelItemCount():
+            sourceCode += '{0}project_urls={{{1}'.format(istring, os.linesep)
+            for row in range(self.projectUrlsList.topLevelItemCount()):
+                urlItem = self.projectUrlsList.topLevelItem(row)
+                sourceCode += '{0}"{1}": "{2}",{3}'.format(
+                    i1string, urlItem.text(0), urlItem.text(1), os.linesep)
+            sourceCode += '{0}}},{1}'.format(istring, os.linesep)
         
         classifiers = []
         if not self.licenseClassifierCheckBox.isChecked():
@@ -292,9 +339,7 @@
             sourceCode += '{0}],{1}'.format(istring, os.linesep)
         
         if self.developmentStatusComboBox.currentIndex() != 0:
-            classifiers.append(
-                self.developmentStatusComboBox.itemData(
-                    self.developmentStatusComboBox.currentIndex()))
+            classifiers.append(self.developmentStatusComboBox.currentData())
         
         itm = self.classifiersList.topLevelItem(0)
         while itm:
@@ -318,6 +363,10 @@
             sourceCode += '{0}keywords="{1}",{2}'.format(
                 istring, self.keywordsEdit.text(), os.linesep)
         
+        if self.pyVersionEdit.text():
+            sourceCode += '{0}python_requires="{1}",{2}'.format(
+                istring, self.pyVersionEdit.text(), os.linesep)
+        
         sourceCode += '{0}packages=find_packages('.format(istring)
         src = Utilities.fromNativeSeparators(
             self.sourceDirectoryPicker.text())
@@ -357,21 +406,376 @@
             sourceCode += '{0}],{1}'.format(istring, os.linesep)
         del modules
         
-        scripts = []
-        for row in range(self.scriptsList.count()):
-            scripts.append(self.scriptsList.item(row).text())
-        if scripts:
-            sourceCode += '{0}scripts=[{1}'.format(istring, os.linesep)
-            sourceCode += '{0}"{1}"{2}'.format(
-                i1string,
-                '",{0}{1}"'.format(os.linesep, i1string).join(scripts),
-                os.linesep)
-            sourceCode += '{0}],{1}'.format(istring, os.linesep)
-        del scripts
+        if self.entryPointsList.topLevelItemCount():
+            entryPoints = {
+                "console_scripts": [],
+                "gui_scripts": [],
+            }
+            for row in range(self.entryPointsList.topLevelItemCount()):
+                itm = self.entryPointsList.topLevelItem(row)
+                entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)].append(
+                    "{0} = {1}".format(itm.text(1), itm.text(2))
+                )
+            sourceCode += '{0}entry_points={{{1}'.format(istring, os.linesep)
+            for epCategory in entryPoints:
+                if entryPoints[epCategory]:
+                    sourceCode += '{0}"{1}": [{2}'.format(
+                        i1string, epCategory, os.linesep)
+                    for entryPoint in entryPoints[epCategory]:
+                        sourceCode += '{0}"{1}",{2}'.format(
+                            i2string, entryPoint, os.linesep)
+                    sourceCode += '{0}],{1}'.format(i1string, os.linesep)
+            sourceCode += '{0}}},{1}'.format(istring, os.linesep)
         
         sourceCode += "){0}".format(estring)
         return sourceCode
     
+    def __getSetupCfgCode(self):
+        """
+        Private method to get the source code for a 'setup.cfg' file.
+        
+        @return generated code
+        @rtype str
+        """
+        from . import SetupCfgUtilities
+        metadata = {
+            "name": self.nameEdit.text(),
+            "version": self.versionEdit.text(),
+        }
+        
+        if self.summaryEdit.text():
+            metadata["description"] = self.summaryEdit.text()
+        
+        if self.descriptionEdit.toPlainText():
+            metadata["long_description"] = (
+                "file: {0}".format(
+                    ", ".join(self.descriptionEdit.toPlainText().splitlines())
+                )
+                if self.descriptionFromFilesCheckBox.isChecked() else
+                self.descriptionEdit.toPlainText()
+            )
+        
+        if self.descriptionContentTypeComboBox.currentData():
+            metadata["long_description_content_type"] = (
+                self.descriptionContentTypeComboBox.currentData()
+            )
+        
+        if self.authorEdit.text():
+            metadata["author"] = self.authorEdit.text()
+            metadata["author_email"] = self.authorEmailEdit.text()
+        
+        if self.maintainerEdit.text():
+            metadata["maintainer"] = self.maintainerEdit.text()
+            metadata["maintainer_email"] = self.maintainerEmailEdit.text()
+        
+        metadata["url"] = self.homePageUrlEdit.text()
+        if self.downloadUrlEdit.text():
+            metadata["download_url"] = self.downloadUrlEdit.text()
+        
+        if self.projectUrlsList.topLevelItemCount():
+            projectURLs = {}
+            for row in range(self.projectUrlsList.topLevelItemCount()):
+                urlItem = self.projectUrlsList.topLevelItem(row)
+                projectURLs[urlItem.text(0)] = urlItem.text(1)
+            metadata["project_urls"] = SetupCfgUtilities.toString(projectURLs)
+        
+        classifiers = []
+        if not self.licenseClassifierCheckBox.isChecked():
+            metadata["license"] = self.licenseEdit.text()
+        else:
+            classifiers.append(
+                self.licenseClassifierComboBox.itemData(
+                    self.licenseClassifierComboBox.currentIndex()))
+        
+        platforms = self.platformsEdit.toPlainText().splitlines()
+        if platforms:
+            metadata["platforms"] = SetupCfgUtilities.toString(platforms)
+        
+        if self.developmentStatusComboBox.currentIndex() != 0:
+            classifiers.append(self.developmentStatusComboBox.currentData())
+        
+        itm = self.classifiersList.topLevelItem(0)
+        while itm:
+            itm.setExpanded(True)
+            if itm.checkState(0) == Qt.CheckState.Checked:
+                classifiers.append(itm.data(0, Qt.ItemDataRole.UserRole))
+            itm = self.classifiersList.itemBelow(itm)
+        
+        # cleanup classifiers list - remove all invalid entries
+        classifiers = [c for c in classifiers if bool(c)]
+        if classifiers:
+            metadata["classifiers"] = SetupCfgUtilities.toString(classifiers)
+        
+        if self.keywordsEdit.text():
+            metadata["keywords"] = SetupCfgUtilities.toString(
+                self.keywordsEdit.text().split())
+        
+        options = {
+            "packages": "find:"
+        }
+        
+        if self.pyVersionEdit.text():
+            options["python_requires"] = self.pyVersionEdit.text()
+        
+        findOptions = {}
+        src = Utilities.fromNativeSeparators(self.sourceDirectoryPicker.text())
+        excludePatterns = []
+        for row in range(self.excludePatternList.count()):
+            excludePatterns.append(
+                self.excludePatternList.item(row).text())
+        if src:
+            options["package_dir"] = SetupCfgUtilities.toString({"": src})
+            findOptions["where"] = src
+        if excludePatterns:
+            findOptions["exclude"] = SetupCfgUtilities.toString(excludePatterns)
+        
+        if self.includePackageDataCheckBox.isChecked():
+            options["include_package_data"] = SetupCfgUtilities.toString(True)
+            packageData = {}    # placeholder section
+        else:
+            packageData = None
+        
+        modules = []
+        for row in range(self.modulesList.count()):
+            modules.append(self.modulesList.item(row).text())
+        if modules:
+            options["py_modules"] = SetupCfgUtilities.toString(modules)
+        
+        if self.entryPointsList.topLevelItemCount():
+            entryPoints = {
+                "console_scripts": {},
+                "gui_scripts": {},
+            }
+            for row in range(self.entryPointsList.topLevelItemCount()):
+                itm = self.entryPointsList.topLevelItem(row)
+                entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)][
+                    itm.text(1)] = itm.text(2)
+            for epType in list(entryPoints.keys()):
+                if entryPoints[epType]:
+                    entryPoints[epType] = SetupCfgUtilities.toString(
+                        entryPoints[epType])
+                else:
+                    del entryPoints[epType]
+        else:
+            entryPoints = {}
+        
+        configDict = {
+            "metadata": metadata,
+            "options": options,
+            "options.packages.find": findOptions,
+        }
+        if packageData is not None:
+            configDict["options.package_data"] = packageData
+        if entryPoints:
+            configDict["options.entry_points"] = entryPoints
+        
+        cparser = configparser.ConfigParser()
+        cparser.read_dict(configDict)
+        sio = io.StringIO()
+        cparser.write(sio)
+        sourceCode = sio.getvalue()
+        return sourceCode
+    
+    def __getPyprojectCode(self):
+        """
+        Private method to get the source code for a 'pyproject.toml' file.
+        
+        @return generated code
+        @rtype str
+        """
+        doc = tomlkit.document()
+        
+        buildSystem = tomlkit.table()
+        buildSystem["requires"] = ["setuptools>=61.0.0", "wheel"]
+        buildSystem["build-backend"] = "setuptools.build_meta"
+        doc["build-system"] = buildSystem
+        
+        project = tomlkit.table()
+        project["name"] = self.nameEdit.text()
+        project["version"] = self.versionEdit.text()
+        
+        if self.summaryEdit.text():
+            project["description"] = self.summaryEdit.text()
+        
+        if self.descriptionEdit.toPlainText():
+            if self.descriptionFromFilesCheckBox.isChecked():
+                project["readme"] = self.descriptionEdit.toPlainText().splitlines()[0]
+            else:
+                readme = tomlkit.table()
+                readme["text"] = self.descriptionEdit.toPlainText()
+                readme["content-type"] = (
+                    self.descriptionContentTypeComboBox.currentData()
+                )
+                project["readme"] = readme
+        
+        if self.authorEdit.text():
+            authors = tomlkit.array()
+            author = tomlkit.inline_table()
+            author["name"] = self.authorEdit.text()
+            author["email"] = self.authorEmailEdit.text()
+            authors.append(author)
+            project["authors"] = authors
+        
+        if self.maintainerEdit.text():
+            maintainers = tomlkit.array()
+            maintainer = tomlkit.inline_table()
+            maintainer["name"] = self.maintainerEdit.text()
+            maintainer["email"] = self.maintainerEmailEdit.text()
+            maintainers.append(maintainer)
+            project["maintainers"] = maintainers
+        
+        urls = tomlkit.table()
+        urls["Homepage"] = self.homePageUrlEdit.text()
+        if self.downloadUrlEdit.text():
+            urls["Download"] = self.downloadUrlEdit.text()
+        
+        if self.projectUrlsList.topLevelItemCount():
+            for row in range(self.projectUrlsList.topLevelItemCount()):
+                urlItem = self.projectUrlsList.topLevelItem(row)
+                urls[urlItem.text(0)] = urlItem.text(1)
+        project["urls"] = urls
+        
+        classifiers = []
+        if not self.licenseClassifierCheckBox.isChecked():
+            license = tomlkit.table()
+            license["text"] =  self.licenseEdit.text()
+            project["license"] = license
+        else:
+            classifiers.append(
+                self.licenseClassifierComboBox.itemData(
+                    self.licenseClassifierComboBox.currentIndex()))
+        
+        if self.developmentStatusComboBox.currentIndex() != 0:
+            classifiers.append(self.developmentStatusComboBox.currentData())
+        
+        itm = self.classifiersList.topLevelItem(0)
+        while itm:
+            itm.setExpanded(True)
+            if itm.checkState(0) == Qt.CheckState.Checked:
+                classifiers.append(itm.data(0, Qt.ItemDataRole.UserRole))
+            itm = self.classifiersList.itemBelow(itm)
+        
+        # cleanup classifiers list - remove all invalid entries
+        classifiers = [c for c in classifiers if bool(c)]
+        if classifiers:
+            classifiersArray = tomlkit.array()
+            for classifier in classifiers:
+                classifiersArray.add_line(classifier)
+            classifiersArray.append(tomlkit.nl())
+            project["classifiers"] = classifiersArray
+        
+        if self.keywordsEdit.text():
+            keywords = tomlkit.array()
+            for kw in self.keywordsEdit.text().split():
+                keywords.add_line(kw)
+            keywords.append(tomlkit.nl())
+            project["keywords"] = keywords
+        
+        if self.pyVersionEdit.text():
+            project["requires-python"] = self.pyVersionEdit.text()
+        
+        if self.entryPointsList.topLevelItemCount():
+            entryPoints = {
+                "console_scripts": {},
+                "gui_scripts": {},
+            }
+            for row in range(self.entryPointsList.topLevelItemCount()):
+                itm = self.entryPointsList.topLevelItem(row)
+                entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)][
+                    itm.text(1)] = itm.text(2)
+            
+            if entryPoints["console_scripts"]:
+                scripts = tomlkit.table()
+                for name, function in entryPoints["console_scripts"].items():
+                    scripts[name] = function
+                project["scripts"] = scripts
+            
+            if entryPoints["gui_scripts"]:
+                guiScripts = tomlkit.table()
+                for name, function in entryPoints["gui_scripts"].items():
+                    guiScripts[name] = function
+                project["gui-scripts"] = guiScripts
+        
+        # placeholder
+        dependencies = tomlkit.array()
+        dependencies.append(tomlkit.comment(
+            "TODO: enter project dependencies "                 # __NO-TASK__
+        ))
+        project["dependencies"] = dependencies
+        
+        doc["project"] = project
+        
+        setuptools = tomlkit.table()
+        
+        platforms = self.platformsEdit.toPlainText().splitlines()
+        if platforms:
+            platformsArray = tomlkit.array()
+            for plt in platforms:
+                platformsArray.add_line(plt)
+            platformsArray.append(tomlkit.nl())
+            setuptools["platforms"] = platformsArray
+        
+        setuptools["include-package-data"] = self.includePackageDataCheckBox.isChecked()
+        if self.includePackageDataCheckBox.isChecked():
+            # placeholder
+            setuptools["package-data"] = tomlkit.table()
+            setuptools["package-data"].add(tomlkit.comment(
+                "TODO: enter package data patterns"             # __NO-TASK__
+            ))
+        
+        if self.modulesList.count():
+            modulesArray = tomlkit.array()
+            for row in range(self.modulesList.count()):
+                modulesArray.add_line(self.modulesList.item(row).text())
+            modulesArray.append(tomlkit.nl())
+            setuptools["py-modules"] = modulesArray
+        
+        findspec = tomlkit.table()
+        src = Utilities.fromNativeSeparators(self.sourceDirectoryPicker.text())
+        excludePatterns = []
+        for row in range(self.excludePatternList.count()):
+            excludePatterns.append(
+                self.excludePatternList.item(row).text())
+        if src:
+            findspec["where"] = [ericApp().getObject("Project").getRelativePath(src)]
+        if excludePatterns:
+            excludePatternsArray = tomlkit.array()
+            for pattern in excludePatterns:
+                excludePatternsArray.add_line(pattern)
+            excludePatternsArray.append(tomlkit.nl())
+            findspec["exclude"] = excludePatternsArray
+        
+        if bool(findspec):
+            setuptools["packages"] = tomlkit.table(is_super_table=True)
+            setuptools["packages"]["find"] = findspec
+        
+        doc["tool"] = tomlkit.table(is_super_table=True)
+        doc["tool"]["setuptools"] = setuptools
+        
+        sourceCode = tomlkit.dumps(doc)
+        return sourceCode
+    
+    def getCode(self, indLevel, indString):
+        """
+        Public method to get the source code.
+        
+        @param indLevel indentation level
+        @type int
+        @param indString string used for indentation (space or tab)
+        @type str
+        @return generated code
+        @rtype str
+        """
+        if self.__category == "setup.py":
+            return self.__getSetupPyCode(indLevel, indString)
+        elif self.__category == "setup.cfg":
+            return self.__getSetupCfgCode()
+        elif self.__category == "pyproject.toml":
+            return self.__getPyprojectCode()
+        else:
+            # should not happen, but play it safe
+            return ""
+    
     @pyqtSlot()
     def on_projectButton_clicked(self):
         """
@@ -415,43 +819,62 @@
                 Utilities.getHomeDir())
     
     @pyqtSlot()
-    def on_scriptsList_itemSelectionChanged(self):
+    def on_entryPointsList_itemSelectionChanged(self):
         """
         Private slot to handle a change of selected items of the
-        scripts list.
+        entry points list.
         """
-        self.deleteScriptButton.setEnabled(
-            len(self.scriptsList.selectedItems()) > 0)
+        self.deleteEntryPointButton.setEnabled(
+            bool(self.entryPointsList.selectedItems()))
+        self.editEntryPointButton.setEnabled(
+            len(self.entryPointsList.selectedItems()) == 1)
     
     @pyqtSlot()
-    def on_deleteScriptButton_clicked(self):
-        """
-        Private slot to delete the selected script items.
+    def on_deleteEntryPointButton_clicked(self):
         """
-        for itm in self.scriptsList.selectedItems():
-            self.scriptsList.takeItem(
-                self.scriptsList.row(itm))
+        Private slot to delete the selected entry point items.
+        """
+        for itm in self.entryPointsList.selectedItems():
+            self.entryPointsList.takeTopLevelItem(self.entryPointsList.row(itm))
             del itm
     
     @pyqtSlot()
-    def on_addScriptButton_clicked(self):
+    def on_addEntryPointButton_clicked(self):
         """
-        Private slot to add scripts to the list.
+        Private slot to add an entry point to the list.
         """
-        startDir = self.packageRootPicker.text() or self.__getStartDir()
-        scriptsList = EricFileDialog.getOpenFileNames(
-            self,
-            self.tr("Add Scripts"),
-            startDir,
-            self.tr("Python Files (*.py);;All Files(*)"))
-        for script in scriptsList:
-            script = script.replace(
-                Utilities.toNativeSeparators(startDir), "")
-            if script.startswith(("\\", "/")):
-                script = script[1:]
-            if script:
-                QListWidgetItem(Utilities.fromNativeSeparators(script),
-                                self.scriptsList)
+        project = ericApp().getObject("Project")
+        rootDir = (
+            project.getProjectPath()
+            if project.isOpen() else
+            ""
+        )
+        dlg = AddEntryPointDialog(rootDir, parent=self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            epType, epCategory, name, script = dlg.getEntryPoint()
+            itm = QTreeWidgetItem(self.entryPointsList, [epType, name, script])
+            itm.setData(0, Qt.ItemDataRole.UserRole, epCategory)
+    
+    @pyqtSlot()
+    def on_editEntryPointButton_clicked(self):
+        """
+        Private slot to edit the selected entry point.
+        """
+        project = ericApp().getObject("Project")
+        rootDir = (
+            project.getProjectPath()
+            if project.isOpen() else
+            ""
+        )
+        itm = self.entryPointsList.selectedItems()[0]
+        dlg = AddEntryPointDialog(rootDir, epType=itm.text(0), name=itm.text(1),
+                                  script=itm.text(2), parent=self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            epType, epCategory, name, script = dlg.getEntryPoint()
+            itm.setText(0, epType)
+            itm.setText(1, name)
+            itm.setText(2, script)
+            itm.setData(0, Qt.ItemDataRole.UserRole, epCategory)
     
     @pyqtSlot()
     def on_modulesList_itemSelectionChanged(self):
@@ -460,16 +883,15 @@
         modules list.
         """
         self.deleteModuleButton.setEnabled(
-            len(self.modulesList.selectedItems()) > 0)
+            bool(self.modulesList.selectedItems()))
     
     @pyqtSlot()
     def on_deleteModuleButton_clicked(self):
         """
-        Private slot to delete the selected script items.
+        Private slot to delete the selected module items.
         """
         for itm in self.modulesList.selectedItems():
-            self.modulesList.takeItem(
-                self.modulesList.row(itm))
+            self.modulesList.takeItem(self.modulesList.row(itm))
             del itm
     
     @pyqtSlot()
@@ -489,9 +911,12 @@
             if module.startswith(("\\", "/")):
                 module = module[1:]
             if module:
-                QListWidgetItem(os.path.splitext(module)[0]
-                                .replace("\\", ".").replace("/", "."),
-                                self.modulesList)
+                QListWidgetItem(
+                    str(pathlib.Path(module).with_suffix(""))
+                    .replace("\\", ".")
+                    .replace("/", "."),
+                    self.modulesList
+                )
     
     @pyqtSlot()
     def on_excludePatternList_itemSelectionChanged(self):
@@ -500,7 +925,7 @@
         exclude pattern list.
         """
         self.deleteExcludePatternButton.setEnabled(
-            len(self.excludePatternList.selectedItems()) > 0)
+            bool(self.excludePatternList.selectedItems()))
     
     @pyqtSlot()
     def on_deleteExcludePatternButton_clicked(self):
@@ -543,3 +968,43 @@
         exclude pattern edit.
         """
         self.on_addExludePatternButton_clicked()
+    
+    @pyqtSlot()
+    def on_urlDeleteButton_clicked(self):
+        """
+        Private slot to delete the selected URL items.
+        """
+        for itm in self.projectUrlsList.selectedItems():
+            self.projectUrlsList.takeTopLevelItem(self.projectUrlsList.row(itm))
+            del itm
+    
+    @pyqtSlot()
+    def on_urlAddButton_clicked(self):
+        """
+        Private slot to add a project URL to the list.
+        """
+        dlg = AddProjectUrlDialog(parent=self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            name, url = dlg.getUrl()
+            QTreeWidgetItem(self.projectUrlsList, [name, url])
+    
+    @pyqtSlot()
+    def on_urlEditButton_clicked(self):
+        """
+        Private slot to edit the selected project URL.
+        """
+        itm = self.projectUrlsList.selectedItems()[0]
+        dlg = AddProjectUrlDialog(name=itm.text(0), url=itm.text(1), parent=self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            name, url = dlg.getUrl()
+            itm.setText(0, name)
+            itm.setText(1, url)
+    
+    @pyqtSlot()
+    def on_projectUrlsList_itemSelectionChanged(self):
+        """
+        Private slot to handle a change of selected items of the
+        project URLs list.
+        """
+        self.urlDeleteButton.setEnabled(bool(self.projectUrlsList.selectedItems()))
+        self.urlEditButton.setEnabled(len(self.projectUrlsList.selectedItems()) == 1)

eric ide

mercurial