From aabf887e1e8bad60313cce83507657bc5d6ef08d Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 31 Aug 2022 19:18:21 -0500 Subject: [PATCH] Addon Manager: Add person editor --- src/Mod/AddonManager/CMakeLists.txt | 2 + src/Mod/AddonManager/addonmanager_devmode.py | 89 +++++++++++++---- .../addonmanager_devmode_license_selector.py | 16 +-- .../addonmanager_devmode_person_editor.py | 77 +++++++++++++++ src/Mod/AddonManager/developer_mode_people.ui | 99 +++++++++++++++++++ 5 files changed, 256 insertions(+), 27 deletions(-) create mode 100644 src/Mod/AddonManager/addonmanager_devmode_person_editor.py create mode 100644 src/Mod/AddonManager/developer_mode_people.ui diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index a1721dc342..747b3015b2 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -11,6 +11,7 @@ SET(AddonManager_SRCS addonmanager_devmode_add_content.py addonmanager_devmode_license_selector.py addonmanager_devmode_dependencies.py + addonmanager_devmode_person_editor.py addonmanager_git.py addonmanager_macro.py addonmanager_utilities.py @@ -28,6 +29,7 @@ SET(AddonManager_SRCS developer_mode_copyright_info.ui developer_mode_dependencies.ui developer_mode_license.ui + developer_mode_people.ui developer_mode_select_from_list.ui expanded_view.py first_run.ui diff --git a/src/Mod/AddonManager/addonmanager_devmode.py b/src/Mod/AddonManager/addonmanager_devmode.py index 41368c4654..9cc4a83a11 100644 --- a/src/Mod/AddonManager/addonmanager_devmode.py +++ b/src/Mod/AddonManager/addonmanager_devmode.py @@ -23,22 +23,28 @@ """ Classes to manage "Developer Mode" """ import os -from typing import Optional import FreeCAD import FreeCADGui -from PySide2.QtWidgets import QFileDialog, QTableWidgetItem, QDialog -from PySide2.QtGui import QIcon, QValidator, QRegularExpressionValidator, QPixmap, QDesktopServices -from PySide2.QtCore import QRegularExpression, QUrl, QFile, QIODevice +from PySide2.QtWidgets import QFileDialog, QTableWidgetItem +from PySide2.QtGui import ( + QIcon, + QValidator, + QRegularExpressionValidator, + QPixmap, +) +from PySide2.QtCore import QRegularExpression, Qt from addonmanager_git import GitManager from addonmanager_devmode_license_selector import LicenseSelector +from addonmanager_devmode_person_editor import PersonEditor translate = FreeCAD.Qt.translate # pylint: disable=too-few-public-methods + class AddonGitInterface: """Wrapper to handle the git calls needed by this class""" @@ -153,6 +159,13 @@ class DeveloperMode: """The main Developer Mode dialog, for editing package.xml metadata graphically.""" def __init__(self): + + # In the UI we want to show a translated string for the person type, but the underlying + # string must be the one expected by the metadata parser, in English + self.person_type_translation = { + "maintainer": translate("AddonsInstaller", "Maintainer"), + "author": translate("AddonsInstaller", "Author"), + } self.dialog = FreeCADGui.PySideUic.loadUi( os.path.join(os.path.dirname(__file__), "developer_mode.ui") ) @@ -246,22 +259,12 @@ class DeveloperMode: for maintainer in metadata.Maintainer: name = maintainer["name"] email = maintainer["email"] - self.dialog.peopleTableWidget.insertRow(row) - self.dialog.peopleTableWidget.setItem( - row, 0, QTableWidgetItem(translate("AddonsInstaller", "Maintainer")) - ) - self.dialog.peopleTableWidget.setItem(row, 1, QTableWidgetItem(name)) - self.dialog.peopleTableWidget.setItem(row, 2, QTableWidgetItem(email)) + self._add_person_row(row, "maintainer", name, email) row += 1 for author in metadata.Author: name = author["name"] email = author["email"] - self.dialog.peopleTableWidget.insertRow(row) - self.dialog.peopleTableWidget.setItem( - row, 0, QTableWidgetItem(translate("AddonsInstaller", "Author")) - ) - self.dialog.peopleTableWidget.setItem(row, 1, QTableWidgetItem(name)) - self.dialog.peopleTableWidget.setItem(row, 2, QTableWidgetItem(email)) + self._add_person_row(row, "author", name, email) row += 1 if row == 0: @@ -273,6 +276,15 @@ class DeveloperMode: + "\n" ) + def _add_person_row(self, row, person_type, name, email): + """Add this person to the peopleTableWidget at row given""" + self.dialog.peopleTableWidget.insertRow(row) + item = QTableWidgetItem(self.person_type_translation[person_type]) + item.setData(Qt.UserRole, person_type) + self.dialog.peopleTableWidget.setItem(row, 0, item) + self.dialog.peopleTableWidget.setItem(row, 1, QTableWidgetItem(name)) + self.dialog.peopleTableWidget.setItem(row, 2, QTableWidgetItem(email)) + def _populate_licenses_from_metadata(self, metadata): """Use the passed metadata object to populate the licenses""" self.dialog.licensesTableWidget.setRowCount(0) @@ -432,12 +444,22 @@ class DeveloperMode: self.dialog.pathToAddonComboBox.editTextChanged.connect( self._addon_combo_text_changed ) + self.dialog.addLicenseToolButton.clicked.connect(self._add_license_clicked) - self.dialog.removeLicenseToolButton.clicked.connect(self._remove_license_clicked) + self.dialog.removeLicenseToolButton.clicked.connect( + self._remove_license_clicked + ) + self.dialog.licensesTableWidget.itemSelectionChanged.connect( + self._license_selection_changed + ) + self.dialog.licensesTableWidget.itemDoubleClicked.connect(self._edit_license) + self.dialog.addPersonToolButton.clicked.connect(self._add_person_clicked) self.dialog.removePersonToolButton.clicked.connect(self._remove_person_clicked) - self.dialog.peopleTableWidget.itemSelectionChanged.connect(self._person_selection_changed) - self.dialog.licensesTableWidget.itemSelectionChanged.connect(self._license_selection_changed) + self.dialog.peopleTableWidget.itemSelectionChanged.connect( + self._person_selection_changed + ) + self.dialog.peopleTableWidget.itemDoubleClicked.connect(self._edit_person) # Finally, populate the combo boxes, etc. self._populate_combo() @@ -552,8 +574,21 @@ class DeveloperMode: # the first entry self.dialog.licensesTableWidget.removeRow(items[0].row()) + def _edit_license(self, item): + row = item.row() + short_code = self.dialog.licensesTableWidget.item(row, 0).text() + path = self.dialog.licensesTableWidget.item(row, 1).text() + license_selector = LicenseSelector(self.current_mod) + short_code, path = license_selector.exec(short_code, path) + if short_code: + self.dialog.licensesTableWidget.removeRow(row) + self._add_license_row(row, short_code, path) + def _add_person_clicked(self): - pass + dlg = PersonEditor() + person_type, name, email = dlg.exec() + if person_type and name: + self._add_person_row(row, person_type, name, email) def _remove_person_clicked(self): items = self.dialog.peopleTableWidget.selectedIndexes() @@ -562,3 +597,17 @@ class DeveloperMode: # the first entry self.dialog.peopleTableWidget.removeRow(items[0].row()) + def _edit_person(self, item): + row = item.row() + person_type = self.dialog.peopleTableWidget.item(row, 0).data(Qt.UserRole) + name = self.dialog.peopleTableWidget.item(row, 1).text() + email = self.dialog.peopleTableWidget.item(row, 2).text() + + dlg = PersonEditor() + dlg.setup(person_type, name, email) + person_type, name, email = dlg.exec() + + if person_type and name: + self.dialog.peopleTableWidget.removeRow(row) + self._add_person_row(row, person_type, name, email) + self.dialog.peopleTableWidget.selectRow(row) diff --git a/src/Mod/AddonManager/addonmanager_devmode_license_selector.py b/src/Mod/AddonManager/addonmanager_devmode_license_selector.py index 4f6643d5a8..5a9bcb37be 100644 --- a/src/Mod/AddonManager/addonmanager_devmode_license_selector.py +++ b/src/Mod/AddonManager/addonmanager_devmode_license_selector.py @@ -114,15 +114,14 @@ class LicenseSelector: short_code = self.pref.GetString("devModeLastSelectedLicense", "LGPLv2.1") self.set_license(short_code) - def exec(self, short_code: str = None, license_path: str = None) -> Optional[str]: + def exec(self, short_code: str = None, license_path: str = "") -> Optional[str]: """The main method for executing this dialog, as a modal that returns a tuple of the license's "short code" and optionally the path to the license file. Returns a tuple of None,None if the user cancels the operation.""" if short_code: self.set_license(short_code) - if license_path: - self.dialog.pathLineEdit.setText(license_path) + self.dialog.pathLineEdit.setText(license_path) result = self.dialog.exec() if result == QDialog.Accepted: new_short_code = self.dialog.comboBox.currentData() @@ -188,17 +187,20 @@ class LicenseSelector: dir=start_dir, ) if license_path: - self._set_path(start_dir, license_path) + self._set_path(self.path_to_addon, license_path) def _set_path(self, start_dir: str, license_path: str): """Sets the value displayed in the path widget to the relative path from start_dir to license_path""" license_path = license_path.replace("/", os.path.sep) - if not license_path.startswith(start_dir.replace("/", os.path.sep)): + base_dir = start_dir.replace("/", os.path.sep) + if base_dir[-1] != os.path.sep: + base_dir += os.path.sep + if not license_path.startswith(base_dir): FreeCAD.Console.PrintError("Selected file not in Addon\n") - # Eventually offer to copy it... + # Eventually offer to copy it? return - relative_path = license_path[len(start_dir) :] + relative_path = license_path[len(base_dir) :] relative_path = relative_path.replace(os.path.sep, "/") self.dialog.pathLineEdit.setText(relative_path) diff --git a/src/Mod/AddonManager/addonmanager_devmode_person_editor.py b/src/Mod/AddonManager/addonmanager_devmode_person_editor.py new file mode 100644 index 0000000000..6efcd8dbf8 --- /dev/null +++ b/src/Mod/AddonManager/addonmanager_devmode_person_editor.py @@ -0,0 +1,77 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2022 FreeCAD Project Association * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, but * +# * WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# *************************************************************************** + +""" Contains a class to handle editing a person (from a Metadata standpoint). """ + +import os +from typing import Tuple # Needed until Py 3.9, when tuple supports this directly + +from PySide2.QtWidgets import QDialog + +import FreeCAD +import FreeCADGui + +translate = FreeCAD.Qt.translate + + +class PersonEditor: + """Create or edit a maintainer or author record.""" + + def __init__(self): + + self.dialog = FreeCADGui.PySideUic.loadUi( + os.path.join(os.path.dirname(__file__), "developer_mode_people.ui") + ) + self.dialog.comboBox.clear() + self.dialog.comboBox.addItem( + translate("AddonsInstaller", "Maintainer"), userData="maintainer" + ) + self.dialog.comboBox.addItem( + translate("AddonsInstaller", "Author"), userData="author" + ) + + def exec(self) -> Tuple[str, str, str]: + """Run the dialog, and return a tuple of the person's record type, their name, and their + email address. Email may be None. If the others are None it's because the user cancelled + the interaction.""" + result = self.dialog.exec() + if result == QDialog.Accepted: + return ( + self.dialog.comboBox.currentData(), + self.dialog.nameLineEdit.text(), + self.dialog.emailLineEdit.text(), + ) + return (None, None, None) + + def setup( + self, person_type: str = "maintainer", name: str = "", email: str = "" + ) -> None: + """Configure the dialog""" + index = self.dialog.comboBox.findData(person_type) + if index == -1: + FreeCAD.Console.PrintWarning( + f"Internal Error: unrecognized person type {person_type}" + ) + index = 0 + self.dialog.comboBox.setCurrentIndex(index) + self.dialog.nameLineEdit.setText(name) + self.dialog.emailLineEdit.setText(email) diff --git a/src/Mod/AddonManager/developer_mode_people.ui b/src/Mod/AddonManager/developer_mode_people.ui new file mode 100644 index 0000000000..1dc0c828fb --- /dev/null +++ b/src/Mod/AddonManager/developer_mode_people.ui @@ -0,0 +1,99 @@ + + + personDialog + + + + 0 + 0 + 313 + 118 + + + + Add Person + + + + + + A maintainer is someone with current commit access on this project. An author is anyone else you'd like to give credit to. + + + + + + + + + Name: + + + + + + + + + + Email: + + + + + + + Email is required for maintainers, and optional for authors. + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + personDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + personDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +