Addon Manager: Add license selector
This commit is contained in:
260
src/Mod/AddonManager/addonmanager_devmode_license_selector.py
Normal file
260
src/Mod/AddonManager/addonmanager_devmode_license_selector.py
Normal file
@@ -0,0 +1,260 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * 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 manage selection of a license for an Addon. """
|
||||
|
||||
import os
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
from PySide2.QtWidgets import QFileDialog, QDialog
|
||||
from PySide2.QtGui import QDesktopServices, QRegularExpressionValidator
|
||||
from PySide2.QtCore import QUrl, QFile, QRegularExpression, QIODevice
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class LicenseSelector:
|
||||
"""Choose from a selection of licenses, or provide your own. Includes the capability to create
|
||||
the license file itself for a variety of popular open-source licenses, as well as providing
|
||||
links to opensource.org's page about the various licenses (which often link to other resources).
|
||||
"""
|
||||
|
||||
licenses = {
|
||||
"Apache-2.0": (
|
||||
"Apache License, Version 2.0",
|
||||
"https://opensource.org/licenses/Apache-2.0",
|
||||
),
|
||||
"BSD-2-Clause": (
|
||||
"The 2-Clause BSD License",
|
||||
"https://opensource.org/licenses/BSD-2-Clause",
|
||||
),
|
||||
"BSD-3-Clause": (
|
||||
"The 3-Clause BSD License",
|
||||
"https://opensource.org/licenses/BSD-3-Clause",
|
||||
),
|
||||
"CC0v1": (
|
||||
"No Rights Reserved/Public Domain",
|
||||
"https://creativecommons.org/choose/zero/",
|
||||
),
|
||||
"GPLv2": (
|
||||
"GNU Lesser General Public License version 2",
|
||||
"https://opensource.org/licenses/GPL-2.0",
|
||||
),
|
||||
"GPLv3": (
|
||||
"GNU General Public License version 3",
|
||||
"https://opensource.org/licenses/GPL-3.0",
|
||||
),
|
||||
"LGPLv2.1": (
|
||||
"GNU Lesser General Public License version 2.1",
|
||||
"https://opensource.org/licenses/LGPL-2.1",
|
||||
),
|
||||
"LGPLv3": (
|
||||
"GNU Lesser General Public License version 3",
|
||||
"https://opensource.org/licenses/LGPL-3.0",
|
||||
),
|
||||
"MIT": (
|
||||
"The MIT License",
|
||||
"https://opensource.org/licenses/MIT",
|
||||
),
|
||||
"MPL-2.0": (
|
||||
"Mozilla Public License 2.0",
|
||||
"https://opensource.org/licenses/MPL-2.0",
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, path_to_addon):
|
||||
self.other_label = translate(
|
||||
"AddonsInstaller",
|
||||
"Other...",
|
||||
"For providing a license other than one listed",
|
||||
)
|
||||
self.path_to_addon = path_to_addon
|
||||
self.dialog = FreeCADGui.PySideUic.loadUi(
|
||||
os.path.join(os.path.dirname(__file__), "developer_mode_license.ui")
|
||||
)
|
||||
self.pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
for short_code, details in LicenseSelector.licenses.items():
|
||||
self.dialog.comboBox.addItem(
|
||||
f"{short_code}: {details[0]}", userData=short_code
|
||||
)
|
||||
self.dialog.comboBox.addItem(self.other_label)
|
||||
self.dialog.otherLineEdit.hide()
|
||||
self.dialog.otherLabel.hide()
|
||||
|
||||
# Connections:
|
||||
self.dialog.comboBox.currentIndexChanged.connect(self._selection_changed)
|
||||
self.dialog.aboutButton.clicked.connect(self._about_clicked)
|
||||
self.dialog.browseButton.clicked.connect(self._browse_clicked)
|
||||
self.dialog.createButton.clicked.connect(self._create_clicked)
|
||||
|
||||
# Set up the first selection to whatever the user chose last time
|
||||
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]:
|
||||
"""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)
|
||||
result = self.dialog.exec()
|
||||
if result == QDialog.Accepted:
|
||||
new_short_code = self.dialog.comboBox.currentData()
|
||||
new_license_path = self.dialog.pathLineEdit.text()
|
||||
if not new_short_code:
|
||||
new_short_code = self.dialog.otherLineEdit.text()
|
||||
self.pref.SetString("devModeLastSelectedLicense", new_short_code)
|
||||
return new_short_code, new_license_path
|
||||
return None, None
|
||||
|
||||
def set_license(self, short_code):
|
||||
"""Set the currently-selected license."""
|
||||
index = self.dialog.comboBox.findData(short_code)
|
||||
if index != -1:
|
||||
self.dialog.comboBox.setCurrentIndex(index)
|
||||
else:
|
||||
self.dialog.comboBox.setCurrentText(self.other_label)
|
||||
self.dialog.otherLineEdit.setText(short_code)
|
||||
|
||||
def _selection_changed(self, _: int):
|
||||
"""Callback: when the license selection changes, the UI is updated here."""
|
||||
if self.dialog.comboBox.currentText() == self.other_label:
|
||||
self.dialog.otherLineEdit.clear()
|
||||
self.dialog.otherLineEdit.show()
|
||||
self.dialog.otherLabel.show()
|
||||
self.dialog.aboutButton.setDisabled(True)
|
||||
else:
|
||||
self.dialog.otherLineEdit.hide()
|
||||
self.dialog.otherLabel.hide()
|
||||
self.dialog.aboutButton.setDisabled(False)
|
||||
|
||||
def _current_short_code(self) -> str:
|
||||
"""Gets the currently-selected license short code"""
|
||||
short_code = self.dialog.comboBox.currentData()
|
||||
if not short_code:
|
||||
short_code = self.dialog.otherLineEdit.text()
|
||||
return short_code
|
||||
|
||||
def _about_clicked(self):
|
||||
"""Callback: when the About button is clicked, try to launch a system-default web browser
|
||||
and display the OSI page about the currently-selected license."""
|
||||
short_code = self.dialog.comboBox.currentData()
|
||||
if short_code in LicenseSelector.licenses:
|
||||
url = LicenseSelector.licenses[short_code][1]
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
f"Internal Error: unrecognized license short code {short_code}\n"
|
||||
)
|
||||
|
||||
def _browse_clicked(self):
|
||||
"""Callback: browse for an existing license file."""
|
||||
start_dir = os.path.join(
|
||||
self.path_to_addon,
|
||||
self.dialog.pathLineEdit.text().replace("/", os.path.sep),
|
||||
)
|
||||
license_path, _ = QFileDialog.getOpenFileName(
|
||||
parent=self.dialog,
|
||||
caption=translate(
|
||||
"AddonsInstaller",
|
||||
"Select the corresponding license file in your Addon",
|
||||
),
|
||||
dir=start_dir,
|
||||
)
|
||||
if license_path:
|
||||
self._set_path(start_dir, 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)):
|
||||
FreeCAD.Console.PrintError("Selected file not in Addon\n")
|
||||
# Eventually offer to copy it...
|
||||
return
|
||||
relative_path = license_path[len(start_dir) :]
|
||||
relative_path = relative_path.replace(os.path.sep, "/")
|
||||
self.dialog.pathLineEdit.setText(relative_path)
|
||||
|
||||
def _create_clicked(self):
|
||||
"""Asks the users for the path to save the new license file to, then copies our internal
|
||||
copy of the license text to that file."""
|
||||
start_dir = os.path.join(
|
||||
self.path_to_addon,
|
||||
self.dialog.pathLineEdit.text().replace("/", os.path.sep),
|
||||
)
|
||||
license_path, _ = QFileDialog.getSaveFileName(
|
||||
parent=self.dialog,
|
||||
caption=translate(
|
||||
"AddonsInstaller",
|
||||
"Location for new license file",
|
||||
),
|
||||
dir=os.path.join(start_dir, "LICENSE"),
|
||||
)
|
||||
if license_path:
|
||||
self._set_path(start_dir, license_path)
|
||||
short_code = self._current_short_code()
|
||||
qf = QFile(f":/licenses/{short_code}.txt")
|
||||
if qf.exists():
|
||||
qf.open(QIODevice.ReadOnly)
|
||||
byte_data = qf.readAll()
|
||||
qf.close()
|
||||
|
||||
string_data = str(byte_data, encoding="utf-8")
|
||||
|
||||
if (
|
||||
"<%%YEAR%%>" in string_data
|
||||
or "<%%COPYRIGHT HOLDER%%>" in string_data
|
||||
):
|
||||
info_dlg = FreeCADGui.PySideUic.loadUi(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"developer_mode_copyright_info.ui",
|
||||
)
|
||||
)
|
||||
info_dlg.yearLineEdit.setValidator(
|
||||
QRegularExpressionValidator(QRegularExpression("^[12]\\d{3}$"))
|
||||
)
|
||||
info_dlg.yearLineEdit.setText(str(date.today().year))
|
||||
result = info_dlg.exec()
|
||||
if result != QDialog.Accepted:
|
||||
return # Don't create the file, just bail out
|
||||
|
||||
holder = info_dlg.copyrightHolderLineEdit.text()
|
||||
year = info_dlg.yearLineEdit.text()
|
||||
|
||||
string_data = string_data.replace("<%%YEAR%%>", year)
|
||||
string_data = string_data.replace("<%%COPYRIGHT HOLDER%%>", holder)
|
||||
|
||||
with open(license_path, "w", encoding="utf-8") as f:
|
||||
f.write(string_data)
|
||||
else:
|
||||
FreeCAD.Console.PrintError(
|
||||
f"Cannot create license file of type {short_code}\n"
|
||||
)
|
||||
Reference in New Issue
Block a user