Addon Manager: Add person editor

This commit is contained in:
Chris Hennes
2022-08-31 19:18:21 -05:00
parent 5afdfea01b
commit 669d0ac13b
5 changed files with 256 additions and 27 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
""" 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)

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>personDialog</class>
<widget class="QDialog" name="personDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>313</width>
<height>118</height>
</rect>
</property>
<property name="windowTitle">
<string>Add Person</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QComboBox" name="comboBox">
<property name="toolTip">
<string>A maintainer is someone with current commit access on this project. An author is anyone else you'd like to give credit to.</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Email:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="emailLineEdit">
<property name="toolTip">
<string>Email is required for maintainers, and optional for authors.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>personDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>personDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>