From 7b751ebb37676969ab21f4b1978b3f53c8d3b15f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 2 Feb 2024 22:04:52 +0100 Subject: [PATCH 01/25] Addon Manager: Refactor main GUI area --- src/Mod/AddonManager/AddonManager.py | 4 +- src/Mod/AddonManager/CMakeLists.txt | 1 + .../AddonManager/Resources/AddonManager.qrc | 1 + .../Resources/icons/composite_view.svg | 13 + src/Mod/AddonManager/Widgets/CMakeLists.txt | 22 ++ src/Mod/AddonManager/Widgets/__init__.py | 0 .../addonmanager_widget_filter_selector.py | 243 ++++++++++++++++++ .../Widgets/addonmanager_widget_search.py | 103 ++++++++ .../Widgets/addonmanager_widget_top_bar.py | 159 ++++++++++++ .../addonmanager_widget_view_selector.py | 152 +++++++++++ src/Mod/AddonManager/compact_view.py | 5 + src/Mod/AddonManager/composite_view.py | 56 ++++ src/Mod/AddonManager/package_list.py | 221 +++------------- 13 files changed, 793 insertions(+), 187 deletions(-) create mode 100644 src/Mod/AddonManager/Resources/icons/composite_view.svg create mode 100644 src/Mod/AddonManager/Widgets/CMakeLists.txt create mode 100644 src/Mod/AddonManager/Widgets/__init__.py create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_search.py create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_top_bar.py create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py create mode 100644 src/Mod/AddonManager/composite_view.py diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 13fcfb6e0f..fad624cbdb 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -481,7 +481,7 @@ class CommandAddonManager: def activate_table_widgets(self) -> None: self.packageList.setEnabled(True) - self.packageList.ui.lineEditFilter.setFocus() + self.packageList.ui.search_box.setFocus() self.do_next_startup_phase() def populate_macros(self) -> None: @@ -824,7 +824,7 @@ class CommandAddonManager: self.dialog.labelStatusInfo.hide() self.dialog.progressBar.hide() self.dialog.buttonPauseUpdate.hide() - self.packageList.ui.lineEditFilter.setFocus() + self.packageList.ui.search_box.setFocus() def show_progress_widgets(self) -> None: if self.dialog.progressBar.isHidden(): diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index bedb988620..1948b69d14 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -1,5 +1,6 @@ IF (BUILD_GUI) PYSIDE_WRAP_RC(AddonManager_QRC_SRCS Resources/AddonManager.qrc) + add_subdirectory(Widgets) ENDIF (BUILD_GUI) SET(AddonManager_SRCS diff --git a/src/Mod/AddonManager/Resources/AddonManager.qrc b/src/Mod/AddonManager/Resources/AddonManager.qrc index 2d006a46b0..e9b5a90e90 100644 --- a/src/Mod/AddonManager/Resources/AddonManager.qrc +++ b/src/Mod/AddonManager/Resources/AddonManager.qrc @@ -64,6 +64,7 @@ icons/workfeature_workbench_icon.svg icons/yaml-workspace_workbench_icon.svg icons/compact_view.svg + icons/composite_view.svg icons/expanded_view.svg licenses/Apache-2.0.txt licenses/BSD-2-Clause.txt diff --git a/src/Mod/AddonManager/Resources/icons/composite_view.svg b/src/Mod/AddonManager/Resources/icons/composite_view.svg new file mode 100644 index 0000000000..7f1bf475be --- /dev/null +++ b/src/Mod/AddonManager/Resources/icons/composite_view.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/Mod/AddonManager/Widgets/CMakeLists.txt b/src/Mod/AddonManager/Widgets/CMakeLists.txt new file mode 100644 index 0000000000..2f1f6762a5 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/CMakeLists.txt @@ -0,0 +1,22 @@ +SET(AddonManagerWidget_SRCS + __init__.py + addonmanager_widget_filter_selector.py + addonmanager_widget_search.py + addonmanager_widget_top_bar.py + addonmanager_widget_view_selector.py +) + +SOURCE_GROUP("" FILES ${AddonManagerWidget_SRCS}) + +ADD_CUSTOM_TARGET(AddonManagerWidget ALL + SOURCES ${AddonManagerWidget_SRCS} +) + +fc_copy_sources(AddonManagerWidget "${CMAKE_BINARY_DIR}/Mod/AddonManager/Widgets" ${AddonManagerWidget_SRCS}) + +INSTALL( + FILES + ${AddonManagerWidget_SRCS} + DESTINATION + Mod/AddonManager/Widgets +) diff --git a/src/Mod/AddonManager/Widgets/__init__.py b/src/Mod/AddonManager/Widgets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py new file mode 100644 index 0000000000..906dc02e57 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py @@ -0,0 +1,243 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a QWidget-derived class for displaying the view selection buttons. """ + +from enum import IntEnum + +try: + import FreeCAD + + translate = FreeCAD.Qt.translate +except ImportError: + FreeCAD = None + + def translate(_: str, text: str): + return text + + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtWidgets + + +class FilterType(IntEnum): + """There are currently two sections in this drop down, for two different types of filters.""" + + PACKAGE_CONTENTS = 0 + INSTALLATION_STATUS = 1 + + +class StatusFilter(IntEnum): + """Predefined filters for status""" + + ANY = 0 + INSTALLED = 1 + NOT_INSTALLED = 2 + UPDATE_AVAILABLE = 3 + + +class ContentFilter(IntEnum): + """Predefined filters for addon content type""" + + ANY = 0 + WORKBENCH = 1 + MACRO = 2 + PREFERENCE_PACK = 3 + + +class Filter: + def __init__(self): + self.status_filter = StatusFilter.ANY + self.content_filter = ContentFilter.ANY + + +class WidgetFilterSelector(QtWidgets.QComboBox): + """A label and menu for selecting what sort of addons are displayed""" + + filter_changed = QtCore.Signal(object) # technically, actually class Filter + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self.addon_type_index = 0 + self.installation_status_index = 0 + self._setup_ui() + self._setup_connections() + self.retranslateUi(None) + self.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + + def _setup_ui(self): + self._build_menu() + + def _build_menu(self): + self.clear() + self.addItem(translate("AddonsInstaller", "Filter by...")) + self.insertSeparator(self.count()) + self.addItem(translate("AddonsInstaller", "Addon Type")) + self.addon_type_index = self.count() - 1 + self.addItem( + translate("AddonsInstaller", "Any"), (FilterType.PACKAGE_CONTENTS, ContentFilter.ANY) + ) + self.addItem( + translate("AddonsInstaller", "Workbench"), + (FilterType.PACKAGE_CONTENTS, ContentFilter.WORKBENCH), + ) + self.addItem( + translate("AddonsInstaller", "Macro"), + (FilterType.PACKAGE_CONTENTS, ContentFilter.MACRO), + ) + self.addItem( + translate("AddonsInstaller", "Preference Pack"), + (FilterType.PACKAGE_CONTENTS, ContentFilter.PREFERENCE_PACK), + ) + self.insertSeparator(self.count()) + self.addItem(translate("AddonsInstaller", "Installation Status")) + self.installation_status_index = self.count() - 1 + self.addItem( + translate("AddonsInstaller", "Any"), (FilterType.INSTALLATION_STATUS, StatusFilter.ANY) + ) + self.addItem( + translate("AddonsInstaller", "Not installed"), + (FilterType.INSTALLATION_STATUS, StatusFilter.NOT_INSTALLED), + ) + self.addItem( + translate("AddonsInstaller", "Installed"), + (FilterType.INSTALLATION_STATUS, StatusFilter.INSTALLED), + ) + self.addItem( + translate("AddonsInstaller", "Update available"), + (FilterType.INSTALLATION_STATUS, StatusFilter.UPDATE_AVAILABLE), + ) + model: QtCore.QAbstractItemModel = self.model() + for row in range(model.rowCount()): + if row <= self.addon_type_index: + model.item(row).setEnabled(False) + elif row < self.installation_status_index: + item = model.item(row) + item.setCheckState(QtCore.Qt.Unchecked) + elif row == self.installation_status_index: + model.item(row).setEnabled(False) + else: + item = model.item(row) + item.setCheckState(QtCore.Qt.Unchecked) + + for row in range(model.rowCount()): + data = self.itemData(row) + if data: + item = model.item(row) + if data[0] == FilterType.PACKAGE_CONTENTS and data[1] == ContentFilter.ANY: + item.setCheckState(QtCore.Qt.Checked) + elif data[0] == FilterType.INSTALLATION_STATUS and data[1] == StatusFilter.ANY: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + + def set_contents_filter(self, contents_filter: ContentFilter): + model = self.model() + for row in range(model.rowCount()): + item = model.item(row) + user_data = self.itemData(row) + if user_data and user_data[0] == FilterType.PACKAGE_CONTENTS: + if user_data[1] == contents_filter: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + + def set_status_filter(self, status_filter: StatusFilter): + model = self.model() + for row in range(model.rowCount()): + item = model.item(row) + user_data = self.itemData(row) + if user_data and user_data[0] == FilterType.INSTALLATION_STATUS: + if user_data[1] == status_filter: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + + def _setup_connections(self): + self.activated.connect(self._selected) + + def retranslateUi(self, _): + self._build_menu() + + def _selected(self, row: int): + if row == 0: + return + if row == self.installation_status_index or row == self.addon_type_index: + self.setCurrentIndex(0) + return + model = self.model() + selected_data = self.itemData(row) + if not selected_data: + return + selected_row_type = selected_data[0] + + for row in range(model.rowCount()): + item = model.item(row) + user_data = self.itemData(row) + if user_data and user_data[0] == selected_row_type: + if user_data[1] == selected_data[1]: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + self._emit_current_filter() + self.setCurrentIndex(0) + self._update_first_row_text() + + def _emit_current_filter(self): + model = self.model() + new_filter = Filter() + for row in range(model.rowCount()): + item = model.item(row) + data = self.itemData(row) + if data and item.checkState() == QtCore.Qt.Checked: + if data[0] == FilterType.INSTALLATION_STATUS: + new_filter.status_filter = data[1] + elif data[0] == FilterType.PACKAGE_CONTENTS: + new_filter.content_filter = data[1] + self.filter_changed.emit(new_filter) + + def _update_first_row_text(self): + model = self.model() + state1 = "" + state2 = "" + for row in range(model.rowCount()): + item = model.item(row) + if item.checkState() == QtCore.Qt.Checked: + if not state1: + state1 = item.text() + else: + state2 = item.text() + break + model.item(0).setText(translate("AddonsInstaller", "Filter") + f": {state1}, {state2}") diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py new file mode 100644 index 0000000000..12c0ffd52b --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a QWidget-derived class for displaying the view selection buttons. """ + +try: + import FreeCAD + + translate = FreeCAD.Qt.translate +except ImportError: + FreeCAD = None + + def translate(_: str, text: str): + return text + + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtGui, QtWidgets + + +class WidgetSearch(QtWidgets.QWidget): + """A widget for selecting the Addon Manager's primary view mode""" + + search_changed = QtCore.Signal(str) + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self._setup_ui() + self._setup_connections() + self.retranslateUi(None) + + def _setup_ui(self): + self.horizontal_layout = QtWidgets.QHBoxLayout() + self.filter_line_edit = QtWidgets.QLineEdit(self) + self.filter_line_edit.setClearButtonEnabled(True) + self.horizontal_layout.addWidget(self.filter_line_edit) + self.filter_validity_label = QtWidgets.QLabel(self) + self.horizontal_layout.addWidget(self.filter_validity_label) + self.filter_validity_label.hide() # This widget starts hidden + self.setLayout(self.horizontal_layout) + + def _setup_connections(self): + self.filter_line_edit.textChanged.connect(self.set_text_filter) + + def set_text_filter(self, text_filter: str) -> None: + """Set the current filter. If the filter is valid, this will emit a filter_changed + signal. text_filter may be regular expression.""" + + if text_filter: + test_regex = QtCore.QRegularExpression(text_filter) + if test_regex.isValid(): + self.filter_validity_label.setToolTip( + translate("AddonsInstaller", "Filter is valid") + ) + icon = QtGui.QIcon.fromTheme("ok", QtGui.QIcon(":/icons/edit_OK.svg")) + self.filter_validity_label.setPixmap(icon.pixmap(16, 16)) + else: + self.filter_validity_label.setToolTip( + translate("AddonsInstaller", "Filter regular expression is invalid") + ) + icon = QtGui.QIcon.fromTheme("cancel", QtGui.QIcon(":/icons/edit_Cancel.svg")) + self.filter_validity_label.setPixmap(icon.pixmap(16, 16)) + self.filter_validity_label.show() + else: + self.filter_validity_label.hide() + self.search_changed.emit(text_filter) + + def retranslateUi(self, _): + self.filter_line_edit.setPlaceholderText( + QtCore.QCoreApplication.translate("AddonsInstaller", "Filter", None) + ) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_top_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_top_bar.py new file mode 100644 index 0000000000..d1c6993d7e --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_top_bar.py @@ -0,0 +1,159 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a class derived from QWidget for displaying the bar at the top of the addons list. """ + +from enum import IntEnum +from addonmanager_widget_view_selector import WidgetViewSelector + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtWidgets + +translate = FreeCAD.Qt.translate + + +class StatusFilter(IntEnum): + """Predefined filers""" + + ANY = 0 + INSTALLED = 1 + NOT_INSTALLED = 2 + UPDATE_AVAILABLE = 3 + + +# pylint: disable=too-few-public-methods + + +class WidgetTopBar(QtWidgets.QWidget): + """A widget to display the buttons at the top of the Addon manager, for changing the view, + filtering, and sorting.""" + + view_changed = QtCore.Signal(int) + filter_changed = QtCore.Signal(str) + search_changed = QtCore.Signal(str) + + def __init__(self, parent=None): + super().__init__(parent) + self.horizontal_layout = None + self.package_type_label = None + self.package_type_combobox = None + self._setup_ui() + self._setup_connections() + self.retranslateUi() + + def _setup_ui(self): + self.horizontal_layout = QtWidgets.QHBoxLayout() + + self.view_selector = WidgetViewSelector(self) + self.horizontal_layout.addWidget(self.view_selector) + + self.package_type_label = QtWidgets.QLabel(self) + self.horizontal_layout.addWidget(self.package_type_label) + + self.package_type_combobox = QtWidgets.QComboBox(self) + self.horizontal_layout.addWidget(self.package_type_combobox) + + self.status_combo_box = QtWidgets.QComboBox(self) + self.horizontal_layout.addWidget(self.status_combo_box) + + self.filter_line_edit = QtWidgets.QLineEdit(self) + self.horizontal_layout.addWidget(self.filter_line_edit) + + # Only shows when the user types in a filter + self.ui.filter_validity_label = QtWidgets.QLabel(self) + self.horizontal_layout.addWidget(self.filter_validity_label) + self.ui.filter_validity_label.hide() + + def _setup_connections(self): + self.ui.view_selector.view_changed.connect(self.view_changed.emit) + + # Set up the view the same as the last time: + pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") + package_type = pref.GetInt("PackageTypeSelection", 1) + self.ui.comboPackageType.setCurrentIndex(package_type) + status = pref.GetInt("StatusSelection", 0) + self.ui.comboStatus.setCurrentIndex(status) + + def update_type_filter(self, type_filter: int) -> None: + """hide/show rows corresponding to the type filter + + type_filter is an integer: 0 for all, 1 for workbenches, 2 for macros, + and 3 for preference packs + + """ + + self.item_filter.setPackageFilter(type_filter) + pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") + pref.SetInt("PackageTypeSelection", type_filter) + + def update_status_filter(self, status_filter: int) -> None: + """hide/show rows corresponding to the status filter + + status_filter is an integer: 0 for any, 1 for installed, 2 for not installed, + and 3 for update available + + """ + + self.item_filter.setStatusFilter(status_filter) + pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") + pref.SetInt("StatusSelection", status_filter) + + def update_text_filter(self, text_filter: str) -> None: + """filter name and description by the regex specified by text_filter""" + + if text_filter: + if hasattr(self.item_filter, "setFilterRegularExpression"): # Added in Qt 5.12 + test_regex = QtCore.QRegularExpression(text_filter) + else: + test_regex = QtCore.QRegExp(text_filter) + if test_regex.isValid(): + self.ui.labelFilterValidity.setToolTip( + translate("AddonsInstaller", "Filter is valid") + ) + icon = QtGui.QIcon.fromTheme("ok", QtGui.QIcon(":/icons/edit_OK.svg")) + self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16)) + else: + self.ui.labelFilterValidity.setToolTip( + translate("AddonsInstaller", "Filter regular expression is invalid") + ) + icon = QtGui.QIcon.fromTheme("cancel", QtGui.QIcon(":/icons/edit_Cancel.svg")) + self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16)) + self.ui.labelFilterValidity.show() + else: + self.ui.labelFilterValidity.hide() + if hasattr(self.item_filter, "setFilterRegularExpression"): # Added in Qt 5.12 + self.item_filter.setFilterRegularExpression(text_filter) + else: + self.item_filter.setFilterRegExp(text_filter) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py new file mode 100644 index 0000000000..7bcb9ff7ba --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py @@ -0,0 +1,152 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a QWidget-derived class for displaying the view selection buttons. """ + +from enum import IntEnum + +try: + import FreeCAD + + translate = FreeCAD.Qt.translate +except ImportError: + FreeCAD = None + + def translate(context: str, text: str): + return text + + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtGui, QtWidgets + + +class AddonManagerDisplayStyle(IntEnum): + """The display mode of the Addon Manager""" + + COMPACT = 0 + EXPANDED = 1 + COMPOSITE = 2 + + +class WidgetViewSelector(QtWidgets.QWidget): + """A widget for selecting the Addon Manager's primary view mode""" + + view_changed = QtCore.Signal(int) + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self.horizontal_layout = None + self.composite_button = None + self.expanded_button = None + self.compact_button = None + self._setup_ui() + self._setup_connections() + + def set_current_view(self, view: AddonManagerDisplayStyle): + """Set the current selection. Does NOT emit a view_changed signal, only changes the + interface display.""" + self.compact_button.setChecked(False) + self.expanded_button.setChecked(False) + self.composite_button.setChecked(False) + if view == AddonManagerDisplayStyle.COMPACT: + self.compact_button.setChecked(True) + elif view == AddonManagerDisplayStyle.EXPANDED: + self.expanded_button.setChecked(True) + elif view == AddonManagerDisplayStyle.COMPOSITE: + self.composite_button.setChecked(True) + else: + if FreeCAD is not None: + FreeCAD.Console.PrintWarning(f"Unrecognized display style {view}") + + def _setup_ui(self): + self.horizontal_layout = QtWidgets.QHBoxLayout() + self.compact_button = QtWidgets.QToolButton(self) + self.compact_button.setObjectName("compact_button") + self.compact_button.setIcon( + QtGui.QIcon.fromTheme("back", QtGui.QIcon(":/icons/compact_view.svg")) + ) + self.compact_button.setCheckable(True) + self.compact_button.setAutoExclusive(True) + + self.expanded_button = QtWidgets.QToolButton(self) + self.expanded_button.setObjectName("expanded_button") + self.expanded_button.setCheckable(True) + self.expanded_button.setChecked(True) + self.expanded_button.setAutoExclusive(True) + self.expanded_button.setIcon( + QtGui.QIcon.fromTheme("expanded_view", QtGui.QIcon(":/icons/expanded_view.svg")) + ) + + self.composite_button = QtWidgets.QToolButton(self) + self.composite_button.setObjectName("expanded_button") + self.composite_button.setCheckable(True) + self.composite_button.setChecked(True) + self.composite_button.setAutoExclusive(True) + self.composite_button.setIcon( + QtGui.QIcon.fromTheme("composite_button", QtGui.QIcon(":/icons/composite_view.svg")) + ) + + self.horizontal_layout.addWidget(self.compact_button) + self.horizontal_layout.addWidget(self.expanded_button) + self.horizontal_layout.addWidget(self.composite_button) + + self.compact_button.clicked.connect( + lambda: self.view_changed.emit(AddonManagerDisplayStyle.COMPACT) + ) + self.expanded_button.clicked.connect( + lambda: self.view_changed.emit(AddonManagerDisplayStyle.EXPANDED) + ) + self.composite_button.clicked.connect( + lambda: self.view_changed.emit(AddonManagerDisplayStyle.COMPOSITE) + ) + + self.setLayout(self.horizontal_layout) + self.retranslateUi(None) + + def _setup_connections(self): + self.compact_button.clicked.connect( + lambda: self.view_changed.emit(AddonManagerDisplayStyle.COMPACT) + ) + self.expanded_button.clicked.connect( + lambda: self.view_changed.emit(AddonManagerDisplayStyle.EXPANDED) + ) + self.composite_button.clicked.connect( + lambda: self.view_changed.emit(AddonManagerDisplayStyle.COMPOSITE) + ) + + def retranslateUi(self, _): + self.composite_button.setToolTip(translate("AddonsInstaller", "Composite view")) + self.expanded_button.setToolTip(translate("AddonsInstaller", "Expanded view")) + self.compact_button.setToolTip(translate("AddonsInstaller", "Compact view")) diff --git a/src/Mod/AddonManager/compact_view.py b/src/Mod/AddonManager/compact_view.py index 2f9d549910..d92e6c4b8a 100644 --- a/src/Mod/AddonManager/compact_view.py +++ b/src/Mod/AddonManager/compact_view.py @@ -42,7 +42,12 @@ class Ui_CompactView(object): self.labelPackageName = QLabel(CompactView) self.labelPackageName.setObjectName("labelPackageName") + self.labelPackageNameSpacer = QLabel(CompactView) + self.labelPackageNameSpacer.setText(" — ") + self.labelPackageNameSpacer.setObjectName("labelPackageNameSpacer") + self.horizontalLayout_2.addWidget(self.labelPackageName) + self.horizontalLayout_2.addWidget(self.labelPackageNameSpacer) self.labelVersion = QLabel(CompactView) self.labelVersion.setObjectName("labelVersion") diff --git a/src/Mod/AddonManager/composite_view.py b/src/Mod/AddonManager/composite_view.py new file mode 100644 index 0000000000..b89041adad --- /dev/null +++ b/src/Mod/AddonManager/composite_view.py @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Provides a class for showing the list view and detail view at the same time. """ + +import addonmanager_freecad_interface + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtWidgets + + +class CompositeView(QtWidgets.QWidget): + """A widget that displays the Addon Manager's top bar, the list of Addons, and the detail + view, all on a single pane (with no switching). Detail view is shown in its "icon-only" mode + for the installation, etc. buttons. The bottom bar remains visible throughout.""" + + def __init__(self, parent=None): + super().__init__(parent) + + # TODO: Refactor the Addon Manager's display into four custom widgets: + # 1) The top bar showing the filter and search + # 2) The package list widget, which can take three forms (expanded, compact, and list) + # 3) The installer bar, which can take two forms (text and icon) + # 4) The bottom bar diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index f6676feaa5..6279990c9c 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -37,6 +37,9 @@ from expanded_view import Ui_ExpandedView import addonmanager_utilities as utils from addonmanager_metadata import get_first_supported_freecad_version, Version +from Widgets.addonmanager_widget_view_selector import WidgetViewSelector, AddonManagerDisplayStyle +from Widgets.addonmanager_widget_search import WidgetSearch +from Widgets.addonmanager_widget_filter_selector import WidgetFilterSelector, StatusFilter, Filter translate = FreeCAD.Qt.translate @@ -44,22 +47,6 @@ translate = FreeCAD.Qt.translate # pylint: disable=too-few-public-methods -class ListDisplayStyle(IntEnum): - """The display mode of the list""" - - COMPACT = 0 - EXPANDED = 1 - - -class StatusFilter(IntEnum): - """Predefined filers""" - - ANY = 0 - INSTALLED = 1 - NOT_INSTALLED = 2 - UPDATE_AVAILABLE = 3 - - class PackageList(QtWidgets.QWidget): """A widget that shows a list of packages and various widgets to control the display of the list""" @@ -77,25 +64,16 @@ class PackageList(QtWidgets.QWidget): self.ui.listPackages.setItemDelegate(self.item_delegate) self.ui.listPackages.clicked.connect(self.on_listPackages_clicked) - self.ui.comboPackageType.currentIndexChanged.connect(self.update_type_filter) - self.ui.comboStatus.currentIndexChanged.connect(self.update_status_filter) - self.ui.lineEditFilter.textChanged.connect(self.update_text_filter) - self.ui.buttonCompactLayout.clicked.connect( - lambda: self.set_view_style(ListDisplayStyle.COMPACT) - ) - self.ui.buttonExpandedLayout.clicked.connect( - lambda: self.set_view_style(ListDisplayStyle.EXPANDED) - ) - - # Only shows when the user types in a filter - self.ui.labelFilterValidity.hide() + self.ui.filter_selector.filter_changed.connect(self.update_status_filter) + self.ui.search_box.search_changed.connect(self.item_filter.setFilterRegularExpression) + self.ui.view_selector.view_changed.connect(self.set_view_style) # Set up the view the same as the last time: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") package_type = pref.GetInt("PackageTypeSelection", 1) - self.ui.comboPackageType.setCurrentIndex(package_type) status = pref.GetInt("StatusSelection", 0) - self.ui.comboStatus.setCurrentIndex(status) + self.ui.filter_selector.set_contents_filter(package_type) + self.ui.filter_selector.set_status_filter(status) # Pre-init of other members: self.item_model = None @@ -107,12 +85,9 @@ class PackageList(QtWidgets.QWidget): self.item_filter.sort(0) pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") - style = pref.GetInt("ViewStyle", ListDisplayStyle.EXPANDED) + style = pref.GetInt("ViewStyle", AddonManagerDisplayStyle.EXPANDED) self.set_view_style(style) - if style == ListDisplayStyle.EXPANDED: - self.ui.buttonExpandedLayout.setChecked(True) - else: - self.ui.buttonCompactLayout.setChecked(True) + self.ui.view_selector.set_current_view(style) self.item_filter.setHidePy2(pref.GetBool("HidePy2", True)) self.item_filter.setHideObsolete(pref.GetBool("HideObsolete", True)) @@ -125,63 +100,21 @@ class PackageList(QtWidgets.QWidget): selected_repo = self.item_model.repos[source_selection.row()] self.itemSelected.emit(selected_repo) - def update_type_filter(self, type_filter: int) -> None: - """hide/show rows corresponding to the type filter + def update_status_filter(self, new_filter: Filter) -> None: + """hide/show rows corresponding to the specified filter""" - type_filter is an integer: 0 for all, 1 for workbenches, 2 for macros, - and 3 for preference packs - - """ - - self.item_filter.setPackageFilter(type_filter) + self.item_filter.setStatusFilter(new_filter.status_filter) + self.item_filter.setPackageFilter(new_filter.content_filter) pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") - pref.SetInt("PackageTypeSelection", type_filter) + pref.SetInt("StatusSelection", new_filter.status_filter) + pref.SetInt("PackageTypeSelection", new_filter.content_filter) - def update_status_filter(self, status_filter: int) -> None: - """hide/show rows corresponding to the status filter - - status_filter is an integer: 0 for any, 1 for installed, 2 for not installed, - and 3 for update available - - """ - - self.item_filter.setStatusFilter(status_filter) - pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") - pref.SetInt("StatusSelection", status_filter) - - def update_text_filter(self, text_filter: str) -> None: - """filter name and description by the regex specified by text_filter""" - - if text_filter: - if hasattr(self.item_filter, "setFilterRegularExpression"): # Added in Qt 5.12 - test_regex = QtCore.QRegularExpression(text_filter) - else: - test_regex = QtCore.QRegExp(text_filter) - if test_regex.isValid(): - self.ui.labelFilterValidity.setToolTip( - translate("AddonsInstaller", "Filter is valid") - ) - icon = QtGui.QIcon.fromTheme("ok", QtGui.QIcon(":/icons/edit_OK.svg")) - self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16)) - else: - self.ui.labelFilterValidity.setToolTip( - translate("AddonsInstaller", "Filter regular expression is invalid") - ) - icon = QtGui.QIcon.fromTheme("cancel", QtGui.QIcon(":/icons/edit_Cancel.svg")) - self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16)) - self.ui.labelFilterValidity.show() - else: - self.ui.labelFilterValidity.hide() - if hasattr(self.item_filter, "setFilterRegularExpression"): # Added in Qt 5.12 - self.item_filter.setFilterRegularExpression(text_filter) - else: - self.item_filter.setFilterRegExp(text_filter) - - def set_view_style(self, style: ListDisplayStyle) -> None: + def set_view_style(self, style: AddonManagerDisplayStyle) -> None: """Set the style (compact or expanded) of the list""" self.item_model.layoutAboutToBeChanged.emit() self.item_delegate.set_view(style) - if style == ListDisplayStyle.COMPACT: + # TODO: Update to support composite + if style == AddonManagerDisplayStyle.COMPACT: self.ui.listPackages.setSpacing(2) else: self.ui.listPackages.setSpacing(5) @@ -324,12 +257,12 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, parent=None): super().__init__(parent) - self.displayStyle = ListDisplayStyle.EXPANDED + self.displayStyle = AddonManagerDisplayStyle.EXPANDED self.expanded = ExpandedView() self.compact = CompactView() self.widget = self.expanded - def set_view(self, style: ListDisplayStyle) -> None: + def set_view(self, style: AddonManagerDisplayStyle) -> None: """Set the view of to style""" if not self.displayStyle == style: self.displayStyle = style @@ -343,7 +276,7 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate): def update_content(self, index): """Creates the display of the content for a given index.""" repo = index.data(PackageListItemModel.DataAccessRole) - if self.displayStyle == ListDisplayStyle.EXPANDED: + if self.displayStyle == AddonManagerDisplayStyle.EXPANDED: self.widget = self.expanded self.widget.ui.labelPackageName.setText(f"

{repo.display_name}

") self.widget.ui.labelIcon.setPixmap(repo.icon.pixmap(QtCore.QSize(48, 48))) @@ -353,23 +286,23 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate): self.widget.ui.labelIcon.setPixmap(repo.icon.pixmap(QtCore.QSize(16, 16))) self.widget.ui.labelIcon.setText("") - if self.displayStyle == ListDisplayStyle.EXPANDED: + if self.displayStyle == AddonManagerDisplayStyle.EXPANDED: self.widget.ui.labelTags.setText("") if repo.metadata: self.widget.ui.labelDescription.setText(repo.metadata.description) self.widget.ui.labelVersion.setText(f"v{repo.metadata.version}") - if self.displayStyle == ListDisplayStyle.EXPANDED: + if self.displayStyle == AddonManagerDisplayStyle.EXPANDED: self._setup_expanded_package(repo) elif repo.macro and repo.macro.parsed: self._setup_macro(repo) else: self.widget.ui.labelDescription.setText("") self.widget.ui.labelVersion.setText("") - if self.displayStyle == ListDisplayStyle.EXPANDED: + if self.displayStyle == AddonManagerDisplayStyle.EXPANDED: self.widget.ui.labelMaintainer.setText("") # Update status - if self.displayStyle == ListDisplayStyle.EXPANDED: + if self.displayStyle == AddonManagerDisplayStyle.EXPANDED: self.widget.ui.labelStatus.setText(self.get_expanded_update_string(repo)) else: self.widget.ui.labelStatus.setText(self.get_compact_update_string(repo)) @@ -417,7 +350,7 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate): + repo.macro.date ) self.widget.ui.labelVersion.setText("" + version_string + "") - if self.displayStyle == ListDisplayStyle.EXPANDED: + if self.displayStyle == AddonManagerDisplayStyle.EXPANDED: if repo.macro.author: caption = translate("AddonsInstaller", "Author") self.widget.ui.labelMaintainer.setText(caption + ": " + repo.macro.author) @@ -665,65 +598,23 @@ class Ui_PackageList: self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout_6 = QtWidgets.QHBoxLayout() self.horizontalLayout_6.setObjectName("horizontalLayout_6") - self.buttonCompactLayout = QtWidgets.QToolButton(form) - self.buttonCompactLayout.setObjectName("buttonCompactLayout") - self.buttonCompactLayout.setCheckable(True) - self.buttonCompactLayout.setAutoExclusive(True) - self.buttonCompactLayout.setIcon( - QtGui.QIcon.fromTheme("expanded_view", QtGui.QIcon(":/icons/compact_view.svg")) - ) - self.horizontalLayout_6.addWidget(self.buttonCompactLayout) - - self.buttonExpandedLayout = QtWidgets.QToolButton(form) - self.buttonExpandedLayout.setObjectName("buttonExpandedLayout") - self.buttonExpandedLayout.setCheckable(True) - self.buttonExpandedLayout.setChecked(True) - self.buttonExpandedLayout.setAutoExclusive(True) - self.buttonExpandedLayout.setIcon( - QtGui.QIcon.fromTheme("expanded_view", QtGui.QIcon(":/icons/expanded_view.svg")) - ) - - self.horizontalLayout_6.addWidget(self.buttonExpandedLayout) + self.view_selector = WidgetViewSelector(form) + self.horizontalLayout_6.addWidget(self.view_selector) self.labelPackagesContaining = QtWidgets.QLabel(form) self.labelPackagesContaining.setObjectName("labelPackagesContaining") self.horizontalLayout_6.addWidget(self.labelPackagesContaining) - self.comboPackageType = QtWidgets.QComboBox(form) - self.comboPackageType.addItem("") - self.comboPackageType.addItem("") - self.comboPackageType.addItem("") - self.comboPackageType.addItem("") - self.comboPackageType.setObjectName("comboPackageType") + self.filter_selector = WidgetFilterSelector(form) + self.filter_selector.setObjectName("filter_selector") + self.horizontalLayout_6.addWidget(self.filter_selector) - self.horizontalLayout_6.addWidget(self.comboPackageType) + self.search_box = WidgetSearch(form) + self.search_box.setObjectName("search_box") - self.labelStatus = QtWidgets.QLabel(form) - self.labelStatus.setObjectName("labelStatus") - - self.horizontalLayout_6.addWidget(self.labelStatus) - - self.comboStatus = QtWidgets.QComboBox(form) - self.comboStatus.addItem("") - self.comboStatus.addItem("") - self.comboStatus.addItem("") - self.comboStatus.addItem("") - self.comboStatus.setObjectName("comboStatus") - - self.horizontalLayout_6.addWidget(self.comboStatus) - - self.lineEditFilter = QtWidgets.QLineEdit(form) - self.lineEditFilter.setObjectName("lineEditFilter") - self.lineEditFilter.setClearButtonEnabled(True) - - self.horizontalLayout_6.addWidget(self.lineEditFilter) - - self.labelFilterValidity = QtWidgets.QLabel(form) - self.labelFilterValidity.setObjectName("labelFilterValidity") - - self.horizontalLayout_6.addWidget(self.labelFilterValidity) + self.horizontalLayout_6.addWidget(self.search_box) self.verticalLayout.addLayout(self.horizontalLayout_6) @@ -744,44 +635,4 @@ class Ui_PackageList: QtCore.QMetaObject.connectSlotsByName(form) def retranslateUi(self, _): - self.labelPackagesContaining.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Show Addons containing:", None) - ) - self.comboPackageType.setItemText( - 0, QtCore.QCoreApplication.translate("AddonsInstaller", "All", None) - ) - self.comboPackageType.setItemText( - 1, QtCore.QCoreApplication.translate("AddonsInstaller", "Workbenches", None) - ) - self.comboPackageType.setItemText( - 2, QtCore.QCoreApplication.translate("AddonsInstaller", "Macros", None) - ) - self.comboPackageType.setItemText( - 3, - QtCore.QCoreApplication.translate("AddonsInstaller", "Preference Packs", None), - ) - self.labelStatus.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Status:", None) - ) - self.comboStatus.setItemText( - StatusFilter.ANY, - QtCore.QCoreApplication.translate("AddonsInstaller", "Any", None), - ) - self.comboStatus.setItemText( - StatusFilter.INSTALLED, - QtCore.QCoreApplication.translate("AddonsInstaller", "Installed", None), - ) - self.comboStatus.setItemText( - StatusFilter.NOT_INSTALLED, - QtCore.QCoreApplication.translate("AddonsInstaller", "Not installed", None), - ) - self.comboStatus.setItemText( - StatusFilter.UPDATE_AVAILABLE, - QtCore.QCoreApplication.translate("AddonsInstaller", "Update available", None), - ) - self.lineEditFilter.setPlaceholderText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Filter", None) - ) - self.labelFilterValidity.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "OK", None) - ) + pass From 5f2df548112942e38b90221f9b0b04fa442461dd Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 3 Feb 2024 22:28:50 +0100 Subject: [PATCH 02/25] Addon Manager: Refactor overall top bar --- src/Mod/AddonManager/AddonManager.py | 4 +- src/Mod/AddonManager/Widgets/CMakeLists.txt | 2 +- .../Widgets/addonmanager_widget_top_bar.py | 159 ------------------ .../addonmanager_widget_view_control_bar.py | 74 ++++++++ .../AddonManager/addonmanager_utilities.py | 3 +- src/Mod/AddonManager/package_list.py | 42 ++--- 6 files changed, 91 insertions(+), 193 deletions(-) delete mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_top_bar.py create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_view_control_bar.py diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index fad624cbdb..7a2680ce67 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -481,7 +481,7 @@ class CommandAddonManager: def activate_table_widgets(self) -> None: self.packageList.setEnabled(True) - self.packageList.ui.search_box.setFocus() + self.packageList.ui.view_bar.search.setFocus() self.do_next_startup_phase() def populate_macros(self) -> None: @@ -824,7 +824,7 @@ class CommandAddonManager: self.dialog.labelStatusInfo.hide() self.dialog.progressBar.hide() self.dialog.buttonPauseUpdate.hide() - self.packageList.ui.search_box.setFocus() + self.packageList.ui.view_bar.search.setFocus() def show_progress_widgets(self) -> None: if self.dialog.progressBar.isHidden(): diff --git a/src/Mod/AddonManager/Widgets/CMakeLists.txt b/src/Mod/AddonManager/Widgets/CMakeLists.txt index 2f1f6762a5..61d8d613b1 100644 --- a/src/Mod/AddonManager/Widgets/CMakeLists.txt +++ b/src/Mod/AddonManager/Widgets/CMakeLists.txt @@ -2,7 +2,7 @@ SET(AddonManagerWidget_SRCS __init__.py addonmanager_widget_filter_selector.py addonmanager_widget_search.py - addonmanager_widget_top_bar.py + addonmanager_widget_view_control_bar.py addonmanager_widget_view_selector.py ) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_top_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_top_bar.py deleted file mode 100644 index d1c6993d7e..0000000000 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_top_bar.py +++ /dev/null @@ -1,159 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -# *************************************************************************** -# * * -# * Copyright (c) 2022-2024 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 * -# * . * -# * * -# *************************************************************************** - -""" Defines a class derived from QWidget for displaying the bar at the top of the addons list. """ - -from enum import IntEnum -from addonmanager_widget_view_selector import WidgetViewSelector - -# Get whatever version of PySide we can -try: - import PySide # Use the FreeCAD wrapper -except ImportError: - try: - import PySide6 # Outside FreeCAD, try Qt6 first - - PySide = PySide6 - except ImportError: - import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) - - PySide = PySide2 - -from PySide import QtCore, QtWidgets - -translate = FreeCAD.Qt.translate - - -class StatusFilter(IntEnum): - """Predefined filers""" - - ANY = 0 - INSTALLED = 1 - NOT_INSTALLED = 2 - UPDATE_AVAILABLE = 3 - - -# pylint: disable=too-few-public-methods - - -class WidgetTopBar(QtWidgets.QWidget): - """A widget to display the buttons at the top of the Addon manager, for changing the view, - filtering, and sorting.""" - - view_changed = QtCore.Signal(int) - filter_changed = QtCore.Signal(str) - search_changed = QtCore.Signal(str) - - def __init__(self, parent=None): - super().__init__(parent) - self.horizontal_layout = None - self.package_type_label = None - self.package_type_combobox = None - self._setup_ui() - self._setup_connections() - self.retranslateUi() - - def _setup_ui(self): - self.horizontal_layout = QtWidgets.QHBoxLayout() - - self.view_selector = WidgetViewSelector(self) - self.horizontal_layout.addWidget(self.view_selector) - - self.package_type_label = QtWidgets.QLabel(self) - self.horizontal_layout.addWidget(self.package_type_label) - - self.package_type_combobox = QtWidgets.QComboBox(self) - self.horizontal_layout.addWidget(self.package_type_combobox) - - self.status_combo_box = QtWidgets.QComboBox(self) - self.horizontal_layout.addWidget(self.status_combo_box) - - self.filter_line_edit = QtWidgets.QLineEdit(self) - self.horizontal_layout.addWidget(self.filter_line_edit) - - # Only shows when the user types in a filter - self.ui.filter_validity_label = QtWidgets.QLabel(self) - self.horizontal_layout.addWidget(self.filter_validity_label) - self.ui.filter_validity_label.hide() - - def _setup_connections(self): - self.ui.view_selector.view_changed.connect(self.view_changed.emit) - - # Set up the view the same as the last time: - pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") - package_type = pref.GetInt("PackageTypeSelection", 1) - self.ui.comboPackageType.setCurrentIndex(package_type) - status = pref.GetInt("StatusSelection", 0) - self.ui.comboStatus.setCurrentIndex(status) - - def update_type_filter(self, type_filter: int) -> None: - """hide/show rows corresponding to the type filter - - type_filter is an integer: 0 for all, 1 for workbenches, 2 for macros, - and 3 for preference packs - - """ - - self.item_filter.setPackageFilter(type_filter) - pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") - pref.SetInt("PackageTypeSelection", type_filter) - - def update_status_filter(self, status_filter: int) -> None: - """hide/show rows corresponding to the status filter - - status_filter is an integer: 0 for any, 1 for installed, 2 for not installed, - and 3 for update available - - """ - - self.item_filter.setStatusFilter(status_filter) - pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") - pref.SetInt("StatusSelection", status_filter) - - def update_text_filter(self, text_filter: str) -> None: - """filter name and description by the regex specified by text_filter""" - - if text_filter: - if hasattr(self.item_filter, "setFilterRegularExpression"): # Added in Qt 5.12 - test_regex = QtCore.QRegularExpression(text_filter) - else: - test_regex = QtCore.QRegExp(text_filter) - if test_regex.isValid(): - self.ui.labelFilterValidity.setToolTip( - translate("AddonsInstaller", "Filter is valid") - ) - icon = QtGui.QIcon.fromTheme("ok", QtGui.QIcon(":/icons/edit_OK.svg")) - self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16)) - else: - self.ui.labelFilterValidity.setToolTip( - translate("AddonsInstaller", "Filter regular expression is invalid") - ) - icon = QtGui.QIcon.fromTheme("cancel", QtGui.QIcon(":/icons/edit_Cancel.svg")) - self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16)) - self.ui.labelFilterValidity.show() - else: - self.ui.labelFilterValidity.hide() - if hasattr(self.item_filter, "setFilterRegularExpression"): # Added in Qt 5.12 - self.item_filter.setFilterRegularExpression(text_filter) - else: - self.item_filter.setFilterRegExp(text_filter) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_view_control_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_control_bar.py new file mode 100644 index 0000000000..286ceac589 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_control_bar.py @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a class derived from QWidget for displaying the bar at the top of the addons list. """ + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtWidgets +from .addonmanager_widget_view_selector import WidgetViewSelector +from .addonmanager_widget_filter_selector import WidgetFilterSelector +from .addonmanager_widget_search import WidgetSearch + + +class WidgetViewControlBar(QtWidgets.QWidget): + """A bar containing a view selection widget, a filter widget, and a search widget""" + + view_changed = QtCore.Signal(int) + filter_changed = QtCore.Signal(object) + search_changed = QtCore.Signal(str) + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self._setup_ui() + self._setup_connections() + self.retranslateUi(None) + + def _setup_ui(self): + self.horizontal_layout = QtWidgets.QHBoxLayout() + self.view_selector = WidgetViewSelector(self) + self.filter_selector = WidgetFilterSelector(self) + self.search = WidgetSearch(self) + self.horizontal_layout.addWidget(self.view_selector) + self.horizontal_layout.addWidget(self.filter_selector) + self.horizontal_layout.addWidget(self.search) + self.setLayout(self.horizontal_layout) + + def _setup_connections(self): + self.view_selector.view_changed.connect(self.view_changed.emit) + self.filter_selector.filter_changed.connect(self.filter_changed.emit) + self.search.search_changed.connect(self.search_changed.emit) + + def retranslateUi(self, _=None): + pass diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index ac91794424..41d6a81229 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -40,6 +40,7 @@ try: except ImportError: QtCore = None QtWidgets = None + QtGui = None import addonmanager_freecad_interface as fci @@ -95,7 +96,7 @@ def symlink(source, link_name): raise ctypes.WinError() -def rmdir(path: os.PathLike) -> bool: +def rmdir(path: str) -> bool: try: if os.path.islink(path): os.unlink(path) # Remove symlink diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index 6279990c9c..2d8c2b0565 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -37,9 +37,9 @@ from expanded_view import Ui_ExpandedView import addonmanager_utilities as utils from addonmanager_metadata import get_first_supported_freecad_version, Version -from Widgets.addonmanager_widget_view_selector import WidgetViewSelector, AddonManagerDisplayStyle -from Widgets.addonmanager_widget_search import WidgetSearch -from Widgets.addonmanager_widget_filter_selector import WidgetFilterSelector, StatusFilter, Filter +from Widgets.addonmanager_widget_view_control_bar import WidgetViewControlBar +from Widgets.addonmanager_widget_view_selector import AddonManagerDisplayStyle +from Widgets.addonmanager_widget_filter_selector import StatusFilter, Filter, ContentFilter translate = FreeCAD.Qt.translate @@ -64,16 +64,16 @@ class PackageList(QtWidgets.QWidget): self.ui.listPackages.setItemDelegate(self.item_delegate) self.ui.listPackages.clicked.connect(self.on_listPackages_clicked) - self.ui.filter_selector.filter_changed.connect(self.update_status_filter) - self.ui.search_box.search_changed.connect(self.item_filter.setFilterRegularExpression) - self.ui.view_selector.view_changed.connect(self.set_view_style) + self.ui.view_bar.view_changed.connect(self.set_view_style) + self.ui.view_bar.filter_changed.connect(self.update_status_filter) + self.ui.view_bar.search_changed.connect(self.item_filter.setFilterRegularExpression) # Set up the view the same as the last time: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") package_type = pref.GetInt("PackageTypeSelection", 1) status = pref.GetInt("StatusSelection", 0) - self.ui.filter_selector.set_contents_filter(package_type) - self.ui.filter_selector.set_status_filter(status) + self.ui.view_bar.filter_selector.set_contents_filter(package_type) + self.ui.view_bar.filter_selector.set_status_filter(status) # Pre-init of other members: self.item_model = None @@ -87,7 +87,7 @@ class PackageList(QtWidgets.QWidget): pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") style = pref.GetInt("ViewStyle", AddonManagerDisplayStyle.EXPANDED) self.set_view_style(style) - self.ui.view_selector.set_current_view(style) + self.ui.view_bar.view_selector.set_current_view(style) self.item_filter.setHidePy2(pref.GetBool("HidePy2", True)) self.item_filter.setHideObsolete(pref.GetBool("HideObsolete", True)) @@ -599,22 +599,9 @@ class Ui_PackageList: self.horizontalLayout_6 = QtWidgets.QHBoxLayout() self.horizontalLayout_6.setObjectName("horizontalLayout_6") - self.view_selector = WidgetViewSelector(form) - self.horizontalLayout_6.addWidget(self.view_selector) - - self.labelPackagesContaining = QtWidgets.QLabel(form) - self.labelPackagesContaining.setObjectName("labelPackagesContaining") - - self.horizontalLayout_6.addWidget(self.labelPackagesContaining) - - self.filter_selector = WidgetFilterSelector(form) - self.filter_selector.setObjectName("filter_selector") - self.horizontalLayout_6.addWidget(self.filter_selector) - - self.search_box = WidgetSearch(form) - self.search_box.setObjectName("search_box") - - self.horizontalLayout_6.addWidget(self.search_box) + self.view_bar = WidgetViewControlBar(form) + self.view_bar.setObjectName("ViewControlBar") + self.horizontalLayout_6.addWidget(self.view_bar) self.verticalLayout.addLayout(self.horizontalLayout_6) @@ -630,9 +617,4 @@ class Ui_PackageList: self.verticalLayout.addWidget(self.listPackages) - self.retranslateUi(form) - QtCore.QMetaObject.connectSlotsByName(form) - - def retranslateUi(self, _): - pass From 9e535d21a2e4e24db59a51d98a3af1f0115abff9 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 12:58:32 +0100 Subject: [PATCH 03/25] Addon Manager: Add support for license exclusion --- src/Mod/AddonManager/Addon.py | 14 + src/Mod/AddonManager/AddonManagerOptions.ui | 32 + src/Mod/AddonManager/AddonStats.py | 58 + src/Mod/AddonManager/CMakeLists.txt | 2 + .../AddonManager/Resources/AddonManager.qrc | 1 + .../Resources/icons/composite_view.svg | 8 +- .../AddonManager/Resources/licenses/spdx.json | 7835 +++++++++++++++++ .../Widgets/addonmanager_widget_search.py | 4 +- src/Mod/AddonManager/addonmanager_licenses.py | 127 + src/Mod/AddonManager/package_list.py | 38 +- 10 files changed, 8108 insertions(+), 11 deletions(-) create mode 100644 src/Mod/AddonManager/AddonStats.py create mode 100644 src/Mod/AddonManager/Resources/licenses/spdx.json create mode 100644 src/Mod/AddonManager/addonmanager_licenses.py diff --git a/src/Mod/AddonManager/Addon.py b/src/Mod/AddonManager/Addon.py index 5289f2752b..59a360b6e3 100644 --- a/src/Mod/AddonManager/Addon.py +++ b/src/Mod/AddonManager/Addon.py @@ -41,11 +41,14 @@ from addonmanager_metadata import ( Version, DependencyType, ) +from AddonStats import AddonStats translate = fci.translate +# A list of internal workbenches that can be used as a dependency of an Addon INTERNAL_WORKBENCHES = { "arch": "Arch", + "assembly": "Assembly", "draft": "Draft", "fem": "FEM", "mesh": "Mesh", @@ -163,6 +166,7 @@ class Addon: self.description = None self.tags = set() # Just a cache, loaded from Metadata self.last_updated = None + self.stats = AddonStats() # To prevent multiple threads from running git actions on this repo at the # same time @@ -205,6 +209,7 @@ class Addon: self.python_min_version = {"major": 3, "minor": 0} self._icon_file = None + self._cached_license: str = "" def __str__(self) -> str: result = f"FreeCAD {self.repo_type}\n" @@ -215,6 +220,15 @@ class Addon: result += "Has linked Macro object\n" return result + @property + def license(self): + if not self._cached_license: + if self.metadata and self.metadata.license: + self._cached_license = self.metadata.license + elif self.stats and self.stats.license: + self._cached_license = self.stats.license + return self._cached_license + @classmethod def from_macro(cls, macro: Macro): """Create an Addon object from a Macro wrapper object""" diff --git a/src/Mod/AddonManager/AddonManagerOptions.ui b/src/Mod/AddonManager/AddonManagerOptions.ui index c935646f72..3bd3b7e591 100644 --- a/src/Mod/AddonManager/AddonManagerOptions.ui +++ b/src/Mod/AddonManager/AddonManagerOptions.ui @@ -90,6 +90,38 @@ installed addons will be checked for available updates + + + + Hide Addons with non-FSF Free/Libre license + + + true + + + HideNonFSFFreeLibre + + + Addons + + + + + + + Hide Addons with non-OSI-approved license + + + true + + + HideNonOSIApproved + + + Addons + + + diff --git a/src/Mod/AddonManager/AddonStats.py b/src/Mod/AddonManager/AddonStats.py new file mode 100644 index 0000000000..60eee8ae5f --- /dev/null +++ b/src/Mod/AddonManager/AddonStats.py @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Classes and structures related to Addon sidecar information """ +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime + + +def to_int_or_zero(inp: [str | int | None]): + try: + return int(inp) + except TypeError: + return 0 + + +@dataclass +class AddonStats: + """Statistics about an addon: not all stats apply to all addon types""" + + last_update_time: datetime | None = None + stars: int = 0 + open_issues: int = 0 + forks: int = 0 + license: str = "" + page_views_last_month: int = 0 + + @classmethod + def from_json(cls, json_dict: dict): + new_stats = AddonStats() + if "pushed_at" in json_dict: + new_stats.last_update_time = datetime.fromisoformat(json_dict["pushed_at"]) + new_stats.stars = to_int_or_zero(json_dict["stargazers_count"]) + new_stats.forks = to_int_or_zero(json_dict["forks_count"]) + new_stats.open_issues = to_int_or_zero(json_dict["open_issues_count"]) + new_stats.license = json_dict["license"] # Might be None or "NOASSERTION" + return new_stats diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index 1948b69d14..8877a544be 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -6,6 +6,7 @@ ENDIF (BUILD_GUI) SET(AddonManager_SRCS add_toolbar_button_dialog.ui Addon.py + AddonStats.py AddonManager.py AddonManager.ui addonmanager_cache.py @@ -25,6 +26,7 @@ SET(AddonManager_SRCS addonmanager_git.py addonmanager_installer.py addonmanager_installer_gui.py + addonmanager_licenses.py addonmanager_macro.py addonmanager_macro_parser.py addonmanager_metadata.py diff --git a/src/Mod/AddonManager/Resources/AddonManager.qrc b/src/Mod/AddonManager/Resources/AddonManager.qrc index e9b5a90e90..a1f12a12d8 100644 --- a/src/Mod/AddonManager/Resources/AddonManager.qrc +++ b/src/Mod/AddonManager/Resources/AddonManager.qrc @@ -76,6 +76,7 @@ licenses/LGPLv3.txt licenses/MIT.txt licenses/MPL-2.0.txt + licenses/spdx.json translations/AddonManager_af.qm translations/AddonManager_ar.qm translations/AddonManager_ca.qm diff --git a/src/Mod/AddonManager/Resources/icons/composite_view.svg b/src/Mod/AddonManager/Resources/icons/composite_view.svg index 7f1bf475be..4c3e7f773b 100644 --- a/src/Mod/AddonManager/Resources/icons/composite_view.svg +++ b/src/Mod/AddonManager/Resources/icons/composite_view.svg @@ -6,8 +6,10 @@ .st0{fill:none;stroke:#000000;stroke-miterlimit:10;} .st1{fill:none;stroke:#000000;stroke-width:0.9259;stroke-miterlimit:10;} - - + + + + - + diff --git a/src/Mod/AddonManager/Resources/licenses/spdx.json b/src/Mod/AddonManager/Resources/licenses/spdx.json new file mode 100644 index 0000000000..a66972e749 --- /dev/null +++ b/src/Mod/AddonManager/Resources/licenses/spdx.json @@ -0,0 +1,7835 @@ +{ + "licenseListVersion": "83c9f84", + "licenses": [ + { + "reference": "https://spdx.org/licenses/0BSD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/0BSD.json", + "referenceNumber": 530, + "name": "BSD Zero Clause License", + "licenseId": "0BSD", + "seeAlso": [ + "http://landley.net/toybox/license.html", + "https://opensource.org/licenses/0BSD" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/AAL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AAL.json", + "referenceNumber": 121, + "name": "Attribution Assurance License", + "licenseId": "AAL", + "seeAlso": [ + "https://opensource.org/licenses/attribution" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Abstyles.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Abstyles.json", + "referenceNumber": 499, + "name": "Abstyles License", + "licenseId": "Abstyles", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Abstyles" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AdaCore-doc.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AdaCore-doc.json", + "referenceNumber": 103, + "name": "AdaCore Doc License", + "licenseId": "AdaCore-doc", + "seeAlso": [ + "https://github.com/AdaCore/xmlada/blob/master/docs/index.rst", + "https://github.com/AdaCore/gnatcoll-core/blob/master/docs/index.rst", + "https://github.com/AdaCore/gnatcoll-db/blob/master/docs/index.rst" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Adobe-2006.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Adobe-2006.json", + "referenceNumber": 471, + "name": "Adobe Systems Incorporated Source Code License Agreement", + "licenseId": "Adobe-2006", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AdobeLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Adobe-Display-PostScript.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Adobe-Display-PostScript.json", + "referenceNumber": 556, + "name": "Adobe Display PostScript License", + "licenseId": "Adobe-Display-PostScript", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/COPYING?ref_type\u003dheads#L752" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Adobe-Glyph.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Adobe-Glyph.json", + "referenceNumber": 585, + "name": "Adobe Glyph List License", + "licenseId": "Adobe-Glyph", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#AdobeGlyph" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Adobe-Utopia.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Adobe-Utopia.json", + "referenceNumber": 89, + "name": "Adobe Utopia Font License", + "licenseId": "Adobe-Utopia", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/font/adobe-utopia-100dpi/-/blob/master/COPYING?ref_type\u003dheads" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ADSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ADSL.json", + "referenceNumber": 535, + "name": "Amazon Digital Services License", + "licenseId": "ADSL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AmazonDigitalServicesLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AFL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-1.1.json", + "referenceNumber": 174, + "name": "Academic Free License v1.1", + "licenseId": "AFL-1.1", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-1.1.txt", + "http://wayback.archive.org/web/20021004124254/http://www.opensource.org/licenses/academic.php" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AFL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-1.2.json", + "referenceNumber": 606, + "name": "Academic Free License v1.2", + "licenseId": "AFL-1.2", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-1.2.txt", + "http://wayback.archive.org/web/20021204204652/http://www.opensource.org/licenses/academic.php" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AFL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-2.0.json", + "referenceNumber": 219, + "name": "Academic Free License v2.0", + "licenseId": "AFL-2.0", + "seeAlso": [ + "http://wayback.archive.org/web/20060924134533/http://www.opensource.org/licenses/afl-2.0.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AFL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-2.1.json", + "referenceNumber": 426, + "name": "Academic Free License v2.1", + "licenseId": "AFL-2.1", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-2.1.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AFL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-3.0.json", + "referenceNumber": 37, + "name": "Academic Free License v3.0", + "licenseId": "AFL-3.0", + "seeAlso": [ + "http://www.rosenlaw.com/AFL3.0.htm", + "https://opensource.org/licenses/afl-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Afmparse.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Afmparse.json", + "referenceNumber": 227, + "name": "Afmparse License", + "licenseId": "Afmparse", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Afmparse" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0.json", + "referenceNumber": 327, + "name": "Affero General Public License v1.0", + "licenseId": "AGPL-1.0", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0-only.json", + "referenceNumber": 75, + "name": "Affero General Public License v1.0 only", + "licenseId": "AGPL-1.0-only", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0-or-later.json", + "referenceNumber": 421, + "name": "Affero General Public License v1.0 or later", + "licenseId": "AGPL-1.0-or-later", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0.json", + "referenceNumber": 500, + "name": "GNU Affero General Public License v3.0", + "licenseId": "AGPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0-only.json", + "referenceNumber": 291, + "name": "GNU Affero General Public License v3.0 only", + "licenseId": "AGPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0-or-later.json", + "referenceNumber": 398, + "name": "GNU Affero General Public License v3.0 or later", + "licenseId": "AGPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Aladdin.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Aladdin.json", + "referenceNumber": 441, + "name": "Aladdin Free Public License", + "licenseId": "Aladdin", + "seeAlso": [ + "http://pages.cs.wisc.edu/~ghost/doc/AFPL/6.01/Public.htm" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/AMDPLPA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AMDPLPA.json", + "referenceNumber": 423, + "name": "AMD\u0027s plpa_map.c License", + "licenseId": "AMDPLPA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AMD_plpa_map_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AML.json", + "referenceNumber": 17, + "name": "Apple MIT License", + "licenseId": "AML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Apple_MIT_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AML-glslang.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AML-glslang.json", + "referenceNumber": 254, + "name": "AML glslang variant License", + "licenseId": "AML-glslang", + "seeAlso": [ + "https://github.com/KhronosGroup/glslang/blob/main/LICENSE.txt#L949", + "https://docs.omniverse.nvidia.com/install-guide/latest/common/licenses.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AMPAS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AMPAS.json", + "referenceNumber": 487, + "name": "Academy of Motion Picture Arts and Sciences BSD", + "licenseId": "AMPAS", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD#AMPASBSD" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ANTLR-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ANTLR-PD.json", + "referenceNumber": 129, + "name": "ANTLR Software Rights Notice", + "licenseId": "ANTLR-PD", + "seeAlso": [ + "http://www.antlr2.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ANTLR-PD-fallback.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ANTLR-PD-fallback.json", + "referenceNumber": 171, + "name": "ANTLR Software Rights Notice with license fallback", + "licenseId": "ANTLR-PD-fallback", + "seeAlso": [ + "http://www.antlr2.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Apache-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-1.0.json", + "referenceNumber": 97, + "name": "Apache License 1.0", + "licenseId": "Apache-1.0", + "seeAlso": [ + "http://www.apache.org/licenses/LICENSE-1.0" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Apache-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-1.1.json", + "referenceNumber": 442, + "name": "Apache License 1.1", + "licenseId": "Apache-1.1", + "seeAlso": [ + "http://apache.org/licenses/LICENSE-1.1", + "https://opensource.org/licenses/Apache-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Apache-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-2.0.json", + "referenceNumber": 300, + "name": "Apache License 2.0", + "licenseId": "Apache-2.0", + "seeAlso": [ + "https://www.apache.org/licenses/LICENSE-2.0", + "https://opensource.org/licenses/Apache-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/APAFML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APAFML.json", + "referenceNumber": 513, + "name": "Adobe Postscript AFM License", + "licenseId": "APAFML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AdobePostscriptAFM" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APL-1.0.json", + "referenceNumber": 605, + "name": "Adaptive Public License 1.0", + "licenseId": "APL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/APL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/App-s2p.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/App-s2p.json", + "referenceNumber": 336, + "name": "App::s2p License", + "licenseId": "App-s2p", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/App-s2p" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.0.json", + "referenceNumber": 164, + "name": "Apple Public Source License 1.0", + "licenseId": "APSL-1.0", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Apple_Public_Source_License_1.0" + ], + "isOsiApproved": true, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/APSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.1.json", + "referenceNumber": 154, + "name": "Apple Public Source License 1.1", + "licenseId": "APSL-1.1", + "seeAlso": [ + "http://www.opensource.apple.com/source/IOSerialFamily/IOSerialFamily-7/APPLE_LICENSE" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/APSL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.2.json", + "referenceNumber": 163, + "name": "Apple Public Source License 1.2", + "licenseId": "APSL-1.2", + "seeAlso": [ + "http://www.samurajdata.se/opensource/mirror/licenses/apsl.php" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/APSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-2.0.json", + "referenceNumber": 59, + "name": "Apple Public Source License 2.0", + "licenseId": "APSL-2.0", + "seeAlso": [ + "http://www.opensource.apple.com/license/apsl/" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Arphic-1999.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Arphic-1999.json", + "referenceNumber": 446, + "name": "Arphic Public License", + "licenseId": "Arphic-1999", + "seeAlso": [ + "http://ftp.gnu.org/gnu/non-gnu/chinese-fonts-truetype/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0.json", + "referenceNumber": 108, + "name": "Artistic License 1.0", + "licenseId": "Artistic-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Artistic-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0-cl8.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0-cl8.json", + "referenceNumber": 405, + "name": "Artistic License 1.0 w/clause 8", + "licenseId": "Artistic-1.0-cl8", + "seeAlso": [ + "https://opensource.org/licenses/Artistic-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0-Perl.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0-Perl.json", + "referenceNumber": 558, + "name": "Artistic License 1.0 (Perl)", + "licenseId": "Artistic-1.0-Perl", + "seeAlso": [ + "http://dev.perl.org/licenses/artistic.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Artistic-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-2.0.json", + "referenceNumber": 152, + "name": "Artistic License 2.0", + "licenseId": "Artistic-2.0", + "seeAlso": [ + "http://www.perlfoundation.org/artistic_license_2_0", + "https://www.perlfoundation.org/artistic-license-20.html", + "https://opensource.org/licenses/artistic-license-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ASWF-Digital-Assets-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ASWF-Digital-Assets-1.0.json", + "referenceNumber": 217, + "name": "ASWF Digital Assets License version 1.0", + "licenseId": "ASWF-Digital-Assets-1.0", + "seeAlso": [ + "https://github.com/AcademySoftwareFoundation/foundation/blob/main/digital_assets/aswf_digital_assets_license_v1.0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ASWF-Digital-Assets-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ASWF-Digital-Assets-1.1.json", + "referenceNumber": 145, + "name": "ASWF Digital Assets License 1.1", + "licenseId": "ASWF-Digital-Assets-1.1", + "seeAlso": [ + "https://github.com/AcademySoftwareFoundation/foundation/blob/main/digital_assets/aswf_digital_assets_license_v1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Baekmuk.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Baekmuk.json", + "referenceNumber": 524, + "name": "Baekmuk License", + "licenseId": "Baekmuk", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:Baekmuk?rd\u003dLicensing/Baekmuk" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Bahyph.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Bahyph.json", + "referenceNumber": 247, + "name": "Bahyph License", + "licenseId": "Bahyph", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Bahyph" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Barr.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Barr.json", + "referenceNumber": 465, + "name": "Barr License", + "licenseId": "Barr", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Barr" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Beerware.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Beerware.json", + "referenceNumber": 432, + "name": "Beerware License", + "licenseId": "Beerware", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Beerware", + "https://people.freebsd.org/~phk/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Bitstream-Charter.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Bitstream-Charter.json", + "referenceNumber": 369, + "name": "Bitstream Charter Font License", + "licenseId": "Bitstream-Charter", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Charter#License_Text", + "https://raw.githubusercontent.com/blackhole89/notekit/master/data/fonts/Charter%20license.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Bitstream-Vera.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Bitstream-Vera.json", + "referenceNumber": 245, + "name": "Bitstream Vera Font License", + "licenseId": "Bitstream-Vera", + "seeAlso": [ + "https://web.archive.org/web/20080207013128/http://www.gnome.org/fonts/", + "https://docubrain.com/sites/default/files/licenses/bitstream-vera.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BitTorrent-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BitTorrent-1.0.json", + "referenceNumber": 425, + "name": "BitTorrent Open Source License v1.0", + "licenseId": "BitTorrent-1.0", + "seeAlso": [ + "http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/licenses/BitTorrent?r1\u003d1.1\u0026r2\u003d1.1.1.1\u0026diff_format\u003ds" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BitTorrent-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BitTorrent-1.1.json", + "referenceNumber": 315, + "name": "BitTorrent Open Source License v1.1", + "licenseId": "BitTorrent-1.1", + "seeAlso": [ + "http://directory.fsf.org/wiki/License:BitTorrentOSL1.1" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/blessing.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/blessing.json", + "referenceNumber": 545, + "name": "SQLite Blessing", + "licenseId": "blessing", + "seeAlso": [ + "https://www.sqlite.org/src/artifact/e33a4df7e32d742a?ln\u003d4-9", + "https://sqlite.org/src/artifact/df5091916dbb40e6" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BlueOak-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BlueOak-1.0.0.json", + "referenceNumber": 274, + "name": "Blue Oak Model License 1.0.0", + "licenseId": "BlueOak-1.0.0", + "seeAlso": [ + "https://blueoakcouncil.org/license/1.0.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Boehm-GC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Boehm-GC.json", + "referenceNumber": 458, + "name": "Boehm-Demers-Weiser GC License", + "licenseId": "Boehm-GC", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:MIT#Another_Minimal_variant_(found_in_libatomic_ops)", + "https://github.com/uim/libgcroots/blob/master/COPYING", + "https://github.com/ivmai/libatomic_ops/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Borceux.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Borceux.json", + "referenceNumber": 18, + "name": "Borceux license", + "licenseId": "Borceux", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Borceux" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Brian-Gladman-3-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Brian-Gladman-3-Clause.json", + "referenceNumber": 511, + "name": "Brian Gladman 3-Clause License", + "licenseId": "Brian-Gladman-3-Clause", + "seeAlso": [ + "https://github.com/SWI-Prolog/packages-clib/blob/master/sha1/brg_endian.h" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-1-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-1-Clause.json", + "referenceNumber": 564, + "name": "BSD 1-Clause License", + "licenseId": "BSD-1-Clause", + "seeAlso": [ + "https://svnweb.freebsd.org/base/head/include/ifaddrs.h?revision\u003d326823" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause.json", + "referenceNumber": 13, + "name": "BSD 2-Clause \"Simplified\" License", + "licenseId": "BSD-2-Clause", + "seeAlso": [ + "https://opensource.org/licenses/BSD-2-Clause" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-FreeBSD.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-FreeBSD.json", + "referenceNumber": 262, + "name": "BSD 2-Clause FreeBSD License", + "licenseId": "BSD-2-Clause-FreeBSD", + "seeAlso": [ + "http://www.freebsd.org/copyright/freebsd-license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-NetBSD.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-NetBSD.json", + "referenceNumber": 476, + "name": "BSD 2-Clause NetBSD License", + "licenseId": "BSD-2-Clause-NetBSD", + "seeAlso": [ + "http://www.netbsd.org/about/redistribution.html#default" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-Patent.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-Patent.json", + "referenceNumber": 363, + "name": "BSD-2-Clause Plus Patent License", + "licenseId": "BSD-2-Clause-Patent", + "seeAlso": [ + "https://opensource.org/licenses/BSDplusPatent" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-Views.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-Views.json", + "referenceNumber": 82, + "name": "BSD 2-Clause with views sentence", + "licenseId": "BSD-2-Clause-Views", + "seeAlso": [ + "http://www.freebsd.org/copyright/freebsd-license.html", + "https://people.freebsd.org/~ivoras/wine/patch-wine-nvidia.sh", + "https://github.com/protegeproject/protege/blob/master/license.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause.json", + "referenceNumber": 355, + "name": "BSD 3-Clause \"New\" or \"Revised\" License", + "licenseId": "BSD-3-Clause", + "seeAlso": [ + "https://opensource.org/licenses/BSD-3-Clause", + "https://www.eclipse.org/org/documents/edl-v10.php" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-acpica.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-acpica.json", + "referenceNumber": 506, + "name": "BSD 3-Clause acpica variant", + "licenseId": "BSD-3-Clause-acpica", + "seeAlso": [ + "https://github.com/acpica/acpica/blob/master/source/common/acfileio.c#L119" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Attribution.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Attribution.json", + "referenceNumber": 387, + "name": "BSD with attribution", + "licenseId": "BSD-3-Clause-Attribution", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD_with_Attribution" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Clear.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Clear.json", + "referenceNumber": 102, + "name": "BSD 3-Clause Clear License", + "licenseId": "BSD-3-Clause-Clear", + "seeAlso": [ + "http://labs.metacarta.com/license-explanation.html#license" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-flex.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-flex.json", + "referenceNumber": 188, + "name": "BSD 3-Clause Flex variant", + "licenseId": "BSD-3-Clause-flex", + "seeAlso": [ + "https://github.com/westes/flex/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-HP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-HP.json", + "referenceNumber": 609, + "name": "Hewlett-Packard BSD variant license", + "licenseId": "BSD-3-Clause-HP", + "seeAlso": [ + "https://github.com/zdohnal/hplip/blob/master/COPYING#L939" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-LBNL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-LBNL.json", + "referenceNumber": 303, + "name": "Lawrence Berkeley National Labs BSD variant license", + "licenseId": "BSD-3-Clause-LBNL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/LBNLBSD" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Modification.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Modification.json", + "referenceNumber": 475, + "name": "BSD 3-Clause Modification", + "licenseId": "BSD-3-Clause-Modification", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:BSD#Modification_Variant" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Military-License.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Military-License.json", + "referenceNumber": 308, + "name": "BSD 3-Clause No Military License", + "licenseId": "BSD-3-Clause-No-Military-License", + "seeAlso": [ + "https://gitlab.syncad.com/hive/dhive/-/blob/master/LICENSE", + "https://github.com/greymass/swift-eosio/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License.json", + "referenceNumber": 541, + "name": "BSD 3-Clause No Nuclear License", + "licenseId": "BSD-3-Clause-No-Nuclear-License", + "seeAlso": [ + "http://download.oracle.com/otn-pub/java/licenses/bsd.txt?AuthParam\u003d1467140197_43d516ce1776bd08a58235a7785be1cc" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License-2014.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License-2014.json", + "referenceNumber": 486, + "name": "BSD 3-Clause No Nuclear License 2014", + "licenseId": "BSD-3-Clause-No-Nuclear-License-2014", + "seeAlso": [ + "https://java.net/projects/javaeetutorial/pages/BerkeleyLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-Warranty.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-Warranty.json", + "referenceNumber": 559, + "name": "BSD 3-Clause No Nuclear Warranty", + "licenseId": "BSD-3-Clause-No-Nuclear-Warranty", + "seeAlso": [ + "https://jogamp.org/git/?p\u003dgluegen.git;a\u003dblob_plain;f\u003dLICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Open-MPI.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Open-MPI.json", + "referenceNumber": 46, + "name": "BSD 3-Clause Open MPI variant", + "licenseId": "BSD-3-Clause-Open-MPI", + "seeAlso": [ + "https://www.open-mpi.org/community/license.php", + "http://www.netlib.org/lapack/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Sun.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Sun.json", + "referenceNumber": 228, + "name": "BSD 3-Clause Sun Microsystems", + "licenseId": "BSD-3-Clause-Sun", + "seeAlso": [ + "https://github.com/xmlark/msv/blob/b9316e2f2270bc1606952ea4939ec87fbba157f3/xsdlib/src/main/java/com/sun/msv/datatype/regexp/InternalImpl.java" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause.json", + "referenceNumber": 619, + "name": "BSD 4-Clause \"Original\" or \"Old\" License", + "licenseId": "BSD-4-Clause", + "seeAlso": [ + "http://directory.fsf.org/wiki/License:BSD_4Clause" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause-Shortened.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause-Shortened.json", + "referenceNumber": 194, + "name": "BSD 4 Clause Shortened", + "licenseId": "BSD-4-Clause-Shortened", + "seeAlso": [ + "https://metadata.ftp-master.debian.org/changelogs//main/a/arpwatch/arpwatch_2.1a15-7_copyright" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause-UC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause-UC.json", + "referenceNumber": 332, + "name": "BSD-4-Clause (University of California-Specific)", + "licenseId": "BSD-4-Clause-UC", + "seeAlso": [ + "http://www.freebsd.org/copyright/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-4.3RENO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4.3RENO.json", + "referenceNumber": 85, + "name": "BSD 4.3 RENO License", + "licenseId": "BSD-4.3RENO", + "seeAlso": [ + "https://sourceware.org/git/?p\u003dbinutils-gdb.git;a\u003dblob;f\u003dlibiberty/strcasecmp.c;h\u003d131d81c2ce7881fa48c363dc5bf5fb302c61ce0b;hb\u003dHEAD", + "https://git.openldap.org/openldap/openldap/-/blob/master/COPYRIGHT#L55-63" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-4.3TAHOE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4.3TAHOE.json", + "referenceNumber": 354, + "name": "BSD 4.3 TAHOE License", + "licenseId": "BSD-4.3TAHOE", + "seeAlso": [ + "https://github.com/389ds/389-ds-base/blob/main/ldap/include/sysexits-compat.h#L15", + "https://git.savannah.gnu.org/cgit/indent.git/tree/doc/indent.texi?id\u003da74c6b4ee49397cf330b333da1042bffa60ed14f#n1788" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Advertising-Acknowledgement.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Advertising-Acknowledgement.json", + "referenceNumber": 200, + "name": "BSD Advertising Acknowledgement License", + "licenseId": "BSD-Advertising-Acknowledgement", + "seeAlso": [ + "https://github.com/python-excel/xlrd/blob/master/LICENSE#L33" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Attribution-HPND-disclaimer.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Attribution-HPND-disclaimer.json", + "referenceNumber": 427, + "name": "BSD with Attribution and HPND disclaimer", + "licenseId": "BSD-Attribution-HPND-disclaimer", + "seeAlso": [ + "https://github.com/cyrusimap/cyrus-sasl/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Inferno-Nettverk.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Inferno-Nettverk.json", + "referenceNumber": 151, + "name": "BSD-Inferno-Nettverk", + "licenseId": "BSD-Inferno-Nettverk", + "seeAlso": [ + "https://www.inet.no/dante/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Protection.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Protection.json", + "referenceNumber": 592, + "name": "BSD Protection License", + "licenseId": "BSD-Protection", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD_Protection_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Source-beginning-file.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Source-beginning-file.json", + "referenceNumber": 137, + "name": "BSD Source Code Attribution - beginning of file variant", + "licenseId": "BSD-Source-beginning-file", + "seeAlso": [ + "https://github.com/lattera/freebsd/blob/master/sys/cam/cam.c#L4" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Source-Code.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Source-Code.json", + "referenceNumber": 433, + "name": "BSD Source Code Attribution", + "licenseId": "BSD-Source-Code", + "seeAlso": [ + "https://github.com/robbiehanson/CocoaHTTPServer/blob/master/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Systemics.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Systemics.json", + "referenceNumber": 325, + "name": "Systemics BSD variant license", + "licenseId": "BSD-Systemics", + "seeAlso": [ + "https://metacpan.org/release/DPARIS/Crypt-DES-2.07/source/COPYRIGHT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Systemics-W3Works.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Systemics-W3Works.json", + "referenceNumber": 340, + "name": "Systemics W3Works BSD variant license", + "licenseId": "BSD-Systemics-W3Works", + "seeAlso": [ + "https://metacpan.org/release/DPARIS/Crypt-Blowfish-2.14/source/COPYRIGHT#L7" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSL-1.0.json", + "referenceNumber": 73, + "name": "Boost Software License 1.0", + "licenseId": "BSL-1.0", + "seeAlso": [ + "http://www.boost.org/LICENSE_1_0.txt", + "https://opensource.org/licenses/BSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BUSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BUSL-1.1.json", + "referenceNumber": 525, + "name": "Business Source License 1.1", + "licenseId": "BUSL-1.1", + "seeAlso": [ + "https://mariadb.com/bsl11/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/bzip2-1.0.5.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/bzip2-1.0.5.json", + "referenceNumber": 438, + "name": "bzip2 and libbzip2 License v1.0.5", + "licenseId": "bzip2-1.0.5", + "seeAlso": [ + "https://sourceware.org/bzip2/1.0.5/bzip2-manual-1.0.5.html", + "http://bzip.org/1.0.5/bzip2-manual-1.0.5.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/bzip2-1.0.6.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/bzip2-1.0.6.json", + "referenceNumber": 252, + "name": "bzip2 and libbzip2 License v1.0.6", + "licenseId": "bzip2-1.0.6", + "seeAlso": [ + "https://sourceware.org/git/?p\u003dbzip2.git;a\u003dblob;f\u003dLICENSE;hb\u003dbzip2-1.0.6", + "http://bzip.org/1.0.5/bzip2-manual-1.0.5.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/C-UDA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/C-UDA-1.0.json", + "referenceNumber": 472, + "name": "Computational Use of Data Agreement v1.0", + "licenseId": "C-UDA-1.0", + "seeAlso": [ + "https://github.com/microsoft/Computational-Use-of-Data-Agreement/blob/master/C-UDA-1.0.md", + "https://cdla.dev/computational-use-of-data-agreement-v1-0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CAL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CAL-1.0.json", + "referenceNumber": 618, + "name": "Cryptographic Autonomy License 1.0", + "licenseId": "CAL-1.0", + "seeAlso": [ + "http://cryptographicautonomylicense.com/license-text.html", + "https://opensource.org/licenses/CAL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CAL-1.0-Combined-Work-Exception.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CAL-1.0-Combined-Work-Exception.json", + "referenceNumber": 384, + "name": "Cryptographic Autonomy License 1.0 (Combined Work Exception)", + "licenseId": "CAL-1.0-Combined-Work-Exception", + "seeAlso": [ + "http://cryptographicautonomylicense.com/license-text.html", + "https://opensource.org/licenses/CAL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Caldera.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Caldera.json", + "referenceNumber": 319, + "name": "Caldera License", + "licenseId": "Caldera", + "seeAlso": [ + "http://www.lemis.com/grog/UNIX/ancient-source-all.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Caldera-no-preamble.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Caldera-no-preamble.json", + "referenceNumber": 51, + "name": "Caldera License (without preamble)", + "licenseId": "Caldera-no-preamble", + "seeAlso": [ + "https://github.com/apache/apr/blob/trunk/LICENSE#L298C6-L298C29" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CATOSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CATOSL-1.1.json", + "referenceNumber": 437, + "name": "Computer Associates Trusted Open Source License 1.1", + "licenseId": "CATOSL-1.1", + "seeAlso": [ + "https://opensource.org/licenses/CATOSL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-1.0.json", + "referenceNumber": 226, + "name": "Creative Commons Attribution 1.0 Generic", + "licenseId": "CC-BY-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-2.0.json", + "referenceNumber": 259, + "name": "Creative Commons Attribution 2.0 Generic", + "licenseId": "CC-BY-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-2.5.json", + "referenceNumber": 165, + "name": "Creative Commons Attribution 2.5 Generic", + "licenseId": "CC-BY-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-2.5-AU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-2.5-AU.json", + "referenceNumber": 518, + "name": "Creative Commons Attribution 2.5 Australia", + "licenseId": "CC-BY-2.5-AU", + "seeAlso": [ + "https://creativecommons.org/licenses/by/2.5/au/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0.json", + "referenceNumber": 370, + "name": "Creative Commons Attribution 3.0 Unported", + "licenseId": "CC-BY-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-AT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-AT.json", + "referenceNumber": 101, + "name": "Creative Commons Attribution 3.0 Austria", + "licenseId": "CC-BY-3.0-AT", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/at/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-AU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-AU.json", + "referenceNumber": 156, + "name": "Creative Commons Attribution 3.0 Australia", + "licenseId": "CC-BY-3.0-AU", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/au/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-DE.json", + "referenceNumber": 9, + "name": "Creative Commons Attribution 3.0 Germany", + "licenseId": "CC-BY-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-IGO.json", + "referenceNumber": 211, + "name": "Creative Commons Attribution 3.0 IGO", + "licenseId": "CC-BY-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-NL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-NL.json", + "referenceNumber": 445, + "name": "Creative Commons Attribution 3.0 Netherlands", + "licenseId": "CC-BY-3.0-NL", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/nl/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-US.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-US.json", + "referenceNumber": 529, + "name": "Creative Commons Attribution 3.0 United States", + "licenseId": "CC-BY-3.0-US", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/us/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-4.0.json", + "referenceNumber": 40, + "name": "Creative Commons Attribution 4.0 International", + "licenseId": "CC-BY-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-1.0.json", + "referenceNumber": 94, + "name": "Creative Commons Attribution Non Commercial 1.0 Generic", + "licenseId": "CC-BY-NC-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/1.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-2.0.json", + "referenceNumber": 246, + "name": "Creative Commons Attribution Non Commercial 2.0 Generic", + "licenseId": "CC-BY-NC-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/2.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-2.5.json", + "referenceNumber": 498, + "name": "Creative Commons Attribution Non Commercial 2.5 Generic", + "licenseId": "CC-BY-NC-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/2.5/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-3.0.json", + "referenceNumber": 469, + "name": "Creative Commons Attribution Non Commercial 3.0 Unported", + "licenseId": "CC-BY-NC-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/3.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-3.0-DE.json", + "referenceNumber": 112, + "name": "Creative Commons Attribution Non Commercial 3.0 Germany", + "licenseId": "CC-BY-NC-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-4.0.json", + "referenceNumber": 70, + "name": "Creative Commons Attribution Non Commercial 4.0 International", + "licenseId": "CC-BY-NC-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-1.0.json", + "referenceNumber": 360, + "name": "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic", + "licenseId": "CC-BY-NC-ND-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd-nc/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-2.0.json", + "referenceNumber": 395, + "name": "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic", + "licenseId": "CC-BY-NC-ND-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-2.5.json", + "referenceNumber": 172, + "name": "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic", + "licenseId": "CC-BY-NC-ND-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-3.0.json", + "referenceNumber": 358, + "name": "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported", + "licenseId": "CC-BY-NC-ND-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-DE.json", + "referenceNumber": 139, + "name": "Creative Commons Attribution Non Commercial No Derivatives 3.0 Germany", + "licenseId": "CC-BY-NC-ND-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-IGO.json", + "referenceNumber": 253, + "name": "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO", + "licenseId": "CC-BY-NC-ND-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-4.0.json", + "referenceNumber": 8, + "name": "Creative Commons Attribution Non Commercial No Derivatives 4.0 International", + "licenseId": "CC-BY-NC-ND-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-1.0.json", + "referenceNumber": 587, + "name": "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic", + "licenseId": "CC-BY-NC-SA-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0.json", + "referenceNumber": 181, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic", + "licenseId": "CC-BY-NC-SA-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-DE.json", + "referenceNumber": 474, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.0 Germany", + "licenseId": "CC-BY-NC-SA-2.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-FR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-FR.json", + "referenceNumber": 196, + "name": "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 France", + "licenseId": "CC-BY-NC-SA-2.0-FR", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/fr/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-UK.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-UK.json", + "referenceNumber": 420, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.0 England and Wales", + "licenseId": "CC-BY-NC-SA-2.0-UK", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/uk/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.5.json", + "referenceNumber": 521, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic", + "licenseId": "CC-BY-NC-SA-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-3.0.json", + "referenceNumber": 379, + "name": "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported", + "licenseId": "CC-BY-NC-SA-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-3.0-DE.json", + "referenceNumber": 278, + "name": "Creative Commons Attribution Non Commercial Share Alike 3.0 Germany", + "licenseId": "CC-BY-NC-SA-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-3.0-IGO.json", + "referenceNumber": 44, + "name": "Creative Commons Attribution Non Commercial Share Alike 3.0 IGO", + "licenseId": "CC-BY-NC-SA-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-4.0.json", + "referenceNumber": 195, + "name": "Creative Commons Attribution Non Commercial Share Alike 4.0 International", + "licenseId": "CC-BY-NC-SA-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-1.0.json", + "referenceNumber": 331, + "name": "Creative Commons Attribution No Derivatives 1.0 Generic", + "licenseId": "CC-BY-ND-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/1.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-2.0.json", + "referenceNumber": 610, + "name": "Creative Commons Attribution No Derivatives 2.0 Generic", + "licenseId": "CC-BY-ND-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/2.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-2.5.json", + "referenceNumber": 91, + "name": "Creative Commons Attribution No Derivatives 2.5 Generic", + "licenseId": "CC-BY-ND-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/2.5/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-3.0.json", + "referenceNumber": 485, + "name": "Creative Commons Attribution No Derivatives 3.0 Unported", + "licenseId": "CC-BY-ND-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/3.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-3.0-DE.json", + "referenceNumber": 538, + "name": "Creative Commons Attribution No Derivatives 3.0 Germany", + "licenseId": "CC-BY-ND-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-4.0.json", + "referenceNumber": 134, + "name": "Creative Commons Attribution No Derivatives 4.0 International", + "licenseId": "CC-BY-ND-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-1.0.json", + "referenceNumber": 526, + "name": "Creative Commons Attribution Share Alike 1.0 Generic", + "licenseId": "CC-BY-SA-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.0.json", + "referenceNumber": 30, + "name": "Creative Commons Attribution Share Alike 2.0 Generic", + "licenseId": "CC-BY-SA-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.0-UK.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.0-UK.json", + "referenceNumber": 222, + "name": "Creative Commons Attribution Share Alike 2.0 England and Wales", + "licenseId": "CC-BY-SA-2.0-UK", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.0/uk/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.1-JP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.1-JP.json", + "referenceNumber": 251, + "name": "Creative Commons Attribution Share Alike 2.1 Japan", + "licenseId": "CC-BY-SA-2.1-JP", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.1/jp/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.5.json", + "referenceNumber": 47, + "name": "Creative Commons Attribution Share Alike 2.5 Generic", + "licenseId": "CC-BY-SA-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0.json", + "referenceNumber": 293, + "name": "Creative Commons Attribution Share Alike 3.0 Unported", + "licenseId": "CC-BY-SA-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0-AT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0-AT.json", + "referenceNumber": 15, + "name": "Creative Commons Attribution Share Alike 3.0 Austria", + "licenseId": "CC-BY-SA-3.0-AT", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/at/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0-DE.json", + "referenceNumber": 586, + "name": "Creative Commons Attribution Share Alike 3.0 Germany", + "licenseId": "CC-BY-SA-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0-IGO.json", + "referenceNumber": 109, + "name": "Creative Commons Attribution-ShareAlike 3.0 IGO", + "licenseId": "CC-BY-SA-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-4.0.json", + "referenceNumber": 86, + "name": "Creative Commons Attribution Share Alike 4.0 International", + "licenseId": "CC-BY-SA-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-PDDC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-PDDC.json", + "referenceNumber": 573, + "name": "Creative Commons Public Domain Dedication and Certification", + "licenseId": "CC-PDDC", + "seeAlso": [ + "https://creativecommons.org/licenses/publicdomain/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC0-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC0-1.0.json", + "referenceNumber": 392, + "name": "Creative Commons Zero v1.0 Universal", + "licenseId": "CC0-1.0", + "seeAlso": [ + "https://creativecommons.org/publicdomain/zero/1.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CDDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDDL-1.0.json", + "referenceNumber": 572, + "name": "Common Development and Distribution License 1.0", + "licenseId": "CDDL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/cddl1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CDDL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDDL-1.1.json", + "referenceNumber": 367, + "name": "Common Development and Distribution License 1.1", + "licenseId": "CDDL-1.1", + "seeAlso": [ + "http://glassfish.java.net/public/CDDL+GPL_1_1.html", + "https://javaee.github.io/glassfish/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDL-1.0.json", + "referenceNumber": 491, + "name": "Common Documentation License 1.0", + "licenseId": "CDL-1.0", + "seeAlso": [ + "http://www.opensource.apple.com/cdl/", + "https://fedoraproject.org/wiki/Licensing/Common_Documentation_License", + "https://www.gnu.org/licenses/license-list.html#ACDL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CDLA-Permissive-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDLA-Permissive-1.0.json", + "referenceNumber": 159, + "name": "Community Data License Agreement Permissive 1.0", + "licenseId": "CDLA-Permissive-1.0", + "seeAlso": [ + "https://cdla.io/permissive-1-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CDLA-Permissive-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDLA-Permissive-2.0.json", + "referenceNumber": 175, + "name": "Community Data License Agreement Permissive 2.0", + "licenseId": "CDLA-Permissive-2.0", + "seeAlso": [ + "https://cdla.dev/permissive-2-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CDLA-Sharing-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDLA-Sharing-1.0.json", + "referenceNumber": 221, + "name": "Community Data License Agreement Sharing 1.0", + "licenseId": "CDLA-Sharing-1.0", + "seeAlso": [ + "https://cdla.io/sharing-1-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-1.0.json", + "referenceNumber": 272, + "name": "CeCILL Free Software License Agreement v1.0", + "licenseId": "CECILL-1.0", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V1-fr.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-1.1.json", + "referenceNumber": 571, + "name": "CeCILL Free Software License Agreement v1.1", + "licenseId": "CECILL-1.1", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V1.1-US.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-2.0.json", + "referenceNumber": 269, + "name": "CeCILL Free Software License Agreement v2.0", + "licenseId": "CECILL-2.0", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V2-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CECILL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-2.1.json", + "referenceNumber": 569, + "name": "CeCILL Free Software License Agreement v2.1", + "licenseId": "CECILL-2.1", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CECILL-B.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-B.json", + "referenceNumber": 501, + "name": "CeCILL-B Free Software License Agreement", + "licenseId": "CECILL-B", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CECILL-C.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-C.json", + "referenceNumber": 503, + "name": "CeCILL-C Free Software License Agreement", + "licenseId": "CECILL-C", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-1.1.json", + "referenceNumber": 502, + "name": "CERN Open Hardware Licence v1.1", + "licenseId": "CERN-OHL-1.1", + "seeAlso": [ + "https://www.ohwr.org/project/licenses/wikis/cern-ohl-v1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-1.2.json", + "referenceNumber": 614, + "name": "CERN Open Hardware Licence v1.2", + "licenseId": "CERN-OHL-1.2", + "seeAlso": [ + "https://www.ohwr.org/project/licenses/wikis/cern-ohl-v1.2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-P-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-P-2.0.json", + "referenceNumber": 324, + "name": "CERN Open Hardware Licence Version 2 - Permissive", + "licenseId": "CERN-OHL-P-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-S-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-S-2.0.json", + "referenceNumber": 41, + "name": "CERN Open Hardware Licence Version 2 - Strongly Reciprocal", + "licenseId": "CERN-OHL-S-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-W-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-W-2.0.json", + "referenceNumber": 162, + "name": "CERN Open Hardware Licence Version 2 - Weakly Reciprocal", + "licenseId": "CERN-OHL-W-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CFITSIO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CFITSIO.json", + "referenceNumber": 209, + "name": "CFITSIO License", + "licenseId": "CFITSIO", + "seeAlso": [ + "https://heasarc.gsfc.nasa.gov/docs/software/fitsio/c/f_user/node9.html", + "https://heasarc.gsfc.nasa.gov/docs/software/ftools/fv/doc/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/check-cvs.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/check-cvs.json", + "referenceNumber": 611, + "name": "check-cvs License", + "licenseId": "check-cvs", + "seeAlso": [ + "http://cvs.savannah.gnu.org/viewvc/cvs/ccvs/contrib/check_cvs.in?revision\u003d1.1.4.3\u0026view\u003dmarkup\u0026pathrev\u003dcvs1-11-23#l2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/checkmk.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/checkmk.json", + "referenceNumber": 148, + "name": "Checkmk License", + "licenseId": "checkmk", + "seeAlso": [ + "https://github.com/libcheck/check/blob/master/checkmk/checkmk.in" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ClArtistic.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ClArtistic.json", + "referenceNumber": 202, + "name": "Clarified Artistic License", + "licenseId": "ClArtistic", + "seeAlso": [ + "http://gianluca.dellavedova.org/2011/01/03/clarified-artistic-license/", + "http://www.ncftp.com/ncftp/doc/LICENSE.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Clips.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Clips.json", + "referenceNumber": 419, + "name": "Clips License", + "licenseId": "Clips", + "seeAlso": [ + "https://github.com/DrItanium/maya/blob/master/LICENSE.CLIPS" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CMU-Mach.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CMU-Mach.json", + "referenceNumber": 212, + "name": "CMU Mach License", + "licenseId": "CMU-Mach", + "seeAlso": [ + "https://www.cs.cmu.edu/~410/licenses.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CNRI-Jython.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Jython.json", + "referenceNumber": 515, + "name": "CNRI Jython License", + "licenseId": "CNRI-Jython", + "seeAlso": [ + "http://www.jython.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CNRI-Python.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Python.json", + "referenceNumber": 124, + "name": "CNRI Python License", + "licenseId": "CNRI-Python", + "seeAlso": [ + "https://opensource.org/licenses/CNRI-Python" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CNRI-Python-GPL-Compatible.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Python-GPL-Compatible.json", + "referenceNumber": 533, + "name": "CNRI Python Open Source GPL Compatible License Agreement", + "licenseId": "CNRI-Python-GPL-Compatible", + "seeAlso": [ + "http://www.python.org/download/releases/1.6.1/download_win/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/COIL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/COIL-1.0.json", + "referenceNumber": 265, + "name": "Copyfree Open Innovation License", + "licenseId": "COIL-1.0", + "seeAlso": [ + "https://coil.apotheon.org/plaintext/01.0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Community-Spec-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Community-Spec-1.0.json", + "referenceNumber": 616, + "name": "Community Specification License 1.0", + "licenseId": "Community-Spec-1.0", + "seeAlso": [ + "https://github.com/CommunitySpecification/1.0/blob/master/1._Community_Specification_License-v1.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Condor-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Condor-1.1.json", + "referenceNumber": 141, + "name": "Condor Public License v1.1", + "licenseId": "Condor-1.1", + "seeAlso": [ + "http://research.cs.wisc.edu/condor/license.html#condor", + "http://web.archive.org/web/20111123062036/http://research.cs.wisc.edu/condor/license.html#condor" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/copyleft-next-0.3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/copyleft-next-0.3.0.json", + "referenceNumber": 110, + "name": "copyleft-next 0.3.0", + "licenseId": "copyleft-next-0.3.0", + "seeAlso": [ + "https://github.com/copyleft-next/copyleft-next/blob/master/Releases/copyleft-next-0.3.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/copyleft-next-0.3.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/copyleft-next-0.3.1.json", + "referenceNumber": 296, + "name": "copyleft-next 0.3.1", + "licenseId": "copyleft-next-0.3.1", + "seeAlso": [ + "https://github.com/copyleft-next/copyleft-next/blob/master/Releases/copyleft-next-0.3.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Cornell-Lossless-JPEG.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Cornell-Lossless-JPEG.json", + "referenceNumber": 271, + "name": "Cornell Lossless JPEG License", + "licenseId": "Cornell-Lossless-JPEG", + "seeAlso": [ + "https://android.googlesource.com/platform/external/dng_sdk/+/refs/heads/master/source/dng_lossless_jpeg.cpp#16", + "https://www.mssl.ucl.ac.uk/~mcrw/src/20050920/proto.h", + "https://gitlab.freedesktop.org/libopenraw/libopenraw/blob/master/lib/ljpegdecompressor.cpp#L32" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CPAL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPAL-1.0.json", + "referenceNumber": 383, + "name": "Common Public Attribution License 1.0", + "licenseId": "CPAL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CPAL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPL-1.0.json", + "referenceNumber": 310, + "name": "Common Public License 1.0", + "licenseId": "CPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CPOL-1.02.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPOL-1.02.json", + "referenceNumber": 78, + "name": "Code Project Open License 1.02", + "licenseId": "CPOL-1.02", + "seeAlso": [ + "http://www.codeproject.com/info/cpol10.aspx" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/Cronyx.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Cronyx.json", + "referenceNumber": 104, + "name": "Cronyx License", + "licenseId": "Cronyx", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/font/alias/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/font/cronyx-cyrillic/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/font/misc-cyrillic/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/font/screen-cyrillic/-/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Crossword.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Crossword.json", + "referenceNumber": 88, + "name": "Crossword License", + "licenseId": "Crossword", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Crossword" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CrystalStacker.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CrystalStacker.json", + "referenceNumber": 608, + "name": "CrystalStacker License", + "licenseId": "CrystalStacker", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:CrystalStacker?rd\u003dLicensing/CrystalStacker" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CUA-OPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CUA-OPL-1.0.json", + "referenceNumber": 364, + "name": "CUA Office Public License v1.0", + "licenseId": "CUA-OPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CUA-OPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Cube.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Cube.json", + "referenceNumber": 385, + "name": "Cube License", + "licenseId": "Cube", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Cube" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/curl.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/curl.json", + "referenceNumber": 243, + "name": "curl License", + "licenseId": "curl", + "seeAlso": [ + "https://github.com/bagder/curl/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/D-FSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/D-FSL-1.0.json", + "referenceNumber": 302, + "name": "Deutsche Freie Software Lizenz", + "licenseId": "D-FSL-1.0", + "seeAlso": [ + "http://www.dipp.nrw.de/d-fsl/lizenzen/", + "http://www.dipp.nrw.de/d-fsl/index_html/lizenzen/de/D-FSL-1_0_de.txt", + "http://www.dipp.nrw.de/d-fsl/index_html/lizenzen/en/D-FSL-1_0_en.txt", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/deutsche-freie-software-lizenz", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/german-free-software-license", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/D-FSL-1_0_de.txt/at_download/file", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/D-FSL-1_0_en.txt/at_download/file" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DEC-3-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DEC-3-Clause.json", + "referenceNumber": 453, + "name": "DEC 3-Clause License", + "licenseId": "DEC-3-Clause", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/COPYING?ref_type\u003dheads#L239" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/diffmark.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/diffmark.json", + "referenceNumber": 350, + "name": "diffmark license", + "licenseId": "diffmark", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/diffmark" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DL-DE-BY-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DL-DE-BY-2.0.json", + "referenceNumber": 266, + "name": "Data licence Germany – attribution – version 2.0", + "licenseId": "DL-DE-BY-2.0", + "seeAlso": [ + "https://www.govdata.de/dl-de/by-2-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DL-DE-ZERO-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DL-DE-ZERO-2.0.json", + "referenceNumber": 540, + "name": "Data licence Germany – zero – version 2.0", + "licenseId": "DL-DE-ZERO-2.0", + "seeAlso": [ + "https://www.govdata.de/dl-de/zero-2-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DOC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DOC.json", + "referenceNumber": 496, + "name": "DOC License", + "licenseId": "DOC", + "seeAlso": [ + "http://www.cs.wustl.edu/~schmidt/ACE-copying.html", + "https://www.dre.vanderbilt.edu/~schmidt/ACE-copying.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Dotseqn.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Dotseqn.json", + "referenceNumber": 229, + "name": "Dotseqn License", + "licenseId": "Dotseqn", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Dotseqn" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DRL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DRL-1.0.json", + "referenceNumber": 365, + "name": "Detection Rule License 1.0", + "licenseId": "DRL-1.0", + "seeAlso": [ + "https://github.com/Neo23x0/sigma/blob/master/LICENSE.Detection.Rules.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DRL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DRL-1.1.json", + "referenceNumber": 130, + "name": "Detection Rule License 1.1", + "licenseId": "DRL-1.1", + "seeAlso": [ + "https://github.com/SigmaHQ/Detection-Rule-License/blob/6ec7fbde6101d101b5b5d1fcb8f9b69fbc76c04a/LICENSE.Detection.Rules.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DSDP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DSDP.json", + "referenceNumber": 123, + "name": "DSDP License", + "licenseId": "DSDP", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/DSDP" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/dtoa.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/dtoa.json", + "referenceNumber": 410, + "name": "David M. Gay dtoa License", + "licenseId": "dtoa", + "seeAlso": [ + "https://github.com/SWI-Prolog/swipl-devel/blob/master/src/os/dtoa.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/dvipdfm.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/dvipdfm.json", + "referenceNumber": 198, + "name": "dvipdfm License", + "licenseId": "dvipdfm", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/dvipdfm" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ECL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ECL-1.0.json", + "referenceNumber": 373, + "name": "Educational Community License v1.0", + "licenseId": "ECL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/ECL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ECL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ECL-2.0.json", + "referenceNumber": 158, + "name": "Educational Community License v2.0", + "licenseId": "ECL-2.0", + "seeAlso": [ + "https://opensource.org/licenses/ECL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/eCos-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/eCos-2.0.json", + "referenceNumber": 344, + "name": "eCos license version 2.0", + "licenseId": "eCos-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/ecos-license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EFL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EFL-1.0.json", + "referenceNumber": 69, + "name": "Eiffel Forum License v1.0", + "licenseId": "EFL-1.0", + "seeAlso": [ + "http://www.eiffel-nice.org/license/forum.txt", + "https://opensource.org/licenses/EFL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/EFL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EFL-2.0.json", + "referenceNumber": 409, + "name": "Eiffel Forum License v2.0", + "licenseId": "EFL-2.0", + "seeAlso": [ + "http://www.eiffel-nice.org/license/eiffel-forum-license-2.html", + "https://opensource.org/licenses/EFL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/eGenix.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/eGenix.json", + "referenceNumber": 216, + "name": "eGenix.com Public License 1.1.0", + "licenseId": "eGenix", + "seeAlso": [ + "http://www.egenix.com/products/eGenix.com-Public-License-1.1.0.pdf", + "https://fedoraproject.org/wiki/Licensing/eGenix.com_Public_License_1.1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Elastic-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Elastic-2.0.json", + "referenceNumber": 389, + "name": "Elastic License 2.0", + "licenseId": "Elastic-2.0", + "seeAlso": [ + "https://www.elastic.co/licensing/elastic-license", + "https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE-2.0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Entessa.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Entessa.json", + "referenceNumber": 29, + "name": "Entessa Public License v1.0", + "licenseId": "Entessa", + "seeAlso": [ + "https://opensource.org/licenses/Entessa" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/EPICS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPICS.json", + "referenceNumber": 31, + "name": "EPICS Open License", + "licenseId": "EPICS", + "seeAlso": [ + "https://epics.anl.gov/license/open.php" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPL-1.0.json", + "referenceNumber": 92, + "name": "Eclipse Public License 1.0", + "licenseId": "EPL-1.0", + "seeAlso": [ + "http://www.eclipse.org/legal/epl-v10.html", + "https://opensource.org/licenses/EPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPL-2.0.json", + "referenceNumber": 593, + "name": "Eclipse Public License 2.0", + "licenseId": "EPL-2.0", + "seeAlso": [ + "https://www.eclipse.org/legal/epl-2.0", + "https://www.opensource.org/licenses/EPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ErlPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ErlPL-1.1.json", + "referenceNumber": 167, + "name": "Erlang Public License v1.1", + "licenseId": "ErlPL-1.1", + "seeAlso": [ + "http://www.erlang.org/EPLICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/etalab-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/etalab-2.0.json", + "referenceNumber": 478, + "name": "Etalab Open License 2.0", + "licenseId": "etalab-2.0", + "seeAlso": [ + "https://github.com/DISIC/politique-de-contribution-open-source/blob/master/LICENSE.pdf", + "https://raw.githubusercontent.com/DISIC/politique-de-contribution-open-source/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EUDatagrid.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUDatagrid.json", + "referenceNumber": 48, + "name": "EU DataGrid Software License", + "licenseId": "EUDatagrid", + "seeAlso": [ + "http://eu-datagrid.web.cern.ch/eu-datagrid/license.html", + "https://opensource.org/licenses/EUDatagrid" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.0.json", + "referenceNumber": 270, + "name": "European Union Public License 1.0", + "licenseId": "EUPL-1.0", + "seeAlso": [ + "http://ec.europa.eu/idabc/en/document/7330.html", + "http://ec.europa.eu/idabc/servlets/Doc027f.pdf?id\u003d31096" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.1.json", + "referenceNumber": 20, + "name": "European Union Public License 1.1", + "licenseId": "EUPL-1.1", + "seeAlso": [ + "https://joinup.ec.europa.eu/software/page/eupl/licence-eupl", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/eupl1.1.-licence-en_0.pdf", + "https://opensource.org/licenses/EUPL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.2.json", + "referenceNumber": 62, + "name": "European Union Public License 1.2", + "licenseId": "EUPL-1.2", + "seeAlso": [ + "https://joinup.ec.europa.eu/page/eupl-text-11-12", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/eupl_v1.2_en.pdf", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/2020-03/EUPL-1.2%20EN.txt", + "https://joinup.ec.europa.eu/sites/default/files/inline-files/EUPL%20v1_2%20EN(1).txt", + "http://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri\u003dCELEX:32017D0863", + "https://opensource.org/licenses/EUPL-1.2" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Eurosym.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Eurosym.json", + "referenceNumber": 464, + "name": "Eurosym License", + "licenseId": "Eurosym", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Eurosym" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Fair.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Fair.json", + "referenceNumber": 557, + "name": "Fair License", + "licenseId": "Fair", + "seeAlso": [ + "https://web.archive.org/web/20150926120323/http://fairlicense.org/", + "https://opensource.org/licenses/Fair" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/FBM.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FBM.json", + "referenceNumber": 115, + "name": "Fuzzy Bitmap License", + "licenseId": "FBM", + "seeAlso": [ + "https://github.com/SWI-Prolog/packages-xpce/blob/161a40cd82004f731ba48024f9d30af388a7edf5/src/img/gifwrite.c#L21-L26" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FDK-AAC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FDK-AAC.json", + "referenceNumber": 168, + "name": "Fraunhofer FDK AAC Codec Library", + "licenseId": "FDK-AAC", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/FDK-AAC", + "https://directory.fsf.org/wiki/License:Fdk" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Ferguson-Twofish.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Ferguson-Twofish.json", + "referenceNumber": 414, + "name": "Ferguson Twofish License", + "licenseId": "Ferguson-Twofish", + "seeAlso": [ + "https://github.com/wernerd/ZRTPCPP/blob/6b3cd8e6783642292bad0c21e3e5e5ce45ff3e03/cryptcommon/twofish.c#L113C3-L127" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Frameworx-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Frameworx-1.0.json", + "referenceNumber": 287, + "name": "Frameworx Open License 1.0", + "licenseId": "Frameworx-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Frameworx-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/FreeBSD-DOC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FreeBSD-DOC.json", + "referenceNumber": 257, + "name": "FreeBSD Documentation License", + "licenseId": "FreeBSD-DOC", + "seeAlso": [ + "https://www.freebsd.org/copyright/freebsd-doc-license/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FreeImage.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FreeImage.json", + "referenceNumber": 144, + "name": "FreeImage Public License v1.0", + "licenseId": "FreeImage", + "seeAlso": [ + "http://freeimage.sourceforge.net/freeimage-license.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FSFAP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFAP.json", + "referenceNumber": 1, + "name": "FSF All Permissive License", + "licenseId": "FSFAP", + "seeAlso": [ + "https://www.gnu.org/prep/maintain/html_node/License-Notices-for-Other-Files.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/FSFAP-no-warranty-disclaimer.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFAP-no-warranty-disclaimer.json", + "referenceNumber": 313, + "name": "FSF All Permissive License (without Warranty)", + "licenseId": "FSFAP-no-warranty-disclaimer", + "seeAlso": [ + "https://git.savannah.gnu.org/cgit/wget.git/tree/util/trunc.c?h\u003dv1.21.3\u0026id\u003d40747a11e44ced5a8ac628a41f879ced3e2ebce9#n6" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FSFUL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFUL.json", + "referenceNumber": 5, + "name": "FSF Unlimited License", + "licenseId": "FSFUL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/FSF_Unlimited_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FSFULLR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFULLR.json", + "referenceNumber": 603, + "name": "FSF Unlimited License (with License Retention)", + "licenseId": "FSFULLR", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/FSF_Unlimited_License#License_Retention_Variant" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FSFULLRWD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFULLRWD.json", + "referenceNumber": 406, + "name": "FSF Unlimited License (With License Retention and Warranty Disclaimer)", + "licenseId": "FSFULLRWD", + "seeAlso": [ + "https://lists.gnu.org/archive/html/autoconf/2012-04/msg00061.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FTL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FTL.json", + "referenceNumber": 562, + "name": "Freetype Project License", + "licenseId": "FTL", + "seeAlso": [ + "http://freetype.fis.uniroma2.it/FTL.TXT", + "http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT", + "http://gitlab.freedesktop.org/freetype/freetype/-/raw/master/docs/FTL.TXT" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Furuseth.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Furuseth.json", + "referenceNumber": 27, + "name": "Furuseth License", + "licenseId": "Furuseth", + "seeAlso": [ + "https://git.openldap.org/openldap/openldap/-/blob/master/COPYRIGHT?ref_type\u003dheads#L39-51" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/fwlw.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/fwlw.json", + "referenceNumber": 402, + "name": "fwlw License", + "licenseId": "fwlw", + "seeAlso": [ + "https://mirrors.nic.cz/tex-archive/macros/latex/contrib/fwlw/README" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GCR-docs.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GCR-docs.json", + "referenceNumber": 98, + "name": "Gnome GCR Documentation License", + "licenseId": "GCR-docs", + "seeAlso": [ + "https://github.com/GNOME/gcr/blob/master/docs/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GD.json", + "referenceNumber": 326, + "name": "GD License", + "licenseId": "GD", + "seeAlso": [ + "https://libgd.github.io/manuals/2.3.0/files/license-txt.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1.json", + "referenceNumber": 90, + "name": "GNU Free Documentation License v1.1", + "licenseId": "GFDL-1.1", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-invariants-only.json", + "referenceNumber": 514, + "name": "GNU Free Documentation License v1.1 only - invariants", + "licenseId": "GFDL-1.1-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-invariants-or-later.json", + "referenceNumber": 381, + "name": "GNU Free Documentation License v1.1 or later - invariants", + "licenseId": "GFDL-1.1-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-no-invariants-only.json", + "referenceNumber": 173, + "name": "GNU Free Documentation License v1.1 only - no invariants", + "licenseId": "GFDL-1.1-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-no-invariants-or-later.json", + "referenceNumber": 301, + "name": "GNU Free Documentation License v1.1 or later - no invariants", + "licenseId": "GFDL-1.1-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-only.json", + "referenceNumber": 309, + "name": "GNU Free Documentation License v1.1 only", + "licenseId": "GFDL-1.1-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-or-later.json", + "referenceNumber": 351, + "name": "GNU Free Documentation License v1.1 or later", + "licenseId": "GFDL-1.1-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2.json", + "referenceNumber": 239, + "name": "GNU Free Documentation License v1.2", + "licenseId": "GFDL-1.2", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-invariants-only.json", + "referenceNumber": 615, + "name": "GNU Free Documentation License v1.2 only - invariants", + "licenseId": "GFDL-1.2-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-invariants-or-later.json", + "referenceNumber": 42, + "name": "GNU Free Documentation License v1.2 or later - invariants", + "licenseId": "GFDL-1.2-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-no-invariants-only.json", + "referenceNumber": 179, + "name": "GNU Free Documentation License v1.2 only - no invariants", + "licenseId": "GFDL-1.2-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-no-invariants-or-later.json", + "referenceNumber": 595, + "name": "GNU Free Documentation License v1.2 or later - no invariants", + "licenseId": "GFDL-1.2-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-only.json", + "referenceNumber": 224, + "name": "GNU Free Documentation License v1.2 only", + "licenseId": "GFDL-1.2-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-or-later.json", + "referenceNumber": 72, + "name": "GNU Free Documentation License v1.2 or later", + "licenseId": "GFDL-1.2-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3.json", + "referenceNumber": 345, + "name": "GNU Free Documentation License v1.3", + "licenseId": "GFDL-1.3", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-invariants-only.json", + "referenceNumber": 61, + "name": "GNU Free Documentation License v1.3 only - invariants", + "licenseId": "GFDL-1.3-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-invariants-or-later.json", + "referenceNumber": 258, + "name": "GNU Free Documentation License v1.3 or later - invariants", + "licenseId": "GFDL-1.3-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-no-invariants-only.json", + "referenceNumber": 22, + "name": "GNU Free Documentation License v1.3 only - no invariants", + "licenseId": "GFDL-1.3-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-no-invariants-or-later.json", + "referenceNumber": 374, + "name": "GNU Free Documentation License v1.3 or later - no invariants", + "licenseId": "GFDL-1.3-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-only.json", + "referenceNumber": 323, + "name": "GNU Free Documentation License v1.3 only", + "licenseId": "GFDL-1.3-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-or-later.json", + "referenceNumber": 449, + "name": "GNU Free Documentation License v1.3 or later", + "licenseId": "GFDL-1.3-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Giftware.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Giftware.json", + "referenceNumber": 311, + "name": "Giftware License", + "licenseId": "Giftware", + "seeAlso": [ + "http://liballeg.org/license.html#allegro-4-the-giftware-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GL2PS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GL2PS.json", + "referenceNumber": 87, + "name": "GL2PS License", + "licenseId": "GL2PS", + "seeAlso": [ + "http://www.geuz.org/gl2ps/COPYING.GL2PS" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Glide.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Glide.json", + "referenceNumber": 566, + "name": "3dfx Glide License", + "licenseId": "Glide", + "seeAlso": [ + "http://www.users.on.net/~triforce/glidexp/COPYING.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Glulxe.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Glulxe.json", + "referenceNumber": 3, + "name": "Glulxe License", + "licenseId": "Glulxe", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Glulxe" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GLWTPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GLWTPL.json", + "referenceNumber": 454, + "name": "Good Luck With That Public License", + "licenseId": "GLWTPL", + "seeAlso": [ + "https://github.com/me-shaon/GLWTPL/commit/da5f6bc734095efbacb442c0b31e33a65b9d6e85" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/gnuplot.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/gnuplot.json", + "referenceNumber": 177, + "name": "gnuplot License", + "licenseId": "gnuplot", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Gnuplot" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0.json", + "referenceNumber": 452, + "name": "GNU General Public License v1.0 only", + "licenseId": "GPL-1.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0+.json", + "referenceNumber": 448, + "name": "GNU General Public License v1.0 or later", + "licenseId": "GPL-1.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0-only.json", + "referenceNumber": 305, + "name": "GNU General Public License v1.0 only", + "licenseId": "GPL-1.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0-or-later.json", + "referenceNumber": 230, + "name": "GNU General Public License v1.0 or later", + "licenseId": "GPL-1.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0.json", + "referenceNumber": 264, + "name": "GNU General Public License v2.0 only", + "licenseId": "GPL-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0+.json", + "referenceNumber": 77, + "name": "GNU General Public License v2.0 or later", + "licenseId": "GPL-2.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-only.json", + "referenceNumber": 600, + "name": "GNU General Public License v2.0 only", + "licenseId": "GPL-2.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-or-later.json", + "referenceNumber": 54, + "name": "GNU General Public License v2.0 or later", + "licenseId": "GPL-2.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-autoconf-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-autoconf-exception.json", + "referenceNumber": 393, + "name": "GNU General Public License v2.0 w/Autoconf exception", + "licenseId": "GPL-2.0-with-autoconf-exception", + "seeAlso": [ + "http://ac-archive.sourceforge.net/doc/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-bison-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-bison-exception.json", + "referenceNumber": 361, + "name": "GNU General Public License v2.0 w/Bison exception", + "licenseId": "GPL-2.0-with-bison-exception", + "seeAlso": [ + "http://git.savannah.gnu.org/cgit/bison.git/tree/data/yacc.c?id\u003d193d7c7054ba7197b0789e14965b739162319b5e#n141" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-classpath-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-classpath-exception.json", + "referenceNumber": 527, + "name": "GNU General Public License v2.0 w/Classpath exception", + "licenseId": "GPL-2.0-with-classpath-exception", + "seeAlso": [ + "https://www.gnu.org/software/classpath/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-font-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-font-exception.json", + "referenceNumber": 400, + "name": "GNU General Public License v2.0 w/Font exception", + "licenseId": "GPL-2.0-with-font-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-faq.html#FontException" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-GCC-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-GCC-exception.json", + "referenceNumber": 343, + "name": "GNU General Public License v2.0 w/GCC Runtime Library exception", + "licenseId": "GPL-2.0-with-GCC-exception", + "seeAlso": [ + "https://gcc.gnu.org/git/?p\u003dgcc.git;a\u003dblob;f\u003dgcc/libgcc1.c;h\u003d762f5143fc6eed57b6797c82710f3538aa52b40b;hb\u003dcb143a3ce4fb417c68f5fa2691a1b1b1053dfba9#l10" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0.json", + "referenceNumber": 488, + "name": "GNU General Public License v3.0 only", + "licenseId": "GPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0+.json", + "referenceNumber": 283, + "name": "GNU General Public License v3.0 or later", + "licenseId": "GPL-3.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-only.json", + "referenceNumber": 495, + "name": "GNU General Public License v3.0 only", + "licenseId": "GPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-or-later.json", + "referenceNumber": 372, + "name": "GNU General Public License v3.0 or later", + "licenseId": "GPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-with-autoconf-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-with-autoconf-exception.json", + "referenceNumber": 489, + "name": "GNU General Public License v3.0 w/Autoconf exception", + "licenseId": "GPL-3.0-with-autoconf-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/autoconf-exception-3.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-with-GCC-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-with-GCC-exception.json", + "referenceNumber": 352, + "name": "GNU General Public License v3.0 w/GCC Runtime Library exception", + "licenseId": "GPL-3.0-with-GCC-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/gcc-exception-3.1.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Graphics-Gems.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Graphics-Gems.json", + "referenceNumber": 275, + "name": "Graphics Gems License", + "licenseId": "Graphics-Gems", + "seeAlso": [ + "https://github.com/erich666/GraphicsGems/blob/master/LICENSE.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/gSOAP-1.3b.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/gSOAP-1.3b.json", + "referenceNumber": 346, + "name": "gSOAP Public License v1.3b", + "licenseId": "gSOAP-1.3b", + "seeAlso": [ + "http://www.cs.fsu.edu/~engelen/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HaskellReport.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HaskellReport.json", + "referenceNumber": 560, + "name": "Haskell Language Report License", + "licenseId": "HaskellReport", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Haskell_Language_Report_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/hdparm.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/hdparm.json", + "referenceNumber": 249, + "name": "hdparm License", + "licenseId": "hdparm", + "seeAlso": [ + "https://github.com/Distrotech/hdparm/blob/4517550db29a91420fb2b020349523b1b4512df2/LICENSE.TXT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Hippocratic-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Hippocratic-2.1.json", + "referenceNumber": 10, + "name": "Hippocratic License 2.1", + "licenseId": "Hippocratic-2.1", + "seeAlso": [ + "https://firstdonoharm.dev/version/2/1/license.html", + "https://github.com/EthicalSource/hippocratic-license/blob/58c0e646d64ff6fbee275bfe2b9492f914e3ab2a/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HP-1986.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HP-1986.json", + "referenceNumber": 11, + "name": "Hewlett-Packard 1986 License", + "licenseId": "HP-1986", + "seeAlso": [ + "https://sourceware.org/git/?p\u003dnewlib-cygwin.git;a\u003dblob;f\u003dnewlib/libc/machine/hppa/memchr.S;h\u003d1cca3e5e8867aa4bffef1f75a5c1bba25c0c441e;hb\u003dHEAD#l2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HP-1989.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HP-1989.json", + "referenceNumber": 418, + "name": "Hewlett-Packard 1989 License", + "licenseId": "HP-1989", + "seeAlso": [ + "https://github.com/bleargh45/Data-UUID/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND.json", + "referenceNumber": 416, + "name": "Historical Permission Notice and Disclaimer", + "licenseId": "HPND", + "seeAlso": [ + "https://opensource.org/licenses/HPND", + "http://lists.opensource.org/pipermail/license-discuss_lists.opensource.org/2002-November/006304.html" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/HPND-DEC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-DEC.json", + "referenceNumber": 206, + "name": "Historical Permission Notice and Disclaimer - DEC variant", + "licenseId": "HPND-DEC", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/app/xkbcomp/-/blob/master/COPYING?ref_type\u003dheads#L69" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-doc.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-doc.json", + "referenceNumber": 450, + "name": "Historical Permission Notice and Disclaimer - documentation variant", + "licenseId": "HPND-doc", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/lib/libxext/-/blob/master/COPYING?ref_type\u003dheads#L185-197", + "https://gitlab.freedesktop.org/xorg/lib/libxtst/-/blob/master/COPYING?ref_type\u003dheads#L70-77" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-doc-sell.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-doc-sell.json", + "referenceNumber": 113, + "name": "Historical Permission Notice and Disclaimer - documentation sell variant", + "licenseId": "HPND-doc-sell", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/lib/libxtst/-/blob/master/COPYING?ref_type\u003dheads#L108-117", + "https://gitlab.freedesktop.org/xorg/lib/libxext/-/blob/master/COPYING?ref_type\u003dheads#L153-162" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-export-US.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-export-US.json", + "referenceNumber": 329, + "name": "HPND with US Government export control warning", + "licenseId": "HPND-export-US", + "seeAlso": [ + "https://www.kermitproject.org/ck90.html#source" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-export-US-modify.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-export-US-modify.json", + "referenceNumber": 304, + "name": "HPND with US Government export control warning and modification rqmt", + "licenseId": "HPND-export-US-modify", + "seeAlso": [ + "https://github.com/krb5/krb5/blob/krb5-1.21.2-final/NOTICE#L1157-L1182", + "https://github.com/pythongssapi/k5test/blob/v0.10.3/K5TEST-LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-Kevlin-Henney.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-Kevlin-Henney.json", + "referenceNumber": 553, + "name": "Historical Permission Notice and Disclaimer - Kevlin Henney variant", + "licenseId": "HPND-Kevlin-Henney", + "seeAlso": [ + "https://github.com/mruby/mruby/blob/83d12f8d52522cdb7c8cc46fad34821359f453e6/mrbgems/mruby-dir/src/Win/dirent.c#L127-L140" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-Markus-Kuhn.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-Markus-Kuhn.json", + "referenceNumber": 223, + "name": "Historical Permission Notice and Disclaimer - Markus Kuhn variant", + "licenseId": "HPND-Markus-Kuhn", + "seeAlso": [ + "https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c", + "https://sourceware.org/git/?p\u003dbinutils-gdb.git;a\u003dblob;f\u003dreadline/readline/support/wcwidth.c;h\u003d0f5ec995796f4813abbcf4972aec0378ab74722a;hb\u003dHEAD#l55" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-MIT-disclaimer.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-MIT-disclaimer.json", + "referenceNumber": 100, + "name": "Historical Permission Notice and Disclaimer with MIT disclaimer", + "licenseId": "HPND-MIT-disclaimer", + "seeAlso": [ + "https://metacpan.org/release/NLNETLABS/Net-DNS-SEC-1.22/source/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-Pbmplus.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-Pbmplus.json", + "referenceNumber": 260, + "name": "Historical Permission Notice and Disclaimer - Pbmplus variant", + "licenseId": "HPND-Pbmplus", + "seeAlso": [ + "https://sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/netpbm.c#l8" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-sell-MIT-disclaimer-xserver.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-sell-MIT-disclaimer-xserver.json", + "referenceNumber": 170, + "name": + "Historical Permission Notice and Disclaimer - sell xserver variant with MIT disclaimer", + "licenseId": "HPND-sell-MIT-disclaimer-xserver", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/COPYING?ref_type\u003dheads#L1781" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-sell-regexpr.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-sell-regexpr.json", + "referenceNumber": 580, + "name": "Historical Permission Notice and Disclaimer - sell regexpr variant", + "licenseId": "HPND-sell-regexpr", + "seeAlso": [ + "https://gitlab.com/bacula-org/bacula/-/blob/Branch-11.0/bacula/LICENSE-FOSS?ref_type\u003dheads#L245" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-sell-variant.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-sell-variant.json", + "referenceNumber": 79, + "name": "Historical Permission Notice and Disclaimer - sell variant", + "licenseId": "HPND-sell-variant", + "seeAlso": [ + "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/sunrpc/auth_gss/gss_generic_token.c?h\u003dv4.19" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-sell-variant-MIT-disclaimer.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-sell-variant-MIT-disclaimer.json", + "referenceNumber": 318, + "name": "HPND sell variant with MIT disclaimer", + "licenseId": "HPND-sell-variant-MIT-disclaimer", + "seeAlso": [ + "https://github.com/sigmavirus24/x11-ssh-askpass/blob/master/README" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-UC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-UC.json", + "referenceNumber": 531, + "name": + "Historical Permission Notice and Disclaimer - University of California variant", + "licenseId": "HPND-UC", + "seeAlso": [ + "https://core.tcl-lang.org/tk/file?name\u003dcompat/unistd.h" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HTMLTIDY.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HTMLTIDY.json", + "referenceNumber": 120, + "name": "HTML Tidy License", + "licenseId": "HTMLTIDY", + "seeAlso": [ + "https://github.com/htacg/tidy-html5/blob/next/README/LICENSE.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/IBM-pibs.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IBM-pibs.json", + "referenceNumber": 596, + "name": "IBM PowerPC Initialization and Boot Software", + "licenseId": "IBM-pibs", + "seeAlso": [ + "http://git.denx.de/?p\u003du-boot.git;a\u003dblob;f\u003darch/powerpc/cpu/ppc4xx/miiphy.c;h\u003d297155fdafa064b955e53e9832de93bfb0cfb85b;hb\u003d9fab4bf4cc077c21e43941866f3f2c196f28670d" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ICU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ICU.json", + "referenceNumber": 330, + "name": "ICU License", + "licenseId": "ICU", + "seeAlso": [ + "http://source.icu-project.org/repos/icu/icu/trunk/license.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/IEC-Code-Components-EULA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IEC-Code-Components-EULA.json", + "referenceNumber": 356, + "name": "IEC Code Components End-user licence agreement", + "licenseId": "IEC-Code-Components-EULA", + "seeAlso": [ + "https://www.iec.ch/webstore/custserv/pdf/CC-EULA.pdf", + "https://www.iec.ch/CCv1", + "https://www.iec.ch/copyright" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/IJG.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IJG.json", + "referenceNumber": 314, + "name": "Independent JPEG Group License", + "licenseId": "IJG", + "seeAlso": [ + "http://dev.w3.org/cvsweb/Amaya/libjpeg/Attic/README?rev\u003d1.2" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/IJG-short.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IJG-short.json", + "referenceNumber": 316, + "name": "Independent JPEG Group License - short", + "licenseId": "IJG-short", + "seeAlso": [ + "https://sourceforge.net/p/xmedcon/code/ci/master/tree/libs/ljpg/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ImageMagick.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ImageMagick.json", + "referenceNumber": 391, + "name": "ImageMagick License", + "licenseId": "ImageMagick", + "seeAlso": [ + "http://www.imagemagick.org/script/license.php" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/iMatix.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/iMatix.json", + "referenceNumber": 295, + "name": "iMatix Standard Function Library Agreement", + "licenseId": "iMatix", + "seeAlso": [ + "http://legacy.imatix.com/html/sfl/sfl4.htm#license" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Imlib2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Imlib2.json", + "referenceNumber": 547, + "name": "Imlib2 License", + "licenseId": "Imlib2", + "seeAlso": [ + "http://trac.enlightenment.org/e/browser/trunk/imlib2/COPYING", + "https://git.enlightenment.org/legacy/imlib2.git/tree/COPYING" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Info-ZIP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Info-ZIP.json", + "referenceNumber": 490, + "name": "Info-ZIP License", + "licenseId": "Info-ZIP", + "seeAlso": [ + "http://www.info-zip.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Inner-Net-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Inner-Net-2.0.json", + "referenceNumber": 435, + "name": "Inner Net License v2.0", + "licenseId": "Inner-Net-2.0", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Inner_Net_License", + "https://sourceware.org/git/?p\u003dglibc.git;a\u003dblob;f\u003dLICENSES;h\u003d530893b1dc9ea00755603c68fb36bd4fc38a7be8;hb\u003dHEAD#l207" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Intel.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Intel.json", + "referenceNumber": 399, + "name": "Intel Open Source License", + "licenseId": "Intel", + "seeAlso": [ + "https://opensource.org/licenses/Intel" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Intel-ACPI.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Intel-ACPI.json", + "referenceNumber": 306, + "name": "Intel ACPI Software License Agreement", + "licenseId": "Intel-ACPI", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Intel_ACPI_Software_License_Agreement" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Interbase-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Interbase-1.0.json", + "referenceNumber": 237, + "name": "Interbase Public License v1.0", + "licenseId": "Interbase-1.0", + "seeAlso": [ + "https://web.archive.org/web/20060319014854/http://info.borland.com/devsupport/interbase/opensource/IPL.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/IPA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IPA.json", + "referenceNumber": 561, + "name": "IPA Font License", + "licenseId": "IPA", + "seeAlso": [ + "https://opensource.org/licenses/IPA" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/IPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IPL-1.0.json", + "referenceNumber": 53, + "name": "IBM Public License v1.0", + "licenseId": "IPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/IPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ISC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ISC.json", + "referenceNumber": 322, + "name": "ISC License", + "licenseId": "ISC", + "seeAlso": [ + "https://www.isc.org/licenses/", + "https://www.isc.org/downloads/software-support-policy/isc-license/", + "https://opensource.org/licenses/ISC" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ISC-Veillard.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ISC-Veillard.json", + "referenceNumber": 284, + "name": "ISC Veillard variant", + "licenseId": "ISC-Veillard", + "seeAlso": [ + "https://raw.githubusercontent.com/GNOME/libxml2/4c2e7c651f6c2f0d1a74f350cbda95f7df3e7017/hash.c", + "https://github.com/GNOME/libxml2/blob/master/dict.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Jam.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Jam.json", + "referenceNumber": 505, + "name": "Jam License", + "licenseId": "Jam", + "seeAlso": [ + "https://www.boost.org/doc/libs/1_35_0/doc/html/jam.html", + "https://web.archive.org/web/20160330173339/https://swarm.workshop.perforce.com/files/guest/perforce_software/jam/src/README" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/JasPer-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JasPer-2.0.json", + "referenceNumber": 60, + "name": "JasPer License", + "licenseId": "JasPer-2.0", + "seeAlso": [ + "http://www.ece.uvic.ca/~mdadams/jasper/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/JPL-image.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JPL-image.json", + "referenceNumber": 509, + "name": "JPL Image Use Policy", + "licenseId": "JPL-image", + "seeAlso": [ + "https://www.jpl.nasa.gov/jpl-image-use-policy" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/JPNIC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JPNIC.json", + "referenceNumber": 579, + "name": "Japan Network Information Center License", + "licenseId": "JPNIC", + "seeAlso": [ + "https://gitlab.isc.org/isc-projects/bind9/blob/master/COPYRIGHT#L366" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/JSON.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JSON.json", + "referenceNumber": 155, + "name": "JSON License", + "licenseId": "JSON", + "seeAlso": [ + "http://www.json.org/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/Kastrup.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Kastrup.json", + "referenceNumber": 570, + "name": "Kastrup License", + "licenseId": "Kastrup", + "seeAlso": [ + "https://ctan.math.utah.edu/ctan/tex-archive/macros/generic/kastrup/binhex.dtx" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Kazlib.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Kazlib.json", + "referenceNumber": 551, + "name": "Kazlib License", + "licenseId": "Kazlib", + "seeAlso": [ + "http://git.savannah.gnu.org/cgit/kazlib.git/tree/except.c?id\u003d0062df360c2d17d57f6af19b0e444c51feb99036" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Knuth-CTAN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Knuth-CTAN.json", + "referenceNumber": 504, + "name": "Knuth CTAN License", + "licenseId": "Knuth-CTAN", + "seeAlso": [ + "https://ctan.org/license/knuth" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LAL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LAL-1.2.json", + "referenceNumber": 468, + "name": "Licence Art Libre 1.2", + "licenseId": "LAL-1.2", + "seeAlso": [ + "http://artlibre.org/licence/lal/licence-art-libre-12/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LAL-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LAL-1.3.json", + "referenceNumber": 118, + "name": "Licence Art Libre 1.3", + "licenseId": "LAL-1.3", + "seeAlso": [ + "https://artlibre.org/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Latex2e.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Latex2e.json", + "referenceNumber": 597, + "name": "Latex2e License", + "licenseId": "Latex2e", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Latex2e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Latex2e-translated-notice.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Latex2e-translated-notice.json", + "referenceNumber": 276, + "name": "Latex2e with translated notice permission", + "licenseId": "Latex2e-translated-notice", + "seeAlso": [ + "https://git.savannah.gnu.org/cgit/indent.git/tree/doc/indent.texi?id\u003da74c6b4ee49397cf330b333da1042bffa60ed14f#n74" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Leptonica.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Leptonica.json", + "referenceNumber": 192, + "name": "Leptonica License", + "licenseId": "Leptonica", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Leptonica" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0.json", + "referenceNumber": 68, + "name": "GNU Library General Public License v2 only", + "licenseId": "LGPL-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0+.json", + "referenceNumber": 492, + "name": "GNU Library General Public License v2 or later", + "licenseId": "LGPL-2.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0-only.json", + "referenceNumber": 434, + "name": "GNU Library General Public License v2 only", + "licenseId": "LGPL-2.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0-or-later.json", + "referenceNumber": 157, + "name": "GNU Library General Public License v2 or later", + "licenseId": "LGPL-2.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1.json", + "referenceNumber": 623, + "name": "GNU Lesser General Public License v2.1 only", + "licenseId": "LGPL-2.1", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1+.json", + "referenceNumber": 357, + "name": "GNU Lesser General Public License v2.1 or later", + "licenseId": "LGPL-2.1+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1-only.json", + "referenceNumber": 35, + "name": "GNU Lesser General Public License v2.1 only", + "licenseId": "LGPL-2.1-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1-or-later.json", + "referenceNumber": 349, + "name": "GNU Lesser General Public License v2.1 or later", + "licenseId": "LGPL-2.1-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0.json", + "referenceNumber": 617, + "name": "GNU Lesser General Public License v3.0 only", + "licenseId": "LGPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://www.gnu.org/licenses/lgpl+gpl-3.0.txt", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0+.json", + "referenceNumber": 52, + "name": "GNU Lesser General Public License v3.0 or later", + "licenseId": "LGPL-3.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://www.gnu.org/licenses/lgpl+gpl-3.0.txt", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0-only.json", + "referenceNumber": 307, + "name": "GNU Lesser General Public License v3.0 only", + "licenseId": "LGPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://www.gnu.org/licenses/lgpl+gpl-3.0.txt", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0-or-later.json", + "referenceNumber": 64, + "name": "GNU Lesser General Public License v3.0 or later", + "licenseId": "LGPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://www.gnu.org/licenses/lgpl+gpl-3.0.txt", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPLLR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPLLR.json", + "referenceNumber": 268, + "name": "Lesser General Public License For Linguistic Resources", + "licenseId": "LGPLLR", + "seeAlso": [ + "http://www-igm.univ-mlv.fr/~unitex/lgpllr.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Libpng.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Libpng.json", + "referenceNumber": 484, + "name": "libpng License", + "licenseId": "Libpng", + "seeAlso": [ + "http://www.libpng.org/pub/png/src/libpng-LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libpng-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libpng-2.0.json", + "referenceNumber": 16, + "name": "PNG Reference Library version 2", + "licenseId": "libpng-2.0", + "seeAlso": [ + "http://www.libpng.org/pub/png/src/libpng-LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libselinux-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libselinux-1.0.json", + "referenceNumber": 67, + "name": "libselinux public domain notice", + "licenseId": "libselinux-1.0", + "seeAlso": [ + "https://github.com/SELinuxProject/selinux/blob/master/libselinux/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libtiff.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libtiff.json", + "referenceNumber": 466, + "name": "libtiff License", + "licenseId": "libtiff", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/libtiff" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libutil-David-Nugent.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libutil-David-Nugent.json", + "referenceNumber": 66, + "name": "libutil David Nugent License", + "licenseId": "libutil-David-Nugent", + "seeAlso": [ + "http://web.mit.edu/freebsd/head/lib/libutil/login_ok.3", + "https://cgit.freedesktop.org/libbsd/tree/man/setproctitle.3bsd" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-P-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-P-1.1.json", + "referenceNumber": 248, + "name": "Licence Libre du Québec – Permissive version 1.1", + "licenseId": "LiLiQ-P-1.1", + "seeAlso": [ + "https://forge.gouv.qc.ca/licence/fr/liliq-v1-1/", + "http://opensource.org/licenses/LiLiQ-P-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-R-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-R-1.1.json", + "referenceNumber": 71, + "name": "Licence Libre du Québec – Réciprocité version 1.1", + "licenseId": "LiLiQ-R-1.1", + "seeAlso": [ + "https://www.forge.gouv.qc.ca/participez/licence-logicielle/licence-libre-du-quebec-liliq-en-francais/licence-libre-du-quebec-reciprocite-liliq-r-v1-1/", + "http://opensource.org/licenses/LiLiQ-R-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-Rplus-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-Rplus-1.1.json", + "referenceNumber": 143, + "name": "Licence Libre du Québec – Réciprocité forte version 1.1", + "licenseId": "LiLiQ-Rplus-1.1", + "seeAlso": [ + "https://www.forge.gouv.qc.ca/participez/licence-logicielle/licence-libre-du-quebec-liliq-en-francais/licence-libre-du-quebec-reciprocite-forte-liliq-r-v1-1/", + "http://opensource.org/licenses/LiLiQ-Rplus-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Linux-man-pages-1-para.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-man-pages-1-para.json", + "referenceNumber": 279, + "name": "Linux man-pages - 1 paragraph", + "licenseId": "Linux-man-pages-1-para", + "seeAlso": [ + "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/getcpu.2#n4" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-man-pages-copyleft.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-man-pages-copyleft.json", + "referenceNumber": 286, + "name": "Linux man-pages Copyleft", + "licenseId": "Linux-man-pages-copyleft", + "seeAlso": [ + "https://www.kernel.org/doc/man-pages/licenses.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-man-pages-copyleft-2-para.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-man-pages-copyleft-2-para.json", + "referenceNumber": 136, + "name": "Linux man-pages Copyleft - 2 paragraphs", + "licenseId": "Linux-man-pages-copyleft-2-para", + "seeAlso": [ + "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/move_pages.2#n5", + "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/migrate_pages.2#n8" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-man-pages-copyleft-var.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-man-pages-copyleft-var.json", + "referenceNumber": 21, + "name": "Linux man-pages Copyleft Variant", + "licenseId": "Linux-man-pages-copyleft-var", + "seeAlso": [ + "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/set_mempolicy.2#n5" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-OpenIB.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-OpenIB.json", + "referenceNumber": 161, + "name": "Linux Kernel Variant of OpenIB.org license", + "licenseId": "Linux-OpenIB", + "seeAlso": [ + "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/infiniband/core/sa.h" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LOOP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LOOP.json", + "referenceNumber": 233, + "name": "Common Lisp LOOP License", + "licenseId": "LOOP", + "seeAlso": [ + "https://gitlab.com/embeddable-common-lisp/ecl/-/blob/develop/src/lsp/loop.lsp", + "http://git.savannah.gnu.org/cgit/gcl.git/tree/gcl/lsp/gcl_loop.lsp?h\u003dVersion_2_6_13pre", + "https://sourceforge.net/p/sbcl/sbcl/ci/master/tree/src/code/loop.lisp", + "https://github.com/cl-adams/adams/blob/master/LICENSE.md", + "https://github.com/blakemcbride/eclipse-lisp/blob/master/lisp/loop.lisp", + "https://gitlab.common-lisp.net/cmucl/cmucl/-/blob/master/src/code/loop.lisp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPD-document.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPD-document.json", + "referenceNumber": 294, + "name": "LPD Documentation License", + "licenseId": "LPD-document", + "seeAlso": [ + "https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md", + "https://www.ietf.org/rfc/rfc1952.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPL-1.0.json", + "referenceNumber": 390, + "name": "Lucent Public License Version 1.0", + "licenseId": "LPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/LPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LPL-1.02.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPL-1.02.json", + "referenceNumber": 459, + "name": "Lucent Public License v1.02", + "licenseId": "LPL-1.02", + "seeAlso": [ + "http://plan9.bell-labs.com/plan9/license.html", + "https://opensource.org/licenses/LPL-1.02" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.0.json", + "referenceNumber": 183, + "name": "LaTeX Project Public License v1.0", + "licenseId": "LPPL-1.0", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.1.json", + "referenceNumber": 408, + "name": "LaTeX Project Public License v1.1", + "licenseId": "LPPL-1.1", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.2.json", + "referenceNumber": 24, + "name": "LaTeX Project Public License v1.2", + "licenseId": "LPPL-1.2", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.3a.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.3a.json", + "referenceNumber": 366, + "name": "LaTeX Project Public License v1.3a", + "licenseId": "LPPL-1.3a", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-3a.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.3c.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.3c.json", + "referenceNumber": 396, + "name": "LaTeX Project Public License v1.3c", + "licenseId": "LPPL-1.3c", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-3c.txt", + "https://opensource.org/licenses/LPPL-1.3c" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/lsof.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/lsof.json", + "referenceNumber": 403, + "name": "lsof License", + "licenseId": "lsof", + "seeAlso": [ + "https://github.com/lsof-org/lsof/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Lucida-Bitmap-Fonts.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Lucida-Bitmap-Fonts.json", + "referenceNumber": 588, + "name": "Lucida Bitmap Fonts License", + "licenseId": "Lucida-Bitmap-Fonts", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/font/bh-100dpi/-/blob/master/COPYING?ref_type\u003dheads" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LZMA-SDK-9.11-to-9.20.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LZMA-SDK-9.11-to-9.20.json", + "referenceNumber": 539, + "name": "LZMA SDK License (versions 9.11 to 9.20)", + "licenseId": "LZMA-SDK-9.11-to-9.20", + "seeAlso": [ + "https://www.7-zip.org/sdk.html", + "https://sourceforge.net/projects/sevenzip/files/LZMA%20SDK/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LZMA-SDK-9.22.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LZMA-SDK-9.22.json", + "referenceNumber": 497, + "name": "LZMA SDK License (versions 9.22 and beyond)", + "licenseId": "LZMA-SDK-9.22", + "seeAlso": [ + "https://www.7-zip.org/sdk.html", + "https://sourceforge.net/projects/sevenzip/files/LZMA%20SDK/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/magaz.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/magaz.json", + "referenceNumber": 380, + "name": "magaz License", + "licenseId": "magaz", + "seeAlso": [ + "https://mirrors.nic.cz/tex-archive/macros/latex/contrib/magaz/magaz.tex" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/mailprio.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/mailprio.json", + "referenceNumber": 339, + "name": "mailprio License", + "licenseId": "mailprio", + "seeAlso": [ + "https://fossies.org/linux/sendmail/contrib/mailprio" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MakeIndex.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MakeIndex.json", + "referenceNumber": 404, + "name": "MakeIndex License", + "licenseId": "MakeIndex", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MakeIndex" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Martin-Birgmeier.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Martin-Birgmeier.json", + "referenceNumber": 2, + "name": "Martin Birgmeier License", + "licenseId": "Martin-Birgmeier", + "seeAlso": [ + "https://github.com/Perl/perl5/blob/blead/util.c#L6136" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/McPhee-slideshow.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/McPhee-slideshow.json", + "referenceNumber": 107, + "name": "McPhee Slideshow License", + "licenseId": "McPhee-slideshow", + "seeAlso": [ + "https://mirror.las.iastate.edu/tex-archive/graphics/metapost/contrib/macros/slideshow/slideshow.mp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/metamail.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/metamail.json", + "referenceNumber": 519, + "name": "metamail License", + "licenseId": "metamail", + "seeAlso": [ + "https://github.com/Dual-Life/mime-base64/blob/master/Base64.xs#L12" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Minpack.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Minpack.json", + "referenceNumber": 621, + "name": "Minpack License", + "licenseId": "Minpack", + "seeAlso": [ + "http://www.netlib.org/minpack/disclaimer", + "https://gitlab.com/libeigen/eigen/-/blob/master/COPYING.MINPACK" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MirOS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MirOS.json", + "referenceNumber": 516, + "name": "The MirOS Licence", + "licenseId": "MirOS", + "seeAlso": [ + "https://opensource.org/licenses/MirOS" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/MIT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT.json", + "referenceNumber": 292, + "name": "MIT License", + "licenseId": "MIT", + "seeAlso": [ + "https://opensource.org/license/mit/" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MIT-0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-0.json", + "referenceNumber": 83, + "name": "MIT No Attribution", + "licenseId": "MIT-0", + "seeAlso": [ + "https://github.com/aws/mit-0", + "https://romanrm.net/mit-zero", + "https://github.com/awsdocs/aws-cloud9-user-guide/blob/master/LICENSE-SAMPLECODE" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/MIT-advertising.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-advertising.json", + "referenceNumber": 317, + "name": "Enlightenment License (e16)", + "licenseId": "MIT-advertising", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT_With_Advertising" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-CMU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-CMU.json", + "referenceNumber": 382, + "name": "CMU License", + "licenseId": "MIT-CMU", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:MIT?rd\u003dLicensing/MIT#CMU_Style", + "https://github.com/python-pillow/Pillow/blob/fffb426092c8db24a5f4b6df243a8a3c01fb63cd/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-enna.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-enna.json", + "referenceNumber": 256, + "name": "enna License", + "licenseId": "MIT-enna", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#enna" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-feh.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-feh.json", + "referenceNumber": 231, + "name": "feh License", + "licenseId": "MIT-feh", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#feh" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-Festival.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-Festival.json", + "referenceNumber": 235, + "name": "MIT Festival Variant", + "licenseId": "MIT-Festival", + "seeAlso": [ + "https://github.com/festvox/flite/blob/master/COPYING", + "https://github.com/festvox/speech_tools/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-Modern-Variant.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-Modern-Variant.json", + "referenceNumber": 34, + "name": "MIT License Modern Variant", + "licenseId": "MIT-Modern-Variant", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:MIT#Modern_Variants", + "https://ptolemy.berkeley.edu/copyright.htm", + "https://pirlwww.lpl.arizona.edu/resources/guide/software/PerlTk/Tixlic.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/MIT-open-group.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-open-group.json", + "referenceNumber": 460, + "name": "MIT Open Group variant", + "licenseId": "MIT-open-group", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/app/iceauth/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xvinfo/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xsetroot/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xauth/-/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-testregex.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-testregex.json", + "referenceNumber": 32, + "name": "MIT testregex Variant", + "licenseId": "MIT-testregex", + "seeAlso": [ + "https://github.com/dotnet/runtime/blob/55e1ac7c07df62c4108d4acedf78f77574470ce5/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/AttRegexTests.cs#L12-L28" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-Wu.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-Wu.json", + "referenceNumber": 320, + "name": "MIT Tom Wu Variant", + "licenseId": "MIT-Wu", + "seeAlso": [ + "https://github.com/chromium/octane/blob/master/crypto.js" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MITNFA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MITNFA.json", + "referenceNumber": 534, + "name": "MIT +no-false-attribs license", + "licenseId": "MITNFA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MITNFA" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MMIXware.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MMIXware.json", + "referenceNumber": 197, + "name": "MMIXware License", + "licenseId": "MMIXware", + "seeAlso": [ + "https://gitlab.lrz.de/mmix/mmixware/-/blob/master/boilerplate.w" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Motosoto.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Motosoto.json", + "referenceNumber": 439, + "name": "Motosoto License", + "licenseId": "Motosoto", + "seeAlso": [ + "https://opensource.org/licenses/Motosoto" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/MPEG-SSG.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPEG-SSG.json", + "referenceNumber": 169, + "name": "MPEG Software Simulation", + "licenseId": "MPEG-SSG", + "seeAlso": [ + "https://sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/ppm/ppmtompeg/jrevdct.c#l1189" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/mpi-permissive.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/mpi-permissive.json", + "referenceNumber": 65, + "name": "mpi Permissive License", + "licenseId": "mpi-permissive", + "seeAlso": [ + "https://sources.debian.org/src/openmpi/4.1.0-10/ompi/debuggers/msgq_interface.h/?hl\u003d19#L19" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/mpich2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/mpich2.json", + "referenceNumber": 189, + "name": "mpich2 License", + "licenseId": "mpich2", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-1.0.json", + "referenceNumber": 127, + "name": "Mozilla Public License 1.0", + "licenseId": "MPL-1.0", + "seeAlso": [ + "http://www.mozilla.org/MPL/MPL-1.0.html", + "https://opensource.org/licenses/MPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/MPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-1.1.json", + "referenceNumber": 482, + "name": "Mozilla Public License 1.1", + "licenseId": "MPL-1.1", + "seeAlso": [ + "http://www.mozilla.org/MPL/MPL-1.1.html", + "https://opensource.org/licenses/MPL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-2.0.json", + "referenceNumber": 342, + "name": "Mozilla Public License 2.0", + "licenseId": "MPL-2.0", + "seeAlso": [ + "https://www.mozilla.org/MPL/2.0/", + "https://opensource.org/licenses/MPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MPL-2.0-no-copyleft-exception.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-2.0-no-copyleft-exception.json", + "referenceNumber": 463, + "name": "Mozilla Public License 2.0 (no copyleft exception)", + "licenseId": "MPL-2.0-no-copyleft-exception", + "seeAlso": [ + "https://www.mozilla.org/MPL/2.0/", + "https://opensource.org/licenses/MPL-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/mplus.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/mplus.json", + "referenceNumber": 436, + "name": "mplus Font License", + "licenseId": "mplus", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:Mplus?rd\u003dLicensing/mplus" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MS-LPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MS-LPL.json", + "referenceNumber": 550, + "name": "Microsoft Limited Public License", + "licenseId": "MS-LPL", + "seeAlso": [ + "https://www.openhub.net/licenses/mslpl", + "https://github.com/gabegundy/atlserver/blob/master/License.txt", + "https://en.wikipedia.org/wiki/Shared_Source_Initiative#Microsoft_Limited_Public_License_(Ms-LPL)" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MS-PL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MS-PL.json", + "referenceNumber": 215, + "name": "Microsoft Public License", + "licenseId": "MS-PL", + "seeAlso": [ + "http://www.microsoft.com/opensource/licenses.mspx", + "https://opensource.org/licenses/MS-PL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MS-RL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MS-RL.json", + "referenceNumber": 190, + "name": "Microsoft Reciprocal License", + "licenseId": "MS-RL", + "seeAlso": [ + "http://www.microsoft.com/opensource/licenses.mspx", + "https://opensource.org/licenses/MS-RL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MTLL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MTLL.json", + "referenceNumber": 26, + "name": "Matrix Template Library License", + "licenseId": "MTLL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Matrix_Template_Library_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MulanPSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MulanPSL-1.0.json", + "referenceNumber": 555, + "name": "Mulan Permissive Software License, Version 1", + "licenseId": "MulanPSL-1.0", + "seeAlso": [ + "https://license.coscl.org.cn/MulanPSL/", + "https://github.com/yuwenlong/longphp/blob/25dfb70cc2a466dc4bb55ba30901cbce08d164b5/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MulanPSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MulanPSL-2.0.json", + "referenceNumber": 376, + "name": "Mulan Permissive Software License, Version 2", + "licenseId": "MulanPSL-2.0", + "seeAlso": [ + "https://license.coscl.org.cn/MulanPSL2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Multics.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Multics.json", + "referenceNumber": 613, + "name": "Multics License", + "licenseId": "Multics", + "seeAlso": [ + "https://opensource.org/licenses/Multics" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Mup.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Mup.json", + "referenceNumber": 184, + "name": "Mup License", + "licenseId": "Mup", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Mup" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NAIST-2003.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NAIST-2003.json", + "referenceNumber": 473, + "name": "Nara Institute of Science and Technology License (2003)", + "licenseId": "NAIST-2003", + "seeAlso": [ + "https://enterprise.dejacode.com/licenses/public/naist-2003/#license-text", + "https://github.com/nodejs/node/blob/4a19cc8947b1bba2b2d27816ec3d0edf9b28e503/LICENSE#L343" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NASA-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NASA-1.3.json", + "referenceNumber": 328, + "name": "NASA Open Source Agreement 1.3", + "licenseId": "NASA-1.3", + "seeAlso": [ + "http://ti.arc.nasa.gov/opensource/nosa/", + "https://opensource.org/licenses/NASA-1.3" + ], + "isOsiApproved": true, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/Naumen.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Naumen.json", + "referenceNumber": 232, + "name": "Naumen Public License", + "licenseId": "Naumen", + "seeAlso": [ + "https://opensource.org/licenses/Naumen" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/NBPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NBPL-1.0.json", + "referenceNumber": 321, + "name": "Net Boolean Public License v1", + "licenseId": "NBPL-1.0", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d37b4b3f6cc4bf34e1d3dec61e69914b9819d8894" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NCGL-UK-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NCGL-UK-2.0.json", + "referenceNumber": 80, + "name": "Non-Commercial Government Licence", + "licenseId": "NCGL-UK-2.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/non-commercial-government-licence/version/2/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NCSA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NCSA.json", + "referenceNumber": 7, + "name": "University of Illinois/NCSA Open Source License", + "licenseId": "NCSA", + "seeAlso": [ + "http://otm.illinois.edu/uiuc_openSource", + "https://opensource.org/licenses/NCSA" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Net-SNMP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Net-SNMP.json", + "referenceNumber": 456, + "name": "Net-SNMP License", + "licenseId": "Net-SNMP", + "seeAlso": [ + "http://net-snmp.sourceforge.net/about/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NetCDF.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NetCDF.json", + "referenceNumber": 282, + "name": "NetCDF license", + "licenseId": "NetCDF", + "seeAlso": [ + "http://www.unidata.ucar.edu/software/netcdf/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Newsletr.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Newsletr.json", + "referenceNumber": 236, + "name": "Newsletr License", + "licenseId": "Newsletr", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Newsletr" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NGPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NGPL.json", + "referenceNumber": 536, + "name": "Nethack General Public License", + "licenseId": "NGPL", + "seeAlso": [ + "https://opensource.org/licenses/NGPL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/NICTA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NICTA-1.0.json", + "referenceNumber": 444, + "name": "NICTA Public Software License, Version 1.0", + "licenseId": "NICTA-1.0", + "seeAlso": [ + "https://opensource.apple.com/source/mDNSResponder/mDNSResponder-320.10/mDNSPosix/nss_ReadMe.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NIST-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NIST-PD.json", + "referenceNumber": 250, + "name": "NIST Public Domain Notice", + "licenseId": "NIST-PD", + "seeAlso": [ + "https://github.com/tcheneau/simpleRPL/blob/e645e69e38dd4e3ccfeceb2db8cba05b7c2e0cd3/LICENSE.txt", + "https://github.com/tcheneau/Routing/blob/f09f46fcfe636107f22f2c98348188a65a135d98/README.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NIST-PD-fallback.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NIST-PD-fallback.json", + "referenceNumber": 186, + "name": "NIST Public Domain Notice with license fallback", + "licenseId": "NIST-PD-fallback", + "seeAlso": [ + "https://github.com/usnistgov/jsip/blob/59700e6926cbe96c5cdae897d9a7d2656b42abe3/LICENSE", + "https://github.com/usnistgov/fipy/blob/86aaa5c2ba2c6f1be19593c5986071cf6568cc34/LICENSE.rst" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NIST-Software.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NIST-Software.json", + "referenceNumber": 33, + "name": "NIST Software License", + "licenseId": "NIST-Software", + "seeAlso": [ + "https://github.com/open-quantum-safe/liboqs/blob/40b01fdbb270f8614fde30e65d30e9da18c02393/src/common/rand/rand_nist.c#L1-L15" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NLOD-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NLOD-1.0.json", + "referenceNumber": 119, + "name": "Norwegian Licence for Open Government Data (NLOD) 1.0", + "licenseId": "NLOD-1.0", + "seeAlso": [ + "http://data.norge.no/nlod/en/1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NLOD-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NLOD-2.0.json", + "referenceNumber": 601, + "name": "Norwegian Licence for Open Government Data (NLOD) 2.0", + "licenseId": "NLOD-2.0", + "seeAlso": [ + "http://data.norge.no/nlod/en/2.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NLPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NLPL.json", + "referenceNumber": 594, + "name": "No Limit Public License", + "licenseId": "NLPL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/NLPL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Nokia.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Nokia.json", + "referenceNumber": 574, + "name": "Nokia Open Source License", + "licenseId": "Nokia", + "seeAlso": [ + "https://opensource.org/licenses/nokia" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NOSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NOSL.json", + "referenceNumber": 160, + "name": "Netizen Open Source License", + "licenseId": "NOSL", + "seeAlso": [ + "http://bits.netizen.com.au/licenses/NOSL/nosl.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Noweb.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Noweb.json", + "referenceNumber": 180, + "name": "Noweb License", + "licenseId": "Noweb", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Noweb" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPL-1.0.json", + "referenceNumber": 388, + "name": "Netscape Public License v1.0", + "licenseId": "NPL-1.0", + "seeAlso": [ + "http://www.mozilla.org/MPL/NPL/1.0/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPL-1.1.json", + "referenceNumber": 598, + "name": "Netscape Public License v1.1", + "licenseId": "NPL-1.1", + "seeAlso": [ + "http://www.mozilla.org/MPL/NPL/1.1/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NPOSL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPOSL-3.0.json", + "referenceNumber": 620, + "name": "Non-Profit Open Software License 3.0", + "licenseId": "NPOSL-3.0", + "seeAlso": [ + "https://opensource.org/licenses/NOSL3.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/NRL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NRL.json", + "referenceNumber": 55, + "name": "NRL License", + "licenseId": "NRL", + "seeAlso": [ + "http://web.mit.edu/network/isakmp/nrllicense.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NTP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NTP.json", + "referenceNumber": 19, + "name": "NTP License", + "licenseId": "NTP", + "seeAlso": [ + "https://opensource.org/licenses/NTP" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/NTP-0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NTP-0.json", + "referenceNumber": 25, + "name": "NTP No Attribution", + "licenseId": "NTP-0", + "seeAlso": [ + "https://github.com/tytso/e2fsprogs/blob/master/lib/et/et_name.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Nunit.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/Nunit.json", + "referenceNumber": 589, + "name": "Nunit License", + "licenseId": "Nunit", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Nunit" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/O-UDA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/O-UDA-1.0.json", + "referenceNumber": 238, + "name": "Open Use of Data Agreement v1.0", + "licenseId": "O-UDA-1.0", + "seeAlso": [ + "https://github.com/microsoft/Open-Use-of-Data-Agreement/blob/v1.0/O-UDA-1.0.md", + "https://cdla.dev/open-use-of-data-agreement-v1-0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OCCT-PL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OCCT-PL.json", + "referenceNumber": 149, + "name": "Open CASCADE Technology Public License", + "licenseId": "OCCT-PL", + "seeAlso": [ + "http://www.opencascade.com/content/occt-public-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OCLC-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OCLC-2.0.json", + "referenceNumber": 417, + "name": "OCLC Research Public License 2.0", + "licenseId": "OCLC-2.0", + "seeAlso": [ + "http://www.oclc.org/research/activities/software/license/v2final.htm", + "https://opensource.org/licenses/OCLC-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ODbL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ODbL-1.0.json", + "referenceNumber": 105, + "name": "Open Data Commons Open Database License v1.0", + "licenseId": "ODbL-1.0", + "seeAlso": [ + "http://www.opendatacommons.org/licenses/odbl/1.0/", + "https://opendatacommons.org/licenses/odbl/1-0/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ODC-By-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ODC-By-1.0.json", + "referenceNumber": 493, + "name": "Open Data Commons Attribution License v1.0", + "licenseId": "ODC-By-1.0", + "seeAlso": [ + "https://opendatacommons.org/licenses/by/1.0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFFIS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFFIS.json", + "referenceNumber": 84, + "name": "OFFIS License", + "licenseId": "OFFIS", + "seeAlso": [ + "https://sourceforge.net/p/xmedcon/code/ci/master/tree/libs/dicom/README" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0.json", + "referenceNumber": 56, + "name": "SIL Open Font License 1.0", + "licenseId": "OFL-1.0", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0-no-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0-no-RFN.json", + "referenceNumber": 394, + "name": "SIL Open Font License 1.0 with no Reserved Font Name", + "licenseId": "OFL-1.0-no-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0-RFN.json", + "referenceNumber": 532, + "name": "SIL Open Font License 1.0 with Reserved Font Name", + "licenseId": "OFL-1.0-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1.json", + "referenceNumber": 337, + "name": "SIL Open Font License 1.1", + "licenseId": "OFL-1.1", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1-no-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1-no-RFN.json", + "referenceNumber": 507, + "name": "SIL Open Font License 1.1 with no Reserved Font Name", + "licenseId": "OFL-1.1-no-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1-RFN.json", + "referenceNumber": 578, + "name": "SIL Open Font License 1.1 with Reserved Font Name", + "licenseId": "OFL-1.1-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OGC-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGC-1.0.json", + "referenceNumber": 543, + "name": "OGC Software License, Version 1.0", + "licenseId": "OGC-1.0", + "seeAlso": [ + "https://www.ogc.org/ogc/software/1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGDL-Taiwan-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGDL-Taiwan-1.0.json", + "referenceNumber": 23, + "name": "Taiwan Open Government Data License, version 1.0", + "licenseId": "OGDL-Taiwan-1.0", + "seeAlso": [ + "https://data.gov.tw/license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-Canada-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-Canada-2.0.json", + "referenceNumber": 210, + "name": "Open Government Licence - Canada", + "licenseId": "OGL-Canada-2.0", + "seeAlso": [ + "https://open.canada.ca/en/open-government-licence-canada" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-1.0.json", + "referenceNumber": 205, + "name": "Open Government Licence v1.0", + "licenseId": "OGL-UK-1.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/1/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-2.0.json", + "referenceNumber": 386, + "name": "Open Government Licence v2.0", + "licenseId": "OGL-UK-2.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/2/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-3.0.json", + "referenceNumber": 415, + "name": "Open Government Licence v3.0", + "licenseId": "OGL-UK-3.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGTSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGTSL.json", + "referenceNumber": 544, + "name": "Open Group Test Suite License", + "licenseId": "OGTSL", + "seeAlso": [ + "http://www.opengroup.org/testing/downloads/The_Open_Group_TSL.txt", + "https://opensource.org/licenses/OGTSL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.1.json", + "referenceNumber": 377, + "name": "Open LDAP Public License v1.1", + "licenseId": "OLDAP-1.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d806557a5ad59804ef3a44d5abfbe91d706b0791f" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.2.json", + "referenceNumber": 548, + "name": "Open LDAP Public License v1.2", + "licenseId": "OLDAP-1.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d42b0383c50c299977b5893ee695cf4e486fb0dc7" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.3.json", + "referenceNumber": 455, + "name": "Open LDAP Public License v1.3", + "licenseId": "OLDAP-1.3", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003de5f8117f0ce088d0bd7a8e18ddf37eaa40eb09b1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.4.json", + "referenceNumber": 38, + "name": "Open LDAP Public License v1.4", + "licenseId": "OLDAP-1.4", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dc9f95c2f3f2ffb5e0ae55fe7388af75547660941" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.0.json", + "referenceNumber": 234, + "name": "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", + "licenseId": "OLDAP-2.0", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dcbf50f4e1185a21abd4c0a54d3f4341fe28f36ea" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.0.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.0.1.json", + "referenceNumber": 397, + "name": "Open LDAP Public License v2.0.1", + "licenseId": "OLDAP-2.0.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003db6d68acd14e51ca3aab4428bf26522aa74873f0e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.1.json", + "referenceNumber": 39, + "name": "Open LDAP Public License v2.1", + "licenseId": "OLDAP-2.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003db0d176738e96a0d3b9f85cb51e140a86f21be715" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.json", + "referenceNumber": 142, + "name": "Open LDAP Public License v2.2", + "licenseId": "OLDAP-2.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d470b0c18ec67621c85881b2733057fecf4a1acc3" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.1.json", + "referenceNumber": 583, + "name": "Open LDAP Public License v2.2.1", + "licenseId": "OLDAP-2.2.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d4bc786f34b50aa301be6f5600f58a980070f481e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.2.json", + "referenceNumber": 153, + "name": "Open LDAP Public License 2.2.2", + "licenseId": "OLDAP-2.2.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003ddf2cc1e21eb7c160695f5b7cffd6296c151ba188" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.3.json", + "referenceNumber": 590, + "name": "Open LDAP Public License v2.3", + "licenseId": "OLDAP-2.3", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dd32cf54a32d581ab475d23c810b0a7fbaf8d63c3" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.4.json", + "referenceNumber": 584, + "name": "Open LDAP Public License v2.4", + "licenseId": "OLDAP-2.4", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dcd1284c4a91a8a380d904eee68d1583f989ed386" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.5.json", + "referenceNumber": 204, + "name": "Open LDAP Public License v2.5", + "licenseId": "OLDAP-2.5", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d6852b9d90022e8593c98205413380536b1b5a7cf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.6.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.6.json", + "referenceNumber": 604, + "name": "Open LDAP Public License v2.6", + "licenseId": "OLDAP-2.6", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d1cae062821881f41b73012ba816434897abf4205" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.7.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.7.json", + "referenceNumber": 125, + "name": "Open LDAP Public License v2.7", + "licenseId": "OLDAP-2.7", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d47c2415c1df81556eeb39be6cad458ef87c534a2" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.8.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.8.json", + "referenceNumber": 528, + "name": "Open LDAP Public License v2.8", + "licenseId": "OLDAP-2.8", + "seeAlso": [ + "http://www.openldap.org/software/release/license.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLFL-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLFL-1.3.json", + "referenceNumber": 63, + "name": "Open Logistics Foundation License Version 1.3", + "licenseId": "OLFL-1.3", + "seeAlso": [ + "https://openlogisticsfoundation.org/licenses/", + "https://opensource.org/license/olfl-1-3/" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OML.json", + "referenceNumber": 199, + "name": "Open Market License", + "licenseId": "OML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Open_Market_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OpenPBS-2.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OpenPBS-2.3.json", + "referenceNumber": 45, + "name": "OpenPBS v2.3 Software License", + "licenseId": "OpenPBS-2.3", + "seeAlso": [ + "https://github.com/adaptivecomputing/torque/blob/master/PBS_License.txt", + "https://www.mcs.anl.gov/research/projects/openpbs/PBS_License.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OpenSSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OpenSSL.json", + "referenceNumber": 508, + "name": "OpenSSL License", + "licenseId": "OpenSSL", + "seeAlso": [ + "http://www.openssl.org/source/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OpenSSL-standalone.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OpenSSL-standalone.json", + "referenceNumber": 542, + "name": "OpenSSL License - standalone", + "licenseId": "OpenSSL-standalone", + "seeAlso": [ + "https://library.netapp.com/ecm/ecm_download_file/ECMP1196395", + "https://hstechdocs.helpsystems.com/manuals/globalscape/archive/cuteftp6/open_ssl_license_agreement.htm" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OPL-1.0.json", + "referenceNumber": 411, + "name": "Open Public License v1.0", + "licenseId": "OPL-1.0", + "seeAlso": [ + "http://old.koalateam.com/jackaroo/OPL_1_0.TXT", + "https://fedoraproject.org/wiki/Licensing/Open_Public_License" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/OPL-UK-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OPL-UK-3.0.json", + "referenceNumber": 378, + "name": "United Kingdom Open Parliament Licence v3.0", + "licenseId": "OPL-UK-3.0", + "seeAlso": [ + "https://www.parliament.uk/site-information/copyright-parliament/open-parliament-licence/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OPUBL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OPUBL-1.0.json", + "referenceNumber": 479, + "name": "Open Publication License v1.0", + "licenseId": "OPUBL-1.0", + "seeAlso": [ + "http://opencontent.org/openpub/", + "https://www.debian.org/opl", + "https://www.ctan.org/license/opl" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSET-PL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSET-PL-2.1.json", + "referenceNumber": 244, + "name": "OSET Public License version 2.1", + "licenseId": "OSET-PL-2.1", + "seeAlso": [ + "http://www.osetfoundation.org/public-license", + "https://opensource.org/licenses/OPL-2.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-1.0.json", + "referenceNumber": 281, + "name": "Open Software License 1.0", + "licenseId": "OSL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/OSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-1.1.json", + "referenceNumber": 146, + "name": "Open Software License 1.1", + "licenseId": "OSL-1.1", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/OSL1.1" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-2.0.json", + "referenceNumber": 290, + "name": "Open Software License 2.0", + "licenseId": "OSL-2.0", + "seeAlso": [ + "http://web.archive.org/web/20041020171434/http://www.rosenlaw.com/osl2.0.html" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OSL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-2.1.json", + "referenceNumber": 375, + "name": "Open Software License 2.1", + "licenseId": "OSL-2.1", + "seeAlso": [ + "http://web.archive.org/web/20050212003940/http://www.rosenlaw.com/osl21.htm", + "https://opensource.org/licenses/OSL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OSL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-3.0.json", + "referenceNumber": 122, + "name": "Open Software License 3.0", + "licenseId": "OSL-3.0", + "seeAlso": [ + "https://web.archive.org/web/20120101081418/http://rosenlaw.com:80/OSL3.0.htm", + "https://opensource.org/licenses/OSL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/PADL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PADL.json", + "referenceNumber": 347, + "name": "PADL License", + "licenseId": "PADL", + "seeAlso": [ + "https://git.openldap.org/openldap/openldap/-/blob/master/libraries/libldap/os-local.c?ref_type\u003dheads#L19-23" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Parity-6.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Parity-6.0.0.json", + "referenceNumber": 241, + "name": "The Parity Public License 6.0.0", + "licenseId": "Parity-6.0.0", + "seeAlso": [ + "https://paritylicense.com/versions/6.0.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Parity-7.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Parity-7.0.0.json", + "referenceNumber": 440, + "name": "The Parity Public License 7.0.0", + "licenseId": "Parity-7.0.0", + "seeAlso": [ + "https://paritylicense.com/versions/7.0.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PDDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PDDL-1.0.json", + "referenceNumber": 208, + "name": "Open Data Commons Public Domain Dedication \u0026 License 1.0", + "licenseId": "PDDL-1.0", + "seeAlso": [ + "http://opendatacommons.org/licenses/pddl/1.0/", + "https://opendatacommons.org/licenses/pddl/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PHP-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PHP-3.0.json", + "referenceNumber": 334, + "name": "PHP License v3.0", + "licenseId": "PHP-3.0", + "seeAlso": [ + "http://www.php.net/license/3_0.txt", + "https://opensource.org/licenses/PHP-3.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/PHP-3.01.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PHP-3.01.json", + "referenceNumber": 447, + "name": "PHP License v3.01", + "licenseId": "PHP-3.01", + "seeAlso": [ + "http://www.php.net/license/3_01.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Pixar.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Pixar.json", + "referenceNumber": 520, + "name": "Pixar License", + "licenseId": "Pixar", + "seeAlso": [ + "https://github.com/PixarAnimationStudios/OpenSubdiv/raw/v3_5_0/LICENSE.txt", + "https://graphics.pixar.com/opensubdiv/docs/license.html", + "https://github.com/PixarAnimationStudios/OpenSubdiv/blob/v3_5_0/opensubdiv/version.cpp#L2-L22" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Plexus.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Plexus.json", + "referenceNumber": 135, + "name": "Plexus Classworlds License", + "licenseId": "Plexus", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Plexus_Classworlds_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/pnmstitch.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/pnmstitch.json", + "referenceNumber": 563, + "name": "pnmstitch License", + "licenseId": "pnmstitch", + "seeAlso": [ + "https://sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/editor/pnmstitch.c#l2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PolyForm-Noncommercial-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PolyForm-Noncommercial-1.0.0.json", + "referenceNumber": 297, + "name": "PolyForm Noncommercial License 1.0.0", + "licenseId": "PolyForm-Noncommercial-1.0.0", + "seeAlso": [ + "https://polyformproject.org/licenses/noncommercial/1.0.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PolyForm-Small-Business-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PolyForm-Small-Business-1.0.0.json", + "referenceNumber": 576, + "name": "PolyForm Small Business License 1.0.0", + "licenseId": "PolyForm-Small-Business-1.0.0", + "seeAlso": [ + "https://polyformproject.org/licenses/small-business/1.0.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PostgreSQL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PostgreSQL.json", + "referenceNumber": 443, + "name": "PostgreSQL License", + "licenseId": "PostgreSQL", + "seeAlso": [ + "http://www.postgresql.org/about/licence", + "https://opensource.org/licenses/PostgreSQL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/PSF-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PSF-2.0.json", + "referenceNumber": 510, + "name": "Python Software Foundation License 2.0", + "licenseId": "PSF-2.0", + "seeAlso": [ + "https://opensource.org/licenses/Python-2.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/psfrag.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/psfrag.json", + "referenceNumber": 99, + "name": "psfrag License", + "licenseId": "psfrag", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/psfrag" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/psutils.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/psutils.json", + "referenceNumber": 242, + "name": "psutils License", + "licenseId": "psutils", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/psutils" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Python-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Python-2.0.json", + "referenceNumber": 57, + "name": "Python License 2.0", + "licenseId": "Python-2.0", + "seeAlso": [ + "https://opensource.org/licenses/Python-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Python-2.0.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Python-2.0.1.json", + "referenceNumber": 298, + "name": "Python License 2.0.1", + "licenseId": "Python-2.0.1", + "seeAlso": [ + "https://www.python.org/download/releases/2.0.1/license/", + "https://docs.python.org/3/license.html", + "https://github.com/python/cpython/blob/main/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/python-ldap.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/python-ldap.json", + "referenceNumber": 193, + "name": "Python ldap License", + "licenseId": "python-ldap", + "seeAlso": [ + "https://github.com/python-ldap/python-ldap/blob/main/LICENCE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Qhull.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Qhull.json", + "referenceNumber": 577, + "name": "Qhull License", + "licenseId": "Qhull", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Qhull" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/QPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/QPL-1.0.json", + "referenceNumber": 240, + "name": "Q Public License 1.0", + "licenseId": "QPL-1.0", + "seeAlso": [ + "http://doc.qt.nokia.com/3.3/license.html", + "https://opensource.org/licenses/QPL-1.0", + "https://doc.qt.io/archives/3.3/license.html" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/QPL-1.0-INRIA-2004.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/QPL-1.0-INRIA-2004.json", + "referenceNumber": 431, + "name": "Q Public License 1.0 - INRIA 2004 variant", + "licenseId": "QPL-1.0-INRIA-2004", + "seeAlso": [ + "https://github.com/maranget/hevea/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/radvd.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/radvd.json", + "referenceNumber": 255, + "name": "radvd License", + "licenseId": "radvd", + "seeAlso": [ + "https://github.com/radvd-project/radvd/blob/master/COPYRIGHT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Rdisc.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Rdisc.json", + "referenceNumber": 348, + "name": "Rdisc License", + "licenseId": "Rdisc", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Rdisc_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RHeCos-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RHeCos-1.1.json", + "referenceNumber": 470, + "name": "Red Hat eCos Public License v1.1", + "licenseId": "RHeCos-1.1", + "seeAlso": [ + "http://ecos.sourceware.org/old-license.html" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/RPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPL-1.1.json", + "referenceNumber": 312, + "name": "Reciprocal Public License 1.1", + "licenseId": "RPL-1.1", + "seeAlso": [ + "https://opensource.org/licenses/RPL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/RPL-1.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPL-1.5.json", + "referenceNumber": 117, + "name": "Reciprocal Public License 1.5", + "licenseId": "RPL-1.5", + "seeAlso": [ + "https://opensource.org/licenses/RPL-1.5" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/RPSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPSL-1.0.json", + "referenceNumber": 512, + "name": "RealNetworks Public Source License v1.0", + "licenseId": "RPSL-1.0", + "seeAlso": [ + "https://helixcommunity.org/content/rpsl", + "https://opensource.org/licenses/RPSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/RSA-MD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RSA-MD.json", + "referenceNumber": 96, + "name": "RSA Message-Digest License", + "licenseId": "RSA-MD", + "seeAlso": [ + "http://www.faqs.org/rfcs/rfc1321.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RSCPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RSCPL.json", + "referenceNumber": 341, + "name": "Ricoh Source Code Public License", + "licenseId": "RSCPL", + "seeAlso": [ + "http://wayback.archive.org/web/20060715140826/http://www.risource.org/RPL/RPL-1.0A.shtml", + "https://opensource.org/licenses/RSCPL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Ruby.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Ruby.json", + "referenceNumber": 602, + "name": "Ruby License", + "licenseId": "Ruby", + "seeAlso": [ + "https://www.ruby-lang.org/en/about/license.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SAX-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SAX-PD.json", + "referenceNumber": 178, + "name": "Sax Public Domain Notice", + "licenseId": "SAX-PD", + "seeAlso": [ + "http://www.saxproject.org/copying.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SAX-PD-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SAX-PD-2.0.json", + "referenceNumber": 567, + "name": "Sax Public Domain Notice 2.0", + "licenseId": "SAX-PD-2.0", + "seeAlso": [ + "http://www.saxproject.org/copying.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Saxpath.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Saxpath.json", + "referenceNumber": 14, + "name": "Saxpath License", + "licenseId": "Saxpath", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Saxpath_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SCEA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SCEA.json", + "referenceNumber": 429, + "name": "SCEA Shared Source License", + "licenseId": "SCEA", + "seeAlso": [ + "http://research.scea.com/scea_shared_source_license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SchemeReport.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SchemeReport.json", + "referenceNumber": 263, + "name": "Scheme Language Report License", + "licenseId": "SchemeReport", + "seeAlso": [], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Sendmail.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sendmail.json", + "referenceNumber": 581, + "name": "Sendmail License", + "licenseId": "Sendmail", + "seeAlso": [ + "http://www.sendmail.com/pdfs/open_source/sendmail_license.pdf", + "https://web.archive.org/web/20160322142305/https://www.sendmail.com/pdfs/open_source/sendmail_license.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Sendmail-8.23.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sendmail-8.23.json", + "referenceNumber": 133, + "name": "Sendmail License 8.23", + "licenseId": "Sendmail-8.23", + "seeAlso": [ + "https://www.proofpoint.com/sites/default/files/sendmail-license.pdf", + "https://web.archive.org/web/20181003101040/https://www.proofpoint.com/sites/default/files/sendmail-license.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGI-B-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-1.0.json", + "referenceNumber": 187, + "name": "SGI Free Software License B v1.0", + "licenseId": "SGI-B-1.0", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/SGIFreeSWLicB.1.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGI-B-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-1.1.json", + "referenceNumber": 147, + "name": "SGI Free Software License B v1.1", + "licenseId": "SGI-B-1.1", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGI-B-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-2.0.json", + "referenceNumber": 285, + "name": "SGI Free Software License B v2.0", + "licenseId": "SGI-B-2.0", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/SGIFreeSWLicB.2.0.pdf" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SGI-OpenGL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-OpenGL.json", + "referenceNumber": 207, + "name": "SGI OpenGL License", + "licenseId": "SGI-OpenGL", + "seeAlso": [ + "https://gitlab.freedesktop.org/mesa/glw/-/blob/master/README?ref_type\u003dheads" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGP4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGP4.json", + "referenceNumber": 467, + "name": "SGP4 Permission Notice", + "licenseId": "SGP4", + "seeAlso": [ + "https://celestrak.org/publications/AIAA/2006-6753/faq.php" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SHL-0.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SHL-0.5.json", + "referenceNumber": 568, + "name": "Solderpad Hardware License v0.5", + "licenseId": "SHL-0.5", + "seeAlso": [ + "https://solderpad.org/licenses/SHL-0.5/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SHL-0.51.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SHL-0.51.json", + "referenceNumber": 150, + "name": "Solderpad Hardware License, Version 0.51", + "licenseId": "SHL-0.51", + "seeAlso": [ + "https://solderpad.org/licenses/SHL-0.51/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SimPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SimPL-2.0.json", + "referenceNumber": 412, + "name": "Simple Public License 2.0", + "licenseId": "SimPL-2.0", + "seeAlso": [ + "https://opensource.org/licenses/SimPL-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/SISSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SISSL.json", + "referenceNumber": 201, + "name": "Sun Industry Standards Source License v1.1", + "licenseId": "SISSL", + "seeAlso": [ + "http://www.openoffice.org/licenses/sissl_license.html", + "https://opensource.org/licenses/SISSL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SISSL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SISSL-1.2.json", + "referenceNumber": 546, + "name": "Sun Industry Standards Source License v1.2", + "licenseId": "SISSL-1.2", + "seeAlso": [ + "http://gridscheduler.sourceforge.net/Gridengine_SISSL_license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SL.json", + "referenceNumber": 131, + "name": "SL License", + "licenseId": "SL", + "seeAlso": [ + "https://github.com/mtoyoda/sl/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Sleepycat.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sleepycat.json", + "referenceNumber": 333, + "name": "Sleepycat License", + "licenseId": "Sleepycat", + "seeAlso": [ + "https://opensource.org/licenses/Sleepycat" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SMLNJ.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SMLNJ.json", + "referenceNumber": 95, + "name": "Standard ML of New Jersey License", + "licenseId": "SMLNJ", + "seeAlso": [ + "https://www.smlnj.org/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SMPPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SMPPL.json", + "referenceNumber": 517, + "name": "Secure Messaging Protocol Public License", + "licenseId": "SMPPL", + "seeAlso": [ + "https://github.com/dcblake/SMP/blob/master/Documentation/License.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SNIA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SNIA.json", + "referenceNumber": 480, + "name": "SNIA Public License 1.1", + "licenseId": "SNIA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/SNIA_Public_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/snprintf.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/snprintf.json", + "referenceNumber": 43, + "name": "snprintf License", + "licenseId": "snprintf", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/master/openbsd-compat/bsd-snprintf.c#L2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Soundex.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Soundex.json", + "referenceNumber": 338, + "name": "Soundex License", + "licenseId": "Soundex", + "seeAlso": [ + "https://metacpan.org/release/RJBS/Text-Soundex-3.05/source/Soundex.pm#L3-11" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Spencer-86.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-86.json", + "referenceNumber": 461, + "name": "Spencer License 86", + "licenseId": "Spencer-86", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Henry_Spencer_Reg-Ex_Library_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Spencer-94.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-94.json", + "referenceNumber": 413, + "name": "Spencer License 94", + "licenseId": "Spencer-94", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Henry_Spencer_Reg-Ex_Library_License", + "https://metacpan.org/release/KNOK/File-MMagic-1.30/source/COPYING#L28" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Spencer-99.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-99.json", + "referenceNumber": 582, + "name": "Spencer License 99", + "licenseId": "Spencer-99", + "seeAlso": [ + "http://www.opensource.apple.com/source/tcl/tcl-5/tcl/generic/regfronts.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SPL-1.0.json", + "referenceNumber": 359, + "name": "Sun Public License v1.0", + "licenseId": "SPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/SPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ssh-keyscan.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ssh-keyscan.json", + "referenceNumber": 483, + "name": "ssh-keyscan License", + "licenseId": "ssh-keyscan", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/master/LICENCE#L82" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SSH-OpenSSH.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSH-OpenSSH.json", + "referenceNumber": 537, + "name": "SSH OpenSSH license", + "licenseId": "SSH-OpenSSH", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/1b11ea7c58cd5c59838b5fa574cd456d6047b2d4/LICENCE#L10" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SSH-short.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSH-short.json", + "referenceNumber": 451, + "name": "SSH short notice", + "licenseId": "SSH-short", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/1b11ea7c58cd5c59838b5fa574cd456d6047b2d4/pathnames.h", + "http://web.mit.edu/kolya/.f/root/athena.mit.edu/sipb.mit.edu/project/openssh/OldFiles/src/openssh-2.9.9p2/ssh-add.1", + "https://joinup.ec.europa.eu/svn/lesoll/trunk/italc/lib/src/dsa_key.cpp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SSLeay-standalone.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSLeay-standalone.json", + "referenceNumber": 607, + "name": "SSLeay License - standalone", + "licenseId": "SSLeay-standalone", + "seeAlso": [ + "https://www.tq-group.com/filedownloads/files/software-license-conditions/OriginalSSLeay/OriginalSSLeay.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SSPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSPL-1.0.json", + "referenceNumber": 599, + "name": "Server Side Public License, v 1", + "licenseId": "SSPL-1.0", + "seeAlso": [ + "https://www.mongodb.com/licensing/server-side-public-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/StandardML-NJ.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/StandardML-NJ.json", + "referenceNumber": 622, + "name": "Standard ML of New Jersey License", + "licenseId": "StandardML-NJ", + "seeAlso": [ + "https://www.smlnj.org/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SugarCRM-1.1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SugarCRM-1.1.3.json", + "referenceNumber": 280, + "name": "SugarCRM Public License v1.1.3", + "licenseId": "SugarCRM-1.1.3", + "seeAlso": [ + "http://www.sugarcrm.com/crm/SPL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SunPro.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SunPro.json", + "referenceNumber": 49, + "name": "SunPro License", + "licenseId": "SunPro", + "seeAlso": [ + "https://github.com/freebsd/freebsd-src/blob/main/lib/msun/src/e_acosh.c", + "https://github.com/freebsd/freebsd-src/blob/main/lib/msun/src/e_lgammal.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SWL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SWL.json", + "referenceNumber": 407, + "name": "Scheme Widget Library (SWL) Software License Agreement", + "licenseId": "SWL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/SWL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/swrule.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/swrule.json", + "referenceNumber": 220, + "name": "swrule License", + "licenseId": "swrule", + "seeAlso": [ + "https://ctan.math.utah.edu/ctan/tex-archive/macros/generic/misc/swrule.sty" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Symlinks.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Symlinks.json", + "referenceNumber": 213, + "name": "Symlinks License", + "licenseId": "Symlinks", + "seeAlso": [ + "https://www.mail-archive.com/debian-bugs-rc@lists.debian.org/msg11494.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TAPR-OHL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TAPR-OHL-1.0.json", + "referenceNumber": 401, + "name": "TAPR Open Hardware License v1.0", + "licenseId": "TAPR-OHL-1.0", + "seeAlso": [ + "https://www.tapr.org/OHL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TCL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TCL.json", + "referenceNumber": 166, + "name": "TCL/TK License", + "licenseId": "TCL", + "seeAlso": [ + "http://www.tcl.tk/software/tcltk/license.html", + "https://fedoraproject.org/wiki/Licensing/TCL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TCP-wrappers.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TCP-wrappers.json", + "referenceNumber": 0, + "name": "TCP Wrappers License", + "licenseId": "TCP-wrappers", + "seeAlso": [ + "http://rc.quest.com/topics/openssh/license.php#tcpwrappers" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TermReadKey.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TermReadKey.json", + "referenceNumber": 214, + "name": "TermReadKey License", + "licenseId": "TermReadKey", + "seeAlso": [ + "https://github.com/jonathanstowe/TermReadKey/blob/master/README#L9-L10" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TGPPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TGPPL-1.0.json", + "referenceNumber": 185, + "name": "Transitive Grace Period Public Licence 1.0", + "licenseId": "TGPPL-1.0", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TGPPL", + "https://tahoe-lafs.org/trac/tahoe-lafs/browser/trunk/COPYING.TGPPL.rst" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TMate.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TMate.json", + "referenceNumber": 591, + "name": "TMate Open Source License", + "licenseId": "TMate", + "seeAlso": [ + "http://svnkit.com/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TORQUE-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TORQUE-1.1.json", + "referenceNumber": 422, + "name": "TORQUE v2.5+ Software License v1.1", + "licenseId": "TORQUE-1.1", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TORQUEv1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TOSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TOSL.json", + "referenceNumber": 12, + "name": "Trusster Open Source License", + "licenseId": "TOSL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TOSL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TPDL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TPDL.json", + "referenceNumber": 575, + "name": "Time::ParseDate License", + "licenseId": "TPDL", + "seeAlso": [ + "https://metacpan.org/pod/Time::ParseDate#LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TPL-1.0.json", + "referenceNumber": 565, + "name": "THOR Public License 1.0", + "licenseId": "TPL-1.0", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:ThorPublicLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TTWL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TTWL.json", + "referenceNumber": 111, + "name": "Text-Tabs+Wrap License", + "licenseId": "TTWL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TTWL", + "https://github.com/ap/Text-Tabs/blob/master/lib.modern/Text/Tabs.pm#L148" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TTYP0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TTYP0.json", + "referenceNumber": 225, + "name": "TTYP0 License", + "licenseId": "TTYP0", + "seeAlso": [ + "https://people.mpi-inf.mpg.de/~uwe/misc/uw-ttyp0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TU-Berlin-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TU-Berlin-1.0.json", + "referenceNumber": 362, + "name": "Technische Universitaet Berlin License 1.0", + "licenseId": "TU-Berlin-1.0", + "seeAlso": [ + "https://github.com/swh/ladspa/blob/7bf6f3799fdba70fda297c2d8fd9f526803d9680/gsm/COPYRIGHT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TU-Berlin-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TU-Berlin-2.0.json", + "referenceNumber": 261, + "name": "Technische Universitaet Berlin License 2.0", + "licenseId": "TU-Berlin-2.0", + "seeAlso": [ + "https://github.com/CorsixTH/deps/blob/fd339a9f526d1d9c9f01ccf39e438a015da50035/licences/libgsm.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/UCAR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UCAR.json", + "referenceNumber": 93, + "name": "UCAR License", + "licenseId": "UCAR", + "seeAlso": [ + "https://github.com/Unidata/UDUNITS-2/blob/master/COPYRIGHT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/UCL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UCL-1.0.json", + "referenceNumber": 612, + "name": "Upstream Compatibility License v1.0", + "licenseId": "UCL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/UCL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ulem.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ulem.json", + "referenceNumber": 481, + "name": "ulem License", + "licenseId": "ulem", + "seeAlso": [ + "https://mirrors.ctan.org/macros/latex/contrib/ulem/README" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Unicode-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-3.0.json", + "referenceNumber": 203, + "name": "Unicode License v3", + "licenseId": "Unicode-3.0", + "seeAlso": [ + "https://www.unicode.org/license.txt" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Unicode-DFS-2015.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-DFS-2015.json", + "referenceNumber": 273, + "name": "Unicode License Agreement - Data Files and Software (2015)", + "licenseId": "Unicode-DFS-2015", + "seeAlso": [ + "https://web.archive.org/web/20151224134844/http://unicode.org/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Unicode-DFS-2016.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-DFS-2016.json", + "referenceNumber": 477, + "name": "Unicode License Agreement - Data Files and Software (2016)", + "licenseId": "Unicode-DFS-2016", + "seeAlso": [ + "https://www.unicode.org/license.txt", + "http://web.archive.org/web/20160823201924/http://www.unicode.org/copyright.html#License", + "http://www.unicode.org/copyright.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Unicode-TOU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-TOU.json", + "referenceNumber": 36, + "name": "Unicode Terms of Use", + "licenseId": "Unicode-TOU", + "seeAlso": [ + "http://web.archive.org/web/20140704074106/http://www.unicode.org/copyright.html", + "http://www.unicode.org/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/UnixCrypt.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UnixCrypt.json", + "referenceNumber": 353, + "name": "UnixCrypt License", + "licenseId": "UnixCrypt", + "seeAlso": [ + "https://foss.heptapod.net/python-libs/passlib/-/blob/branch/stable/LICENSE#L70", + "https://opensource.apple.com/source/JBoss/JBoss-737/jboss-all/jetty/src/main/org/mortbay/util/UnixCrypt.java.auto.html", + "https://archive.eclipse.org/jetty/8.0.1.v20110908/xref/org/eclipse/jetty/http/security/UnixCrypt.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Unlicense.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unlicense.json", + "referenceNumber": 50, + "name": "The Unlicense", + "licenseId": "Unlicense", + "seeAlso": [ + "https://unlicense.org/" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/UPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UPL-1.0.json", + "referenceNumber": 28, + "name": "Universal Permissive License v1.0", + "licenseId": "UPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/UPL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/URT-RLE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/URT-RLE.json", + "referenceNumber": 549, + "name": "Utah Raster Toolkit Run Length Encoded License", + "licenseId": "URT-RLE", + "seeAlso": [ + "https://sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/other/pnmtorle.c", + "https://sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/other/rletopnm.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Vim.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Vim.json", + "referenceNumber": 554, + "name": "Vim License", + "licenseId": "Vim", + "seeAlso": [ + "http://vimdoc.sourceforge.net/htmldoc/uganda.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/VOSTROM.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/VOSTROM.json", + "referenceNumber": 106, + "name": "VOSTROM Public License for Open Source", + "licenseId": "VOSTROM", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/VOSTROM" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/VSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/VSL-1.0.json", + "referenceNumber": 128, + "name": "Vovida Software License v1.0", + "licenseId": "VSL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/VSL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/W3C.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C.json", + "referenceNumber": 522, + "name": "W3C Software Notice and License (2002-12-31)", + "licenseId": "W3C", + "seeAlso": [ + "http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231.html", + "https://opensource.org/licenses/W3C" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/W3C-19980720.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C-19980720.json", + "referenceNumber": 126, + "name": "W3C Software Notice and License (1998-07-20)", + "licenseId": "W3C-19980720", + "seeAlso": [ + "http://www.w3.org/Consortium/Legal/copyright-software-19980720.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/W3C-20150513.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C-20150513.json", + "referenceNumber": 368, + "name": "W3C Software Notice and Document License (2015-05-13)", + "licenseId": "W3C-20150513", + "seeAlso": [ + "https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/w3m.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/w3m.json", + "referenceNumber": 277, + "name": "w3m License", + "licenseId": "w3m", + "seeAlso": [ + "https://github.com/tats/w3m/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Watcom-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Watcom-1.0.json", + "referenceNumber": 430, + "name": "Sybase Open Watcom Public License 1.0", + "licenseId": "Watcom-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Watcom-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/Widget-Workshop.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Widget-Workshop.json", + "referenceNumber": 371, + "name": "Widget Workshop License", + "licenseId": "Widget-Workshop", + "seeAlso": [ + "https://github.com/novnc/noVNC/blob/master/core/crypto/des.js#L24" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Wsuipa.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Wsuipa.json", + "referenceNumber": 81, + "name": "Wsuipa License", + "licenseId": "Wsuipa", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Wsuipa" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/WTFPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/WTFPL.json", + "referenceNumber": 74, + "name": "Do What The F*ck You Want To Public License", + "licenseId": "WTFPL", + "seeAlso": [ + "http://www.wtfpl.net/about/", + "http://sam.zoy.org/wtfpl/COPYING" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/wxWindows.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/wxWindows.json", + "referenceNumber": 288, + "name": "wxWindows Library License", + "licenseId": "wxWindows", + "seeAlso": [ + "https://opensource.org/licenses/WXwindows" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/X11.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/X11.json", + "referenceNumber": 424, + "name": "X11 License", + "licenseId": "X11", + "seeAlso": [ + "http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/X11-distribute-modifications-variant.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/X11-distribute-modifications-variant.json", + "referenceNumber": 494, + "name": "X11 License Distribution Modification Variant", + "licenseId": "X11-distribute-modifications-variant", + "seeAlso": [ + "https://github.com/mirror/ncurses/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xdebug-1.03.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xdebug-1.03.json", + "referenceNumber": 58, + "name": "Xdebug License v 1.03", + "licenseId": "Xdebug-1.03", + "seeAlso": [ + "https://github.com/xdebug/xdebug/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xerox.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xerox.json", + "referenceNumber": 428, + "name": "Xerox License", + "licenseId": "Xerox", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Xerox" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xfig.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xfig.json", + "referenceNumber": 289, + "name": "Xfig License", + "licenseId": "Xfig", + "seeAlso": [ + "https://github.com/Distrotech/transfig/blob/master/transfig/transfig.c", + "https://fedoraproject.org/wiki/Licensing:MIT#Xfig_Variant", + "https://sourceforge.net/p/mcj/xfig/ci/master/tree/src/Makefile.am" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/XFree86-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/XFree86-1.1.json", + "referenceNumber": 218, + "name": "XFree86 License 1.1", + "licenseId": "XFree86-1.1", + "seeAlso": [ + "http://www.xfree86.org/current/LICENSE4.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/xinetd.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xinetd.json", + "referenceNumber": 138, + "name": "xinetd License", + "licenseId": "xinetd", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Xinetd_License" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/xkeyboard-config-Zinoviev.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xkeyboard-config-Zinoviev.json", + "referenceNumber": 462, + "name": "xkeyboard-config Zinoviev License", + "licenseId": "xkeyboard-config-Zinoviev", + "seeAlso": [ + "https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/blob/master/COPYING?ref_type\u003dheads#L178" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/xlock.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xlock.json", + "referenceNumber": 299, + "name": "xlock License", + "licenseId": "xlock", + "seeAlso": [ + "https://fossies.org/linux/tiff/contrib/ras/ras2tif.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xnet.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xnet.json", + "referenceNumber": 523, + "name": "X.Net License", + "licenseId": "Xnet", + "seeAlso": [ + "https://opensource.org/licenses/Xnet" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/xpp.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xpp.json", + "referenceNumber": 457, + "name": "XPP License", + "licenseId": "xpp", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/xpp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/XSkat.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/XSkat.json", + "referenceNumber": 267, + "name": "XSkat License", + "licenseId": "XSkat", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/XSkat_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/YPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/YPL-1.0.json", + "referenceNumber": 191, + "name": "Yahoo! Public License v1.0", + "licenseId": "YPL-1.0", + "seeAlso": [ + "http://www.zimbra.com/license/yahoo_public_license_1.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/YPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/YPL-1.1.json", + "referenceNumber": 114, + "name": "Yahoo! Public License v1.1", + "licenseId": "YPL-1.1", + "seeAlso": [ + "http://www.zimbra.com/license/yahoo_public_license_1.1.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Zed.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zed.json", + "referenceNumber": 182, + "name": "Zed License", + "licenseId": "Zed", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Zed" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zeeff.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zeeff.json", + "referenceNumber": 4, + "name": "Zeeff License", + "licenseId": "Zeeff", + "seeAlso": [ + "ftp://ftp.tin.org/pub/news/utils/newsx/newsx-1.6.tar.gz" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zend-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zend-2.0.json", + "referenceNumber": 140, + "name": "Zend License v2.0", + "licenseId": "Zend-2.0", + "seeAlso": [ + "https://web.archive.org/web/20130517195954/http://www.zend.com/license/2_00.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Zimbra-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zimbra-1.3.json", + "referenceNumber": 116, + "name": "Zimbra Public License v1.3", + "licenseId": "Zimbra-1.3", + "seeAlso": [ + "http://web.archive.org/web/20100302225219/http://www.zimbra.com/license/zimbra-public-license-1-3.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Zimbra-1.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zimbra-1.4.json", + "referenceNumber": 6, + "name": "Zimbra Public License v1.4", + "licenseId": "Zimbra-1.4", + "seeAlso": [ + "http://www.zimbra.com/legal/zimbra-public-license-1-4" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zlib.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zlib.json", + "referenceNumber": 132, + "name": "zlib License", + "licenseId": "Zlib", + "seeAlso": [ + "http://www.zlib.net/zlib_license.html", + "https://opensource.org/licenses/Zlib" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/zlib-acknowledgement.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/zlib-acknowledgement.json", + "referenceNumber": 76, + "name": "zlib/libpng License with Acknowledgement", + "licenseId": "zlib-acknowledgement", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/ZlibWithAcknowledgement" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ZPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-1.1.json", + "referenceNumber": 176, + "name": "Zope Public License 1.1", + "licenseId": "ZPL-1.1", + "seeAlso": [ + "http://old.zope.org/Resources/License/ZPL-1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ZPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-2.0.json", + "referenceNumber": 552, + "name": "Zope Public License 2.0", + "licenseId": "ZPL-2.0", + "seeAlso": [ + "http://old.zope.org/Resources/License/ZPL-2.0", + "https://opensource.org/licenses/ZPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ZPL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-2.1.json", + "referenceNumber": 335, + "name": "Zope Public License 2.1", + "licenseId": "ZPL-2.1", + "seeAlso": [ + "http://old.zope.org/Resources/ZPL/" + ], + "isOsiApproved": true, + "isFsfLibre": true + } + ], + "releaseDate": "2024-02-03" +} diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py index 12c0ffd52b..28d2060f9c 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * -# * Copyright (c) 2022-2024 FreeCAD Project Association * +# * Copyright (c) 2024 FreeCAD Project Association * # * * # * This file is part of FreeCAD. * # * * @@ -99,5 +99,5 @@ class WidgetSearch(QtWidgets.QWidget): def retranslateUi(self, _): self.filter_line_edit.setPlaceholderText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Filter", None) + QtCore.QCoreApplication.translate("AddonsInstaller", "Search...", None) ) diff --git a/src/Mod/AddonManager/addonmanager_licenses.py b/src/Mod/AddonManager/addonmanager_licenses.py new file mode 100644 index 0000000000..7577ec8891 --- /dev/null +++ b/src/Mod/AddonManager/addonmanager_licenses.py @@ -0,0 +1,127 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Utilities for working with licenses. Based on SPDX info downloaded from +https://github.com/spdx/license-list-data and stored as part of the FreeCAD repo, loaded into a Qt +resource. """ + +import json + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore + + +class SPDXLicenseManager: + """A class that loads a list of licenses from an internal Qt resource and provides access to + some information about those licenses.""" + + def __init__(self): + self.license_data = {} + self._load_license_data() + + def _load_license_data(self): + qf = QtCore.QFile(f":/licenses/spdx.json") + if qf.exists(): + qf.open(QtCore.QIODevice.ReadOnly) + byte_data = qf.readAll() + qf.close() + + string_data = str(byte_data, encoding="utf-8") + raw_license_data = json.loads(string_data) + + self._process_raw_spdx_json(raw_license_data) + + def _process_raw_spdx_json(self, raw_license_data: dict): + """The raw JSON data is a list of licenses, with the ID as an element of the contained + data members. More useful for our purposes is a dictionary with the SPDX IDs as the keys + and the remaining data as the values.""" + for entry in raw_license_data["licenses"]: + self.license_data[entry["licenseId"]] = entry + + def is_osi_approved(self, spdx_id: str) -> bool: + """Check to see if the license is OSI-approved, according to the SPDX database. Returns + False if the license is not in the database, or is not marked as "isOsiApproved".""" + if spdx_id not in self.license_data: + return False + return self.license_data[spdx_id]["isOsiApproved"] + + def is_fsf_libre(self, spdx_id: str) -> bool: + """Check to see if the license is FSF Free/Libre, according to the SPDX database. Returns + False if the license is not in the database, or is not marked as "isFsfLibre".""" + if spdx_id not in self.license_data: + return False + return self.license_data[spdx_id]["isFsfLibre"] + + def name(self, spdx_id: str) -> str: + if spdx_id not in self.license_data: + return "" + return self.license_data[spdx_id]["name"] + + def url(self, spdx_id: str) -> str: + if spdx_id not in self.license_data: + return "" + return self.license_data[spdx_id]["reference"] + + def details_json_url(self, spdx_id: str): + """The "detailsUrl" entry in the SPDX database, which is a link to a JSON file containing + the details of the license. As of SPDX v3 the fields are: + * isDeprecatedLicenseId + * isFsfLibre + * licenseText + * standardLicenseHeaderTemplate + * standardLicenseTemplate + * name + * licenseId + * standardLicenseHeader + * crossRef + * seeAlso + * isOsiApproved + * licenseTextHtml + * standardLicenseHeaderHtml""" + if spdx_id not in self.license_data: + return "" + return self.license_data[spdx_id]["detailsUrl"] + + +_LICENSE_MANAGER = None # Internal use only, see get_license_manager() + + +def get_license_manager() -> SPDXLicenseManager: + """Get the license manager. Prevents multiple re-loads of the license list by keeping a + single copy of the manager.""" + global _LICENSE_MANAGER + if _LICENSE_MANAGER is None: + _LICENSE_MANAGER = SPDXLicenseManager() + return _LICENSE_MANAGER diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index 2d8c2b0565..b4e2a9d1ce 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -40,6 +40,7 @@ from addonmanager_metadata import get_first_supported_freecad_version, Version from Widgets.addonmanager_widget_view_control_bar import WidgetViewControlBar from Widgets.addonmanager_widget_view_selector import AddonManagerDisplayStyle from Widgets.addonmanager_widget_filter_selector import StatusFilter, Filter, ContentFilter +from addonmanager_licenses import get_license_manager, SPDXLicenseManager translate = FreeCAD.Qt.translate @@ -91,6 +92,8 @@ class PackageList(QtWidgets.QWidget): self.item_filter.setHidePy2(pref.GetBool("HidePy2", True)) self.item_filter.setHideObsolete(pref.GetBool("HideObsolete", True)) + self.item_filter.setHideNonOSIApproved(pref.GetBool("HideNonOSIApproved", True)) + self.item_filter.setHideNonFSFLibre(pref.GetBool("HideNonFSFFreeLibre", True)) self.item_filter.setHideNewerFreeCADRequired(pref.GetBool("HideNewerFreeCADRequired", True)) def on_listPackages_clicked(self, index: QtCore.QModelIndex): @@ -464,6 +467,8 @@ class PackageListFilter(QtCore.QSortFilterProxyModel): self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) self.hide_obsolete = False self.hide_py2 = False + self.hide_non_OSI_approved = False + self.hide_non_FSF_libre = False self.hide_newer_freecad_required = False def setPackageFilter( @@ -490,6 +495,16 @@ class PackageListFilter(QtCore.QSortFilterProxyModel): self.hide_obsolete = hide_obsolete self.invalidateFilter() + def setHideNonOSIApproved(self, hide: bool) -> None: + """Sets whether to hide Addons with non-OSI-approved licenses""" + self.hide_non_OSI_approved = hide + self.invalidateFilter() + + def setHideNonFSFLibre(self, hide: bool) -> None: + """Sets whether to hide Addons with non-FSF-Libre licenses""" + self.hide_non_FSF_libre = hide + self.invalidateFilter() + def setHideNewerFreeCADRequired(self, hide_nfr: bool) -> None: """Sets whether to hide packages that have indicated they need a newer version of FreeCAD than the one currently running.""" @@ -529,13 +544,24 @@ class PackageListFilter(QtCore.QSortFilterProxyModel): if data.status() != Addon.Status.UPDATE_AVAILABLE: return False - # If it's not installed, check to see if it's Py2 only - if data.status() == Addon.Status.NOT_INSTALLED and self.hide_py2 and data.python2: - return False + license_manager = get_license_manager() + if data.status() == Addon.Status.NOT_INSTALLED: - # If it's not installed, check to see if it's marked obsolete - if data.status() == Addon.Status.NOT_INSTALLED and self.hide_obsolete and data.obsolete: - return False + # If it's not installed, check to see if it's Py2 only + if self.hide_py2 and data.python2: + return False + + # If it's not installed, check to see if it's marked obsolete + if self.hide_obsolete and data.obsolete: + return False + + # If it is not an OSI-approved license, check to see if we are hiding those + if self.hide_non_OSI_approved and not license_manager.is_osi_approved(data.license): + return False + + # If it is not an FSF Free/Libre license, check to see if we are hiding those + if self.hide_non_FSF_libre and not license_manager.is_fsf_libre(data.license): + return False # If it's not installed, check to see if it's for a newer version of FreeCAD if ( From 0a5a5d60d01b856ae560a50c3a50e9c486ff70aa Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 15:57:37 +0100 Subject: [PATCH 04/25] Addon Manager: Fix macro cache update hang --- src/Mod/AddonManager/addonmanager_workers_startup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_workers_startup.py b/src/Mod/AddonManager/addonmanager_workers_startup.py index 7769216e04..c196477a5d 100644 --- a/src/Mod/AddonManager/addonmanager_workers_startup.py +++ b/src/Mod/AddonManager/addonmanager_workers_startup.py @@ -871,8 +871,6 @@ class CacheMacroCodeWorker(QtCore.QThread): ) with self.lock: self.failed.append(macro_name) - self.repo_queue.task_done() - self.counter += 1 class GetMacroDetailsWorker(QtCore.QThread): From d7c7b6dc49e9f02030af4217fc8c55e7153130a6 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 15:59:33 +0100 Subject: [PATCH 05/25] Addon Manager: Clean up spacing in top bar --- src/Mod/AddonManager/Widgets/addonmanager_widget_search.py | 1 + .../Widgets/addonmanager_widget_view_control_bar.py | 1 + .../AddonManager/Widgets/addonmanager_widget_view_selector.py | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py index 28d2060f9c..7da8b1c271 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_search.py @@ -63,6 +63,7 @@ class WidgetSearch(QtWidgets.QWidget): def _setup_ui(self): self.horizontal_layout = QtWidgets.QHBoxLayout() + self.horizontal_layout.setContentsMargins(0, 0, 0, 0) self.filter_line_edit = QtWidgets.QLineEdit(self) self.filter_line_edit.setClearButtonEnabled(True) self.horizontal_layout.addWidget(self.filter_line_edit) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_view_control_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_control_bar.py index 286ceac589..46c0430e46 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_view_control_bar.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_control_bar.py @@ -57,6 +57,7 @@ class WidgetViewControlBar(QtWidgets.QWidget): def _setup_ui(self): self.horizontal_layout = QtWidgets.QHBoxLayout() + self.horizontal_layout.setContentsMargins(0, 0, 0, 0) self.view_selector = WidgetViewSelector(self) self.filter_selector = WidgetFilterSelector(self) self.search = WidgetSearch(self) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py index 7bcb9ff7ba..dca81ec6f8 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py @@ -92,6 +92,8 @@ class WidgetViewSelector(QtWidgets.QWidget): def _setup_ui(self): self.horizontal_layout = QtWidgets.QHBoxLayout() + self.horizontal_layout.setContentsMargins(0, 0, 0, 0) + self.horizontal_layout.setSpacing(2) self.compact_button = QtWidgets.QToolButton(self) self.compact_button.setObjectName("compact_button") self.compact_button.setIcon( From 0dbc9aa6e3ea3fd86bb92d4bda333f6d0290e515 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 16:12:04 +0100 Subject: [PATCH 06/25] Addon Manager: Further macro load cleanup --- src/Mod/AddonManager/addonmanager_workers_startup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/AddonManager/addonmanager_workers_startup.py b/src/Mod/AddonManager/addonmanager_workers_startup.py index c196477a5d..0797d1f122 100644 --- a/src/Mod/AddonManager/addonmanager_workers_startup.py +++ b/src/Mod/AddonManager/addonmanager_workers_startup.py @@ -859,7 +859,7 @@ class CacheMacroCodeWorker(QtCore.QThread): ).format(macro_name) + "\n" ) - worker.blockSignals(True) + # worker.blockSignals(True) worker.requestInterruption() worker.wait(100) if worker.isRunning(): From 34850ef8e1e417adaee869365010d081d7267d8e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 18:01:34 +0100 Subject: [PATCH 07/25] Addon Manager: Refactor progress bar --- src/Mod/AddonManager/AddonManager.py | 28 ++---- src/Mod/AddonManager/AddonManager.ui | 70 +------------- src/Mod/AddonManager/TODO.md | 14 +++ src/Mod/AddonManager/Widgets/CMakeLists.txt | 1 + .../addonmanager_widget_progress_bar.py | 94 +++++++++++++++++++ src/Mod/AddonManager/package_list.py | 4 + 6 files changed, 123 insertions(+), 88 deletions(-) create mode 100644 src/Mod/AddonManager/TODO.md create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 7a2680ce67..8d80b689fa 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -214,9 +214,6 @@ class CommandAddonManager: self.dialog.buttonClose.setIcon( QtGui.QIcon.fromTheme("close", QtGui.QIcon(":/icons/process-stop.svg")) ) - self.dialog.buttonPauseUpdate.setIcon( - QtGui.QIcon.fromTheme("pause", QtGui.QIcon(":/icons/media-playback-stop.svg")) - ) pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") dev_mode_active = pref.GetBool("developerMode", False) @@ -236,12 +233,12 @@ class CommandAddonManager: self.dialog.buttonUpdateAll.clicked.connect(self.update_all) self.dialog.buttonClose.clicked.connect(self.dialog.reject) self.dialog.buttonUpdateCache.clicked.connect(self.on_buttonUpdateCache_clicked) - self.dialog.buttonPauseUpdate.clicked.connect(self.stop_update) self.dialog.buttonCheckForUpdates.clicked.connect( lambda: self.force_check_updates(standalone=True) ) self.dialog.buttonUpdateDependencies.clicked.connect(self.show_python_updates_dialog) self.dialog.buttonDevTools.clicked.connect(self.show_developer_tools) + self.packageList.ui.progressBar.stop_clicked.connect(self.stop_update) self.packageList.itemSelected.connect(self.table_row_activated) self.packageList.setEnabled(False) self.packageDetails.execute.connect(self.executemacro) @@ -257,9 +254,6 @@ class CommandAddonManager: mw.frameGeometry().topLeft() + mw.rect().center() - self.dialog.rect().center() ) - # set info for the progress bar: - self.dialog.progressBar.setMaximum(1000) - # begin populating the table in a set of sub-threads self.startup() @@ -407,8 +401,8 @@ class CommandAddonManager: if selection: self.startup_sequence.insert(2, functools.partial(self.select_addon, selection)) pref.SetString("SelectedAddon", "") - self.current_progress_region = 0 self.number_of_progress_regions = len(self.startup_sequence) + self.current_progress_region = 0 self.do_next_startup_phase() def do_next_startup_phase(self) -> None: @@ -750,8 +744,8 @@ class CommandAddonManager: def show_information(self, message: str) -> None: """shows generic text in the information pane""" - self.dialog.labelStatusInfo.setText(message) - self.dialog.labelStatusInfo.repaint() + self.packageList.ui.progressBar.set_status(message) + self.packageList.ui.progressBar.repaint() def show_workbench(self, repo: Addon) -> None: self.packageList.hide() @@ -821,16 +815,12 @@ class CommandAddonManager: def hide_progress_widgets(self) -> None: """hides the progress bar and related widgets""" - self.dialog.labelStatusInfo.hide() - self.dialog.progressBar.hide() - self.dialog.buttonPauseUpdate.hide() + self.packageList.ui.progressBar.hide() self.packageList.ui.view_bar.search.setFocus() def show_progress_widgets(self) -> None: - if self.dialog.progressBar.isHidden(): - self.dialog.progressBar.show() - self.dialog.buttonPauseUpdate.show() - self.dialog.labelStatusInfo.show() + if self.packageList.ui.progressBar.isHidden(): + self.packageList.ui.progressBar.show() def update_progress_bar(self, current_value: int, max_value: int) -> None: """Update the progress bar, showing it if it's hidden""" @@ -847,10 +837,10 @@ class CommandAddonManager: completed_region_portion = (self.current_progress_region - 1) * region_size current_region_portion = (float(current_value) / float(max_value)) * region_size value = completed_region_portion + current_region_portion - self.dialog.progressBar.setValue( + self.packageList.ui.progressBar.set_value( value * 10 ) # Out of 1000 segments, so it moves sort of smoothly - self.dialog.progressBar.repaint() + self.packageList.ui.progressBar.repaint() def stop_update(self) -> None: self.cleanup_workers() diff --git a/src/Mod/AddonManager/AddonManager.ui b/src/Mod/AddonManager/AddonManager.ui index bc75e323fa..8d394a589c 100644 --- a/src/Mod/AddonManager/AddonManager.ui +++ b/src/Mod/AddonManager/AddonManager.ui @@ -6,7 +6,7 @@ 0 0 - 800 + 928 600 @@ -24,74 +24,6 @@ - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 12 - - - - - 0 - 0 - - - - 0 - - - false - - - Downloading info... - - - - - - - Stop the cache update - - - - - - - - - - - - - 0 - 0 - - - - labelStatusInfo - - - - - diff --git a/src/Mod/AddonManager/TODO.md b/src/Mod/AddonManager/TODO.md new file mode 100644 index 0000000000..2c51912303 --- /dev/null +++ b/src/Mod/AddonManager/TODO.md @@ -0,0 +1,14 @@ +# Addon Manager Future Work + +* Restructure widgets into logical groups to better enable showing and hiding those groups all at once. +* Reduce coupling between data and UI, switching logical groupings of widgets into a MVC or similar framework. + * Particularly in the addons list +* Download Addon statistics from central location. + * Allow sorting on those statistics. +* Download a "rank" from user-specified locations. + * Allow sorting on that rank. +* Implement a server-side cache of Addon metadata. +* Implement an "offline mode" that does not attempt to use remote data for anything. +* When installing a Preference Pack, offer to apply it once installed, and to undo after that. +* Better support "headless" mode, with no GUI. +* Add "Composite" display mode, showing compact list and details at the same time diff --git a/src/Mod/AddonManager/Widgets/CMakeLists.txt b/src/Mod/AddonManager/Widgets/CMakeLists.txt index 61d8d613b1..99b4ee4617 100644 --- a/src/Mod/AddonManager/Widgets/CMakeLists.txt +++ b/src/Mod/AddonManager/Widgets/CMakeLists.txt @@ -1,6 +1,7 @@ SET(AddonManagerWidget_SRCS __init__.py addonmanager_widget_filter_selector.py + addonmanager_widget_progress_bar.py addonmanager_widget_search.py addonmanager_widget_view_control_bar.py addonmanager_widget_view_selector.py diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py new file mode 100644 index 0000000000..3f61950ba5 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a QWidget-derived class for displaying the cache load status. """ + +try: + import FreeCAD + + translate = FreeCAD.Qt.translate +except ImportError: + FreeCAD = None + + def translate(_: str, text: str): + return text + + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtGui, QtWidgets + +_TOTAL_INCREMENTS = 1000 + + +class WidgetProgressBar(QtWidgets.QWidget): + + """A multipart progress bar widget, including a stop button and a status label. Defaults to a + single range with 100 increments, but can be configured with any number of major and minor + ranges. Clicking the stop button will emit a signal, but does not otherwise affect the + widget.""" + + stop_clicked = QtCore.Signal() + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self.vertical_layout = None + self.horizontal_layout = None + self.progress_bar = None + self.status_label = None + self.stop_button = None + self._setup_ui() + + def _setup_ui(self): + self.vertical_layout = QtWidgets.QVBoxLayout(self) + self.horizontal_layout = QtWidgets.QHBoxLayout(self) + self.progress_bar = QtWidgets.QProgressBar(self) + self.status_label = QtWidgets.QLabel(self) + self.stop_button = QtWidgets.QToolButton(self) + self.progress_bar.setMaximum(_TOTAL_INCREMENTS) + self.stop_button.clicked.connect(self.stop_clicked) + self.stop_button.setIcon( + QtGui.QIcon.fromTheme("stop", QtGui.QIcon(":/icons/media-playback-stop.svg")) + ) + self.vertical_layout.addLayout(self.horizontal_layout) + self.vertical_layout.addWidget(self.status_label) + self.horizontal_layout.addWidget(self.progress_bar) + self.horizontal_layout.addWidget(self.stop_button) + self.setLayout(self.vertical_layout) + + def set_status(self, status: str): + self.status_label.setText(status) + + def set_value(self, value: int): + self.progress_bar.setValue(value) diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index b4e2a9d1ce..6b443994b1 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -40,6 +40,7 @@ from addonmanager_metadata import get_first_supported_freecad_version, Version from Widgets.addonmanager_widget_view_control_bar import WidgetViewControlBar from Widgets.addonmanager_widget_view_selector import AddonManagerDisplayStyle from Widgets.addonmanager_widget_filter_selector import StatusFilter, Filter, ContentFilter +from Widgets.addonmanager_widget_progress_bar import WidgetProgressBar from addonmanager_licenses import get_license_manager, SPDXLicenseManager translate = FreeCAD.Qt.translate @@ -643,4 +644,7 @@ class Ui_PackageList: self.verticalLayout.addWidget(self.listPackages) + self.progressBar = WidgetProgressBar() + self.verticalLayout.addWidget(self.progressBar) + QtCore.QMetaObject.connectSlotsByName(form) From b994701042f2ded8933862c14a08956b8dd77b8c Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 18:05:32 +0100 Subject: [PATCH 08/25] Addon Manager: Clean up new progress bar --- .../AddonManager/Widgets/addonmanager_widget_progress_bar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py index 3f61950ba5..f52d73b806 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py @@ -85,6 +85,8 @@ class WidgetProgressBar(QtWidgets.QWidget): self.vertical_layout.addWidget(self.status_label) self.horizontal_layout.addWidget(self.progress_bar) self.horizontal_layout.addWidget(self.stop_button) + self.vertical_layout.setContentsMargins(0, 0, 0, 0) + self.horizontal_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.vertical_layout) def set_status(self, status: str): From 8e60d05df2310b54c873a44105e5747211c2129f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 14:48:04 -0600 Subject: [PATCH 09/25] Addon Manager: Refactor global button bar --- src/Mod/AddonManager/AddonManager.py | 75 ++++++------- src/Mod/AddonManager/AddonManager.ui | 90 +--------------- src/Mod/AddonManager/Widgets/CMakeLists.txt | 1 + .../addonmanager_widget_global_buttons.py | 100 ++++++++++++++++++ 4 files changed, 141 insertions(+), 125 deletions(-) create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 8d80b689fa..2c2dcabf63 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -53,6 +53,7 @@ import addonmanager_utilities as utils import AddonManager_rc # This is required by Qt, it's not unused from package_list import PackageList, PackageListItemModel from package_details import PackageDetails +from Widgets.addonmanager_widget_global_buttons import WidgetGlobalButtonBar from Addon import Addon from manage_python_dependencies import ( PythonPackageManager, @@ -130,6 +131,7 @@ class CommandAddonManager: self.update_all_worker = None self.developer_mode = None self.installer_gui = None + self.button_bar = None self.update_cache = False self.dialog = None @@ -185,21 +187,21 @@ class CommandAddonManager: w = pref.GetInt("WindowWidth", 800) h = pref.GetInt("WindowHeight", 600) self.dialog.resize(w, h) + self.button_bar = WidgetGlobalButtonBar(self.dialog) # If we are checking for updates automatically, hide the Check for updates button: autocheck = pref.GetBool("AutoCheck", False) if autocheck: - self.dialog.buttonCheckForUpdates.hide() + self.button_bar.check_for_updates.hide() else: - self.dialog.buttonUpdateAll.hide() + self.button_bar.update_all_addons.hide() # Set up the listing of packages using the model-view-controller architecture self.packageList = PackageList(self.dialog) self.item_model = PackageListItemModel() self.packageList.setModel(self.item_model) - self.dialog.contentPlaceholder.hide() - self.dialog.layout().replaceWidget(self.dialog.contentPlaceholder, self.packageList) - self.packageList.show() + self.dialog.layout().addWidget(self.packageList) + self.dialog.layout().addWidget(self.button_bar) # Package details start out hidden self.packageDetails = PackageDetails(self.dialog) @@ -209,35 +211,30 @@ class CommandAddonManager: # set nice icons to everything, by theme with fallback to FreeCAD icons self.dialog.setWindowIcon(QtGui.QIcon(":/icons/AddonManager.svg")) - self.dialog.buttonUpdateAll.setIcon(QtGui.QIcon(":/icons/button_valid.svg")) - self.dialog.buttonCheckForUpdates.setIcon(QtGui.QIcon(":/icons/view-refresh.svg")) - self.dialog.buttonClose.setIcon( - QtGui.QIcon.fromTheme("close", QtGui.QIcon(":/icons/process-stop.svg")) - ) pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") dev_mode_active = pref.GetBool("developerMode", False) # enable/disable stuff - self.dialog.buttonUpdateAll.setEnabled(False) + self.button_bar.update_all_addons.setEnabled(False) self.hide_progress_widgets() - self.dialog.buttonUpdateCache.setEnabled(False) - self.dialog.buttonUpdateCache.setText(translate("AddonsInstaller", "Starting up...")) + self.button_bar.refresh_local_cache.setEnabled(False) + self.button_bar.refresh_local_cache.setText(translate("AddonsInstaller", "Starting up...")) if dev_mode_active: - self.dialog.buttonDevTools.show() + self.button_bar.developer_tools.show() else: - self.dialog.buttonDevTools.hide() + self.button_bar.developer_tools.hide() # connect slots self.dialog.rejected.connect(self.reject) - self.dialog.buttonUpdateAll.clicked.connect(self.update_all) - self.dialog.buttonClose.clicked.connect(self.dialog.reject) - self.dialog.buttonUpdateCache.clicked.connect(self.on_buttonUpdateCache_clicked) - self.dialog.buttonCheckForUpdates.clicked.connect( + self.button_bar.update_all_addons.clicked.connect(self.update_all) + self.button_bar.close.clicked.connect(self.dialog.reject) + self.button_bar.refresh_local_cache.clicked.connect(self.on_buttonUpdateCache_clicked) + self.button_bar.check_for_updates.clicked.connect( lambda: self.force_check_updates(standalone=True) ) - self.dialog.buttonUpdateDependencies.clicked.connect(self.show_python_updates_dialog) - self.dialog.buttonDevTools.clicked.connect(self.show_developer_tools) + self.button_bar.python_dependencies.clicked.connect(self.show_python_updates_dialog) + self.button_bar.developer_tools.clicked.connect(self.show_developer_tools) self.packageList.ui.progressBar.stop_clicked.connect(self.stop_update) self.packageList.itemSelected.connect(self.table_row_activated) self.packageList.setEnabled(False) @@ -415,8 +412,8 @@ class CommandAddonManager: else: self.hide_progress_widgets() self.update_cache = False - self.dialog.buttonUpdateCache.setEnabled(True) - self.dialog.buttonUpdateCache.setText( + self.button_bar.refresh_local_cache.setEnabled(True) + self.button_bar.refresh_local_cache.setText( translate("AddonsInstaller", "Refresh local cache") ) pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") @@ -545,8 +542,10 @@ class CommandAddonManager: cache_path = FreeCAD.getUserCachePath() am_path = os.path.join(cache_path, "AddonManager") utils.rmdir(am_path) - self.dialog.buttonUpdateCache.setEnabled(False) - self.dialog.buttonUpdateCache.setText(translate("AddonsInstaller", "Updating cache...")) + self.button_bar.refresh_local_cache.setEnabled(False) + self.button_bar.refresh_local_cache.setText( + translate("AddonsInstaller", "Updating cache...") + ) self.startup() # Recaching implies checking for updates, regardless of the user's autocheck option @@ -608,10 +607,12 @@ class CommandAddonManager: self.do_next_startup_phase() return - self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "Checking for updates...")) + self.button_bar.update_all_addons.setText( + translate("AddonsInstaller", "Checking for updates...") + ) self.packages_with_updates.clear() - self.dialog.buttonUpdateAll.show() - self.dialog.buttonCheckForUpdates.setDisabled(True) + self.button_bar.update_all_addons.show() + self.button_bar.check_for_updates.setDisabled(True) self.check_worker = CheckWorkbenchesForUpdatesWorker(self.item_model.repos) self.check_worker.finished.connect(self.do_next_startup_phase) self.check_worker.finished.connect(self.update_check_complete) @@ -636,21 +637,21 @@ class CommandAddonManager: if number_of_updates: s = translate("AddonsInstaller", "Apply {} update(s)", "", number_of_updates) - self.dialog.buttonUpdateAll.setText(s.format(number_of_updates)) - self.dialog.buttonUpdateAll.setEnabled(True) + self.button_bar.update_all_addons.setText(s.format(number_of_updates)) + self.button_bar.update_all_addons.setEnabled(True) elif hasattr(self, "check_worker") and self.check_worker.isRunning(): - self.dialog.buttonUpdateAll.setText( + self.button_bar.update_all_addons.setText( translate("AddonsInstaller", "Checking for updates...") ) else: - self.dialog.buttonUpdateAll.setText( + self.button_bar.update_all_addons.setText( translate("AddonsInstaller", "No updates available") ) - self.dialog.buttonUpdateAll.setEnabled(False) + self.button_bar.update_all_addons.setEnabled(False) def update_check_complete(self) -> None: self.enable_updates(len(self.packages_with_updates)) - self.dialog.buttonCheckForUpdates.setEnabled(True) + self.button_bar.check_for_updates.setEnabled(True) def check_python_updates(self) -> None: PythonPackageManager.migrate_old_am_installations() # Migrate 0.20 to 0.21 @@ -846,8 +847,10 @@ class CommandAddonManager: self.cleanup_workers() self.hide_progress_widgets() self.write_cache_stopfile() - self.dialog.buttonUpdateCache.setEnabled(True) - self.dialog.buttonUpdateCache.setText(translate("AddonsInstaller", "Refresh local cache")) + self.button_bar.refresh_local_cache.setEnabled(True) + self.button_bar.refresh_local_cache.setText( + translate("AddonsInstaller", "Refresh local cache") + ) def write_cache_stopfile(self) -> None: stopfile = utils.get_cache_file_name("CACHE_UPDATE_INTERRUPTED") diff --git a/src/Mod/AddonManager/AddonManager.ui b/src/Mod/AddonManager/AddonManager.ui index 8d394a589c..b2d31fcb15 100644 --- a/src/Mod/AddonManager/AddonManager.ui +++ b/src/Mod/AddonManager/AddonManager.ui @@ -13,95 +13,7 @@ Addon Manager - - - - - - 0 - 0 - - - - - - - - QLayout::SetDefaultConstraint - - - - - Refresh local cache - - - - - - - Download and apply all available updates - - - Update all Addons - - - - - - - Check for updates - - - - - - - true - - - View and update Python package dependencies - - - Python dependencies... - - - - - - - Developer tools... - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Close the Addon Manager - - - Close - - - true - - - - - - + diff --git a/src/Mod/AddonManager/Widgets/CMakeLists.txt b/src/Mod/AddonManager/Widgets/CMakeLists.txt index 99b4ee4617..e3fefa9405 100644 --- a/src/Mod/AddonManager/Widgets/CMakeLists.txt +++ b/src/Mod/AddonManager/Widgets/CMakeLists.txt @@ -1,6 +1,7 @@ SET(AddonManagerWidget_SRCS __init__.py addonmanager_widget_filter_selector.py + addonmanager_widget_global_buttons.py addonmanager_widget_progress_bar.py addonmanager_widget_search.py addonmanager_widget_view_control_bar.py diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py new file mode 100644 index 0000000000..b175657b56 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a QWidget-derived class for displaying a set of buttons that affect the Addon +Manager as a whole (rather than a specific Addon). Typically inserted at the bottom of the Addon +Manager main window. """ + +try: + import FreeCAD + + translate = FreeCAD.Qt.translate +except ImportError: + FreeCAD = None + + def translate(_: str, text: str): + return text + + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtGui, QtWidgets + + +class WidgetGlobalButtonBar(QtWidgets.QWidget): + """A QWidget-derived class for displaying a set of buttons that affect the Addon Manager as a + whole (rather than a specific Addon).""" + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self.horizontal_layout = None + self.refresh_local_cache = None + self.update_all_addons = None + self.check_for_updates = None + self.python_dependencies = None + self.developer_tools = None + self.close = None + self._update_ui() + self.retranslateUi(None) + self._set_icons() + + def _update_ui(self): + self.horizontal_layout = QtWidgets.QHBoxLayout() + self.refresh_local_cache = QtWidgets.QPushButton(self) + self.update_all_addons = QtWidgets.QPushButton(self) + self.check_for_updates = QtWidgets.QPushButton(self) + self.python_dependencies = QtWidgets.QPushButton(self) + self.developer_tools = QtWidgets.QPushButton(self) + self.close = QtWidgets.QPushButton(self) + self.horizontal_layout.addWidget(self.refresh_local_cache) + self.horizontal_layout.addWidget(self.update_all_addons) + self.horizontal_layout.addWidget(self.check_for_updates) + self.horizontal_layout.addWidget(self.python_dependencies) + self.horizontal_layout.addWidget(self.developer_tools) + self.horizontal_layout.addStretch() + self.horizontal_layout.addWidget(self.close) + self.setLayout(self.horizontal_layout) + + def _set_icons(self): + self.update_all_addons.setIcon(QtGui.QIcon(":/icons/button_valid.svg")) + self.check_for_updates.setIcon(QtGui.QIcon(":/icons/view-refresh.svg")) + self.close.setIcon(QtGui.QIcon.fromTheme("close", QtGui.QIcon(":/icons/process-stop.svg"))) + + def retranslateUi(self, _): + self.refresh_local_cache.setText(translate("AddonsInstaller", "Close")) + self.update_all_addons.setText(translate("AddonsInstaller", "Update all addons")) + self.check_for_updates.setText(translate("AddonsInstaller", "Check for updates")) + self.python_dependencies.setText(translate("AddonsInstaller", "Python dependencies...")) + self.developer_tools.setText(translate("AddonsInstaller", "Developer tools...")) + self.close.setText(translate("AddonsInstaller", "Close")) From e76c22d77c3d79477028f46ee12bf9f1b23eeafc Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 15:01:26 -0600 Subject: [PATCH 10/25] Addon Manager: Minor refactoring of buttons --- src/Mod/AddonManager/AddonManager.py | 9 ++------- .../Widgets/addonmanager_widget_global_buttons.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 2c2dcabf63..fac90e86ae 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -636,18 +636,13 @@ class CommandAddonManager: """enables the update button""" if number_of_updates: - s = translate("AddonsInstaller", "Apply {} update(s)", "", number_of_updates) - self.button_bar.update_all_addons.setText(s.format(number_of_updates)) - self.button_bar.update_all_addons.setEnabled(True) + self.button_bar.set_number_of_available_updates(number_of_updates) elif hasattr(self, "check_worker") and self.check_worker.isRunning(): self.button_bar.update_all_addons.setText( translate("AddonsInstaller", "Checking for updates...") ) else: - self.button_bar.update_all_addons.setText( - translate("AddonsInstaller", "No updates available") - ) - self.button_bar.update_all_addons.setEnabled(False) + self.button_bar.set_number_of_available_updates(0) def update_check_complete(self) -> None: self.enable_updates(len(self.packages_with_updates)) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py index b175657b56..f5660e7a89 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py @@ -98,3 +98,16 @@ class WidgetGlobalButtonBar(QtWidgets.QWidget): self.python_dependencies.setText(translate("AddonsInstaller", "Python dependencies...")) self.developer_tools.setText(translate("AddonsInstaller", "Developer tools...")) self.close.setText(translate("AddonsInstaller", "Close")) + + def set_number_of_available_updates(self, updates: int): + if updates <= 0: + self.update_all_addons.setEnabled(False) + self.update_all_addons.setText(translate("AddonsInstaller", "No updates available")) + elif updates == 1: + self.update_all_addons.setEnabled(True) + self.update_all_addons.setText(translate("AddonsInstaller", "Apply 1 available update")) + else: + self.update_all_addons.setEnabled(True) + self.update_all_addons.setText( + translate("AddonsInstaller", "Apply %1 available updates").format(updates) + ) From 2e81663cc481e396a0012aff0456d4b9b7702dab Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 16:01:36 -0600 Subject: [PATCH 11/25] Addon Manager: Refactor buttons above detail view --- src/Mod/AddonManager/Widgets/CMakeLists.txt | 1 + .../addonmanager_widget_addon_buttons.py | 115 ++++++++++++ src/Mod/AddonManager/package_details.py | 170 +++++------------- 3 files changed, 164 insertions(+), 122 deletions(-) create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_addon_buttons.py diff --git a/src/Mod/AddonManager/Widgets/CMakeLists.txt b/src/Mod/AddonManager/Widgets/CMakeLists.txt index e3fefa9405..6637dad646 100644 --- a/src/Mod/AddonManager/Widgets/CMakeLists.txt +++ b/src/Mod/AddonManager/Widgets/CMakeLists.txt @@ -1,5 +1,6 @@ SET(AddonManagerWidget_SRCS __init__.py + addonmanager_widget_addon_buttons.py addonmanager_widget_filter_selector.py addonmanager_widget_global_buttons.py addonmanager_widget_progress_bar.py diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_addon_buttons.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_addon_buttons.py new file mode 100644 index 0000000000..29086a9cd0 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_addon_buttons.py @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2024 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a QWidget-derived class for displaying the single-addon buttons. """ + +from enum import Enum, auto + +try: + import FreeCAD + + translate = FreeCAD.Qt.translate +except ImportError: + FreeCAD = None + + def translate(_: str, text: str): + return text + + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtGui, QtWidgets + + +class ButtonBarDisplayMode(Enum): + TextOnly = auto() + IconsOnly = auto() + TextAndIcons = auto() + + +class WidgetAddonButtons(QtWidgets.QWidget): + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self.display_mode = ButtonBarDisplayMode.TextAndIcons + self._setup_ui() + self._set_icons() + self.retranslateUi(None) + + def set_display_mode(self, mode: ButtonBarDisplayMode): + """NOTE: Not really implemented yet -- TODO: Implement this functionality""" + if mode == self.display_mode: + return + self._setup_ui() + self._set_icons() + self.retranslateUi(None) + + def _setup_ui(self): + self.horizontal_layout = QtWidgets.QHBoxLayout() + self.horizontal_layout.setContentsMargins(0, 0, 0, 0) + self.back_button = QtWidgets.QToolButton(self) + self.install_button = QtWidgets.QPushButton(self) + self.uninstall_button = QtWidgets.QPushButton(self) + self.enable_button = QtWidgets.QPushButton(self) + self.disable_button = QtWidgets.QPushButton(self) + self.update_button = QtWidgets.QPushButton(self) + self.run_macro_button = QtWidgets.QPushButton(self) + self.change_branch_button = QtWidgets.QPushButton(self) + self.check_for_update_button = QtWidgets.QPushButton(self) + self.horizontal_layout.addWidget(self.back_button) + self.horizontal_layout.addStretch() + self.horizontal_layout.addWidget(self.check_for_update_button) + self.horizontal_layout.addWidget(self.install_button) + self.horizontal_layout.addWidget(self.uninstall_button) + self.horizontal_layout.addWidget(self.enable_button) + self.horizontal_layout.addWidget(self.disable_button) + self.horizontal_layout.addWidget(self.update_button) + self.horizontal_layout.addWidget(self.run_macro_button) + self.horizontal_layout.addWidget(self.change_branch_button) + self.setLayout(self.horizontal_layout) + + def _set_icons(self): + self.back_button.setIcon( + QtGui.QIcon.fromTheme("back", QtGui.QIcon(":/icons/button_left.svg")) + ) + + def retranslateUi(self, _): + self.check_for_update_button.setText(translate("AddonsInstaller", "Check for update")) + self.install_button.setText(translate("AddonsInstaller", "Install")) + self.uninstall_button.setText(translate("AddonsInstaller", "Uninstall")) + self.disable_button.setText(translate("AddonsInstaller", "Disable")) + self.enable_button.setText(translate("AddonsInstaller", "Enable")) + self.update_button.setText(translate("AddonsInstaller", "Update")) + self.run_macro_button.setText(translate("AddonsInstaller", "Run")) + self.change_branch_button.setText(translate("AddonsInstaller", "Change branch...")) + self.back_button.setToolTip(translate("AddonsInstaller", "Return to package list")) diff --git a/src/Mod/AddonManager/package_details.py b/src/Mod/AddonManager/package_details.py index b66100ba82..2b6e4a824c 100644 --- a/src/Mod/AddonManager/package_details.py +++ b/src/Mod/AddonManager/package_details.py @@ -41,6 +41,7 @@ from addonmanager_readme_viewer import ReadmeViewer from addonmanager_git import GitManager, NoGitFound from Addon import Addon from change_branch import ChangeBranchDialog +from Widgets.addonmanager_widget_addon_buttons import WidgetAddonButtons translate = fci.translate @@ -70,15 +71,17 @@ class PackageDetails(QtWidgets.QWidget): except NoGitFound: self.git_manager = None - self.ui.buttonBack.clicked.connect(self.back.emit) - self.ui.buttonExecute.clicked.connect(lambda: self.execute.emit(self.repo)) - self.ui.buttonInstall.clicked.connect(lambda: self.install.emit(self.repo)) - self.ui.buttonUninstall.clicked.connect(lambda: self.uninstall.emit(self.repo)) - self.ui.buttonUpdate.clicked.connect(lambda: self.update.emit(self.repo)) - self.ui.buttonCheckForUpdate.clicked.connect(lambda: self.check_for_update.emit(self.repo)) - self.ui.buttonChangeBranch.clicked.connect(self.change_branch_clicked) - self.ui.buttonEnable.clicked.connect(self.enable_clicked) - self.ui.buttonDisable.clicked.connect(self.disable_clicked) + self.ui.button_bar.back_button.clicked.connect(self.back.emit) + self.ui.button_bar.run_macro_button.clicked.connect(lambda: self.execute.emit(self.repo)) + self.ui.button_bar.install_button.clicked.connect(lambda: self.install.emit(self.repo)) + self.ui.button_bar.uninstall_button.clicked.connect(lambda: self.uninstall.emit(self.repo)) + self.ui.button_bar.update_button.clicked.connect(lambda: self.update.emit(self.repo)) + self.ui.button_bar.check_for_update_button.clicked.connect( + lambda: self.check_for_update.emit(self.repo) + ) + self.ui.button_bar.change_branch_button.clicked.connect(self.change_branch_clicked) + self.ui.button_bar.enable_button.clicked.connect(self.enable_clicked) + self.ui.button_bar.disable_button.clicked.connect(self.disable_clicked) def show_repo(self, repo: Addon, reload: bool = False) -> None: """The main entry point for this class, shows the package details and related buttons @@ -97,13 +100,13 @@ class PackageDetails(QtWidgets.QWidget): if repo.repo_type == Addon.Kind.MACRO: self.show_macro(repo) - self.ui.buttonExecute.show() + self.ui.button_bar.run_macro_button.show() elif repo.repo_type == Addon.Kind.WORKBENCH: self.show_workbench(repo) - self.ui.buttonExecute.hide() + self.ui.button_bar.run_macro_button.hide() elif repo.repo_type == Addon.Kind.PACKAGE: self.show_package(repo) - self.ui.buttonExecute.hide() + self.ui.button_bar.run_macro_button.hide() if repo.status() == Addon.Status.UNCHECKED: if not self.status_update_thread: @@ -264,35 +267,35 @@ class PackageDetails(QtWidgets.QWidget): self.ui.labelInstallationLocation.hide() if status == Addon.Status.NOT_INSTALLED: - self.ui.buttonInstall.show() - self.ui.buttonUninstall.hide() - self.ui.buttonUpdate.hide() - self.ui.buttonCheckForUpdate.hide() + self.ui.button_bar.install_button.show() + self.ui.button_bar.uninstall_button.hide() + self.ui.button_bar.update_button.hide() + self.ui.button_bar.check_for_update_button.hide() elif status == Addon.Status.NO_UPDATE_AVAILABLE: - self.ui.buttonInstall.hide() - self.ui.buttonUninstall.show() - self.ui.buttonUpdate.hide() - self.ui.buttonCheckForUpdate.hide() + self.ui.button_bar.install_button.hide() + self.ui.button_bar.uninstall_button.show() + self.ui.button_bar.update_button.hide() + self.ui.button_bar.check_for_update_button.hide() elif status == Addon.Status.UPDATE_AVAILABLE: - self.ui.buttonInstall.hide() - self.ui.buttonUninstall.show() - self.ui.buttonUpdate.show() - self.ui.buttonCheckForUpdate.hide() + self.ui.button_bar.install_button.hide() + self.ui.button_bar.uninstall_button.show() + self.ui.button_bar.update_button.show() + self.ui.button_bar.check_for_update_button.hide() elif status == Addon.Status.UNCHECKED: - self.ui.buttonInstall.hide() - self.ui.buttonUninstall.show() - self.ui.buttonUpdate.hide() - self.ui.buttonCheckForUpdate.show() + self.ui.button_bar.install_button.hide() + self.ui.button_bar.uninstall_button.show() + self.ui.button_bar.update_button.hide() + self.ui.button_bar.check_for_update_button.show() elif status == Addon.Status.PENDING_RESTART: - self.ui.buttonInstall.hide() - self.ui.buttonUninstall.show() - self.ui.buttonUpdate.hide() - self.ui.buttonCheckForUpdate.hide() + self.ui.button_bar.install_button.hide() + self.ui.button_bar.uninstall_button.show() + self.ui.button_bar.update_button.hide() + self.ui.button_bar.check_for_update_button.hide() elif status == Addon.Status.CANNOT_CHECK: - self.ui.buttonInstall.hide() - self.ui.buttonUninstall.show() - self.ui.buttonUpdate.show() - self.ui.buttonCheckForUpdate.hide() + self.ui.button_bar.install_button.hide() + self.ui.button_bar.uninstall_button.show() + self.ui.button_bar.update_button.show() + self.ui.button_bar.check_for_update_button.hide() required_version = self.requires_newer_freecad() if repo.obsolete: @@ -355,7 +358,7 @@ class PackageDetails(QtWidgets.QWidget): """The change branch button is only available for installed Addons that have a .git directory and in runs where the git is available.""" - self.ui.buttonChangeBranch.hide() + self.ui.button_bar.change_branch_button.hide() pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons") show_switcher = pref.GetBool("ShowBranchSwitcher", False) @@ -382,19 +385,19 @@ class PackageDetails(QtWidgets.QWidget): # If all four above checks passed, then it's possible for us to switch # branches, if there are any besides the one we are on: show the button - self.ui.buttonChangeBranch.show() + self.ui.button_bar.change_branch_button.show() def set_disable_button_state(self): """Set up the enable/disable button based on the enabled/disabled state of the addon""" - self.ui.buttonEnable.hide() - self.ui.buttonDisable.hide() + self.ui.button_bar.enable_button.hide() + self.ui.button_bar.disable_button.hide() status = self.repo.status() if status != Addon.Status.NOT_INSTALLED: disabled = self.repo.is_disabled() if disabled: - self.ui.buttonEnable.show() + self.ui.button_bar.enable_button.show() else: - self.ui.buttonDisable.show() + self.ui.button_bar.disable_button.show() def show_workbench(self, repo: Addon) -> None: """loads information of a given workbench""" @@ -511,59 +514,9 @@ class Ui_PackageDetails(object): self.verticalLayout_2.setObjectName("verticalLayout_2") self.layoutDetailsBackButton = QtWidgets.QHBoxLayout() self.layoutDetailsBackButton.setObjectName("layoutDetailsBackButton") - self.buttonBack = QtWidgets.QToolButton(PackageDetails) - self.buttonBack.setObjectName("buttonBack") - self.buttonBack.setIcon( - QtGui.QIcon.fromTheme("back", QtGui.QIcon(":/icons/button_left.svg")) - ) - self.layoutDetailsBackButton.addWidget(self.buttonBack) - - self.horizontalSpacer = QtWidgets.QSpacerItem( - 40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum - ) - - self.layoutDetailsBackButton.addItem(self.horizontalSpacer) - - self.buttonInstall = QtWidgets.QPushButton(PackageDetails) - self.buttonInstall.setObjectName("buttonInstall") - - self.layoutDetailsBackButton.addWidget(self.buttonInstall) - - self.buttonUninstall = QtWidgets.QPushButton(PackageDetails) - self.buttonUninstall.setObjectName("buttonUninstall") - - self.layoutDetailsBackButton.addWidget(self.buttonUninstall) - - self.buttonUpdate = QtWidgets.QPushButton(PackageDetails) - self.buttonUpdate.setObjectName("buttonUpdate") - - self.layoutDetailsBackButton.addWidget(self.buttonUpdate) - - self.buttonCheckForUpdate = QtWidgets.QPushButton(PackageDetails) - self.buttonCheckForUpdate.setObjectName("buttonCheckForUpdate") - - self.layoutDetailsBackButton.addWidget(self.buttonCheckForUpdate) - - self.buttonChangeBranch = QtWidgets.QPushButton(PackageDetails) - self.buttonChangeBranch.setObjectName("buttonChangeBranch") - - self.layoutDetailsBackButton.addWidget(self.buttonChangeBranch) - - self.buttonExecute = QtWidgets.QPushButton(PackageDetails) - self.buttonExecute.setObjectName("buttonExecute") - - self.layoutDetailsBackButton.addWidget(self.buttonExecute) - - self.buttonDisable = QtWidgets.QPushButton(PackageDetails) - self.buttonDisable.setObjectName("buttonDisable") - - self.layoutDetailsBackButton.addWidget(self.buttonDisable) - - self.buttonEnable = QtWidgets.QPushButton(PackageDetails) - self.buttonEnable.setObjectName("buttonEnable") - - self.layoutDetailsBackButton.addWidget(self.buttonEnable) + self.button_bar = WidgetAddonButtons(PackageDetails) + self.layoutDetailsBackButton.addWidget(self.button_bar) self.verticalLayout_2.addLayout(self.layoutDetailsBackButton) @@ -601,33 +554,6 @@ class Ui_PackageDetails(object): # setupUi def retranslateUi(self, _): - self.buttonBack.setText("") - self.buttonInstall.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Install", None) - ) - self.buttonUninstall.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Uninstall", None) - ) - self.buttonUpdate.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Update", None) - ) - self.buttonCheckForUpdate.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Check for Update", None) - ) - self.buttonExecute.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Run Macro", None) - ) - self.buttonChangeBranch.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Change Branch", None) - ) - self.buttonEnable.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Enable", None) - ) - self.buttonDisable.setText( - QtCore.QCoreApplication.translate("AddonsInstaller", "Disable", None) - ) - self.buttonBack.setToolTip( - QtCore.QCoreApplication.translate("AddonsInstaller", "Return to package list", None) - ) + pass # retranslateUi From 2db2fa1ac6a6e6fab9dc76ce329708c56d477934 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 4 Feb 2024 16:15:44 -0600 Subject: [PATCH 12/25] Addon Manager: Fix filter display --- .../Widgets/addonmanager_widget_filter_selector.py | 2 ++ src/Mod/AddonManager/package_list.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py index 906dc02e57..c90c9d70f8 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py @@ -173,6 +173,7 @@ class WidgetFilterSelector(QtWidgets.QComboBox): item.setCheckState(QtCore.Qt.Checked) else: item.setCheckState(QtCore.Qt.Unchecked) + self._update_first_row_text() def set_status_filter(self, status_filter: StatusFilter): model = self.model() @@ -184,6 +185,7 @@ class WidgetFilterSelector(QtWidgets.QComboBox): item.setCheckState(QtCore.Qt.Checked) else: item.setCheckState(QtCore.Qt.Unchecked) + self._update_first_row_text() def _setup_connections(self): self.activated.connect(self._selected) diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index 6b443994b1..3d9f310346 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -76,6 +76,8 @@ class PackageList(QtWidgets.QWidget): status = pref.GetInt("StatusSelection", 0) self.ui.view_bar.filter_selector.set_contents_filter(package_type) self.ui.view_bar.filter_selector.set_status_filter(status) + self.item_filter.setPackageFilter(package_type) + self.item_filter.setStatusFilter(status) # Pre-init of other members: self.item_model = None @@ -112,6 +114,7 @@ class PackageList(QtWidgets.QWidget): pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") pref.SetInt("StatusSelection", new_filter.status_filter) pref.SetInt("PackageTypeSelection", new_filter.content_filter) + self.item_filter.invalidateFilter() def set_view_style(self, style: AddonManagerDisplayStyle) -> None: """Set the style (compact or expanded) of the list""" From 0e0d53def72054503ea16f6ca7a3d84ed4906872 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 7 Feb 2024 19:29:39 -0600 Subject: [PATCH 13/25] Addon Manager: Begin refactor of listing --- ...ddonmanager_widget_package_details_view.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py new file mode 100644 index 0000000000..2653e8b431 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + + +try: + import FreeCAD + + translate = FreeCAD.Qt.translate +except ImportError: + FreeCAD = None + + def translate(_: str, text: str): + return text + + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtWidgets + +from .addonmanager_widget_addon_buttons import WidgetAddonButtons + + +class PackageDetailsView(QtWidgets.QWidget): + """The view class for the package details""" + + install_clicked = QtCore.Signal() + uninstall_clicked = QtCore.Signal() + enable_clicked = QtCore.Signal() + disable_clicked = QtCore.Signal() + update_clicked = QtCore.Signal() + check_for_updates = QtCore.Signal() + run_clicked = QtCore.Signal() + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self.button_bar = None + self.text_browser = None + self._setup_ui() + + def _setup_ui(self): + self.vertical_layout = QtWidgets.QVBoxLayout(self) + self.button_bar = WidgetAddonButtons(self) + self.text_browser = QtWidgets.QTextBrowser(self) + self.vertical_layout.addWidget(self.button_bar) + self.vertical_layout.addWidget(self.text_browser) From 81d77fdb4b8121763a6693f29c24d7a1e02f4874 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 7 Feb 2024 22:08:42 -0600 Subject: [PATCH 14/25] Addon Manager: Break up ReadmeViewer into view and controller Addon Manager: Cleanup enable/disable message --- src/Mod/AddonManager/AddonManager.py | 26 +- src/Mod/AddonManager/CMakeLists.txt | 5 +- src/Mod/AddonManager/NetworkManager.py | 12 +- src/Mod/AddonManager/Widgets/CMakeLists.txt | 3 + .../Widgets/addonmanager_colors.py | 48 ++ .../addonmanager_widget_addon_buttons.py | 58 +- .../addonmanager_widget_global_buttons.py | 2 +- ...ddonmanager_widget_package_details_view.py | 286 ++++++++- .../addonmanager_widget_readme_browser.py | 111 ++++ .../addonmanager_widget_view_selector.py | 1 + src/Mod/AddonManager/__init__.py | 0 ...addonmanager_package_details_controller.py | 258 ++++++++ ...r.py => addonmanager_readme_controller.py} | 121 ++-- src/Mod/AddonManager/package_details.py | 559 ------------------ src/__init__.py | 0 15 files changed, 811 insertions(+), 679 deletions(-) create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_colors.py create mode 100644 src/Mod/AddonManager/Widgets/addonmanager_widget_readme_browser.py create mode 100644 src/Mod/AddonManager/__init__.py create mode 100644 src/Mod/AddonManager/addonmanager_package_details_controller.py rename src/Mod/AddonManager/{addonmanager_readme_viewer.py => addonmanager_readme_controller.py} (73%) delete mode 100644 src/Mod/AddonManager/package_details.py create mode 100644 src/__init__.py diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index fac90e86ae..74333a3c51 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -52,7 +52,8 @@ from addonmanager_update_all_gui import UpdateAllGUI import addonmanager_utilities as utils import AddonManager_rc # This is required by Qt, it's not unused from package_list import PackageList, PackageListItemModel -from package_details import PackageDetails +from addonmanager_package_details_controller import PackageDetailsController +from Widgets.addonmanager_widget_package_details_view import PackageDetailsView from Widgets.addonmanager_widget_global_buttons import WidgetGlobalButtonBar from Addon import Addon from manage_python_dependencies import ( @@ -204,7 +205,8 @@ class CommandAddonManager: self.dialog.layout().addWidget(self.button_bar) # Package details start out hidden - self.packageDetails = PackageDetails(self.dialog) + self.packageDetails = PackageDetailsView(self.dialog) + self.package_details_controller = PackageDetailsController(self.packageDetails) self.packageDetails.hide() index = self.dialog.layout().indexOf(self.packageList) self.dialog.layout().insertWidget(index, self.packageDetails) @@ -238,12 +240,12 @@ class CommandAddonManager: self.packageList.ui.progressBar.stop_clicked.connect(self.stop_update) self.packageList.itemSelected.connect(self.table_row_activated) self.packageList.setEnabled(False) - self.packageDetails.execute.connect(self.executemacro) - self.packageDetails.install.connect(self.launch_installer_gui) - self.packageDetails.uninstall.connect(self.remove) - self.packageDetails.update.connect(self.update) - self.packageDetails.back.connect(self.on_buttonBack_clicked) - self.packageDetails.update_status.connect(self.status_updated) + self.package_details_controller.execute.connect(self.executemacro) + self.package_details_controller.install.connect(self.launch_installer_gui) + self.package_details_controller.uninstall.connect(self.remove) + self.package_details_controller.update.connect(self.update) + self.package_details_controller.back.connect(self.on_buttonBack_clicked) + self.package_details_controller.update_status.connect(self.status_updated) # center the dialog over the FreeCAD window mw = FreeCADGui.getMainWindow() @@ -735,7 +737,7 @@ class CommandAddonManager: self.packageList.hide() self.packageDetails.show() - self.packageDetails.show_repo(selected_repo) + self.package_details_controller.show_repo(selected_repo) def show_information(self, message: str) -> None: """shows generic text in the information pane""" @@ -746,7 +748,7 @@ class CommandAddonManager: def show_workbench(self, repo: Addon) -> None: self.packageList.hide() self.packageDetails.show() - self.packageDetails.show_repo(repo) + self.package_details_controller.show_repo(repo) def on_buttonBack_clicked(self) -> None: self.packageDetails.hide() @@ -765,7 +767,7 @@ class CommandAddonManager: else: repo.set_status(Addon.Status.NO_UPDATE_AVAILABLE) self.item_model.reload_item(repo) - self.packageDetails.show_repo(repo) + self.package_details_controller.show_repo(repo) def launch_installer_gui(self, addon: Addon) -> None: if self.installer_gui is not None: @@ -860,7 +862,7 @@ class CommandAddonManager: if repo.status() == Addon.Status.PENDING_RESTART: self.restart_required = True self.item_model.reload_item(repo) - self.packageDetails.show_repo(repo) + self.package_details_controller.show_repo(repo) if repo in self.packages_with_updates: self.packages_with_updates.remove(repo) self.enable_updates(len(self.packages_with_updates)) diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index 8877a544be..042ff80363 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -30,8 +30,9 @@ SET(AddonManager_SRCS addonmanager_macro.py addonmanager_macro_parser.py addonmanager_metadata.py + addonmanager_package_details_controller.py addonmanager_pyside_interface.py - addonmanager_readme_viewer.py + addonmanager_readme_controller.py addonmanager_update_all_gui.py addonmanager_uninstaller.py addonmanager_uninstaller_gui.py @@ -68,7 +69,7 @@ SET(AddonManager_SRCS loading.html manage_python_dependencies.py NetworkManager.py - package_details.py + addonmanager_package_details_controller.py package_list.py PythonDependencyUpdateDialog.ui select_toolbar_dialog.ui diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index 8b1c1e6e1d..d3481187f8 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -472,19 +472,21 @@ if HAVE_QTNETWORK: sender.abort() self.__launch_request(current_index, self.__create_get_request(url)) - def __on_ssl_error(self, reply: str, errors: List[str]): + def __on_ssl_error(self, reply: str, errors: List[str] = None): """Called when an SSL error occurs: prints the error information.""" if HAVE_FREECAD: FreeCAD.Console.PrintWarning( translate("AddonsInstaller", "Error with encrypted connection") + "\n:" ) FreeCAD.Console.PrintWarning(reply) - for error in errors: - FreeCAD.Console.PrintWarning(error) + if errors is not None: + for error in errors: + FreeCAD.Console.PrintWarning(error) else: print("Error with encrypted connection") - for error in errors: - print(error) + if errors is not None: + for error in errors: + print(error) def __download_progress(self, bytesReceived: int, bytesTotal: int) -> None: """Monitors download progress and emits a progress_made signal""" diff --git a/src/Mod/AddonManager/Widgets/CMakeLists.txt b/src/Mod/AddonManager/Widgets/CMakeLists.txt index 6637dad646..939e257e2e 100644 --- a/src/Mod/AddonManager/Widgets/CMakeLists.txt +++ b/src/Mod/AddonManager/Widgets/CMakeLists.txt @@ -1,9 +1,12 @@ SET(AddonManagerWidget_SRCS __init__.py + addonmanager_colors.py addonmanager_widget_addon_buttons.py addonmanager_widget_filter_selector.py addonmanager_widget_global_buttons.py + addonmanager_widget_package_details_view.py addonmanager_widget_progress_bar.py + addonmanager_widget_readme_browser.py addonmanager_widget_search.py addonmanager_widget_view_control_bar.py addonmanager_widget_view_selector.py diff --git a/src/Mod/AddonManager/Widgets/addonmanager_colors.py b/src/Mod/AddonManager/Widgets/addonmanager_colors.py new file mode 100644 index 0000000000..7d08a07421 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_colors.py @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 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 * +# * . * +# * * +# *************************************************************************** + +from enum import Enum, auto + +import FreeCADGui +from PySide import QtGui + + +def is_darkmode() -> bool: + """Heuristics to determine if we are in a darkmode stylesheet""" + pl = FreeCADGui.getMainWindow().palette() + return pl.color(QtGui.QPalette.Window).lightness() < 128 + + +def warning_color_string() -> str: + """A shade of red, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio.""" + return "rgb(255,105,97)" if is_darkmode() else "rgb(215,0,21)" + + +def bright_color_string() -> str: + """A shade of green, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio.""" + return "rgb(48,219,91)" if is_darkmode() else "rgb(36,138,61)" + + +def attention_color_string() -> str: + """A shade of orange, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio.""" + return "rgb(255,179,64)" if is_darkmode() else "rgb(255,149,0)" diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_addon_buttons.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_addon_buttons.py index 29086a9cd0..21583e8850 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_addon_buttons.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_addon_buttons.py @@ -77,39 +77,37 @@ class WidgetAddonButtons(QtWidgets.QWidget): def _setup_ui(self): self.horizontal_layout = QtWidgets.QHBoxLayout() self.horizontal_layout.setContentsMargins(0, 0, 0, 0) - self.back_button = QtWidgets.QToolButton(self) - self.install_button = QtWidgets.QPushButton(self) - self.uninstall_button = QtWidgets.QPushButton(self) - self.enable_button = QtWidgets.QPushButton(self) - self.disable_button = QtWidgets.QPushButton(self) - self.update_button = QtWidgets.QPushButton(self) - self.run_macro_button = QtWidgets.QPushButton(self) - self.change_branch_button = QtWidgets.QPushButton(self) - self.check_for_update_button = QtWidgets.QPushButton(self) - self.horizontal_layout.addWidget(self.back_button) + self.back = QtWidgets.QToolButton(self) + self.install = QtWidgets.QPushButton(self) + self.uninstall = QtWidgets.QPushButton(self) + self.enable = QtWidgets.QPushButton(self) + self.disable = QtWidgets.QPushButton(self) + self.update = QtWidgets.QPushButton(self) + self.run_macro = QtWidgets.QPushButton(self) + self.change_branch = QtWidgets.QPushButton(self) + self.check_for_update = QtWidgets.QPushButton(self) + self.horizontal_layout.addWidget(self.back) self.horizontal_layout.addStretch() - self.horizontal_layout.addWidget(self.check_for_update_button) - self.horizontal_layout.addWidget(self.install_button) - self.horizontal_layout.addWidget(self.uninstall_button) - self.horizontal_layout.addWidget(self.enable_button) - self.horizontal_layout.addWidget(self.disable_button) - self.horizontal_layout.addWidget(self.update_button) - self.horizontal_layout.addWidget(self.run_macro_button) - self.horizontal_layout.addWidget(self.change_branch_button) + self.horizontal_layout.addWidget(self.check_for_update) + self.horizontal_layout.addWidget(self.install) + self.horizontal_layout.addWidget(self.uninstall) + self.horizontal_layout.addWidget(self.enable) + self.horizontal_layout.addWidget(self.disable) + self.horizontal_layout.addWidget(self.update) + self.horizontal_layout.addWidget(self.run_macro) + self.horizontal_layout.addWidget(self.change_branch) self.setLayout(self.horizontal_layout) def _set_icons(self): - self.back_button.setIcon( - QtGui.QIcon.fromTheme("back", QtGui.QIcon(":/icons/button_left.svg")) - ) + self.back.setIcon(QtGui.QIcon.fromTheme("back", QtGui.QIcon(":/icons/button_left.svg"))) def retranslateUi(self, _): - self.check_for_update_button.setText(translate("AddonsInstaller", "Check for update")) - self.install_button.setText(translate("AddonsInstaller", "Install")) - self.uninstall_button.setText(translate("AddonsInstaller", "Uninstall")) - self.disable_button.setText(translate("AddonsInstaller", "Disable")) - self.enable_button.setText(translate("AddonsInstaller", "Enable")) - self.update_button.setText(translate("AddonsInstaller", "Update")) - self.run_macro_button.setText(translate("AddonsInstaller", "Run")) - self.change_branch_button.setText(translate("AddonsInstaller", "Change branch...")) - self.back_button.setToolTip(translate("AddonsInstaller", "Return to package list")) + self.check_for_update.setText(translate("AddonsInstaller", "Check for update")) + self.install.setText(translate("AddonsInstaller", "Install")) + self.uninstall.setText(translate("AddonsInstaller", "Uninstall")) + self.disable.setText(translate("AddonsInstaller", "Disable")) + self.enable.setText(translate("AddonsInstaller", "Enable")) + self.update.setText(translate("AddonsInstaller", "Update")) + self.run_macro.setText(translate("AddonsInstaller", "Run")) + self.change_branch.setText(translate("AddonsInstaller", "Change branch...")) + self.back.setToolTip(translate("AddonsInstaller", "Return to package list")) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py index f5660e7a89..1c293348aa 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_global_buttons.py @@ -109,5 +109,5 @@ class WidgetGlobalButtonBar(QtWidgets.QWidget): else: self.update_all_addons.setEnabled(True) self.update_all_addons.setText( - translate("AddonsInstaller", "Apply %1 available updates").format(updates) + translate("AddonsInstaller", "Apply {} available updates").format(updates) ) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py index 2653e8b431..ce83b6af6e 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * -# * Copyright (c) 2022-2024 FreeCAD Project Association * +# * Copyright (c) 2022-2024 The FreeCAD Project Association AISBL * # * * # * This file is part of FreeCAD. * # * * @@ -21,6 +21,10 @@ # * * # *************************************************************************** +from dataclasses import dataclass +from enum import Enum, auto +import os +from typing import Optional try: import FreeCAD @@ -49,28 +53,286 @@ except ImportError: from PySide import QtCore, QtWidgets from .addonmanager_widget_addon_buttons import WidgetAddonButtons +from .addonmanager_widget_readme_browser import WidgetReadmeBrowser +from .addonmanager_colors import warning_color_string, attention_color_string, bright_color_string + + +class MessageType(Enum): + Message = auto() + Warning = auto() + Error = auto() + + +@dataclass +class UpdateInformation: + check_in_progress: bool = False + update_available: bool = False + detached_head: bool = False + version: str = "" + tag: str = "" + branch: Optional[str] = None + + +@dataclass +class WarningFlags: + obsolete: bool = False + python2: bool = False + required_freecad_version: Optional[str] = None + non_osi_approved = False + non_fsf_libre = False class PackageDetailsView(QtWidgets.QWidget): """The view class for the package details""" - install_clicked = QtCore.Signal() - uninstall_clicked = QtCore.Signal() - enable_clicked = QtCore.Signal() - disable_clicked = QtCore.Signal() - update_clicked = QtCore.Signal() - check_for_updates = QtCore.Signal() - run_clicked = QtCore.Signal() - def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) self.button_bar = None - self.text_browser = None + self.readme_browser = None + self.message_label = None + self.location_label = None + self.installed = False + self.disabled = False + self.update_info = UpdateInformation() + self.warning_flags = WarningFlags() + self.installed_version = None + self.installed_branch = None + self.installed_timestamp = None + self.can_disable = True self._setup_ui() def _setup_ui(self): self.vertical_layout = QtWidgets.QVBoxLayout(self) self.button_bar = WidgetAddonButtons(self) - self.text_browser = QtWidgets.QTextBrowser(self) + self.readme_browser = WidgetReadmeBrowser(self) + self.message_label = QtWidgets.QLabel(self) + self.location_label = QtWidgets.QLabel(self) + self.location_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) self.vertical_layout.addWidget(self.button_bar) - self.vertical_layout.addWidget(self.text_browser) + self.vertical_layout.addWidget(self.message_label) + self.vertical_layout.addWidget(self.location_label) + self.vertical_layout.addWidget(self.readme_browser) + + def set_location(self, location: Optional[str]): + if location is not None: + text = ( + translate("AddonsInstaller", "Installation location") + + ": " + + os.path.normpath(location) + ) + self.location_label.setText(text) + self.location_label.show() + else: + self.location_label.hide() + + def set_installed( + self, + installed: bool, + on_date: Optional[str] = None, + version: Optional[str] = None, + branch: Optional[str] = None, + ): + self.installed = installed + self.installed_timestamp = on_date + self.installed_version = version + self.installed_branch = branch + if not self.installed: + self.set_location(None) + self._sync_ui_state() + + def set_update_available(self, info: UpdateInformation): + self.update_info = info + self._sync_ui_state() + + def set_disabled(self, disabled: bool): + self.disabled = disabled + self._sync_ui_state() + + def allow_disabling(self, allow: bool): + self.can_disable = allow + self._sync_ui_state() + + def allow_running(self, show: bool): + self.button_bar.run_macro.setVisible(show) + + def set_warning_flags(self, flags: WarningFlags): + self.warning_flags = flags + self._sync_ui_state() + + def set_new_disabled_status(self, disabled: bool): + """If the user just changed the enabled/disabled state of the addon, display a message + indicating that will not take place until restart. Do not call except in a case of a + state change during this run.""" + + if disabled: + message = translate( + "AddonsInstaller", "This Addon will be disabled next time you restart FreeCAD." + ) + else: + message = translate( + "AddonsInstaller", "This Addon will be enabled next time you restart FreeCAD." + ) + self.message_label.setText(f"

{message}

") + self.message_label.setStyleSheet("color:" + attention_color_string()) + + def set_new_branch(self, branch: str): + """If the user just changed branches, update the message to show that a restart is + needed.""" + message_string = "

" + message_string += translate( + "AddonsInstaller", "Changed to branch '{}' -- please restart to use Addon." + ).format(branch) + message_string += "

" + self.message_label.setText(message_string) + self.message_label.setStyleSheet("color:" + attention_color_string()) + + def set_updated(self): + """If the user has just updated the addon but not yet restarted, show an indication that + we are awaiting a restart.""" + message = translate( + "AddonsInstaller", "This Addon has been updated. Restart FreeCAD to see changes." + ) + self.message_label.setText(f"

{message}

") + self.message_label.setStyleSheet("color:" + attention_color_string()) + + def _sync_ui_state(self): + self._sync_button_state() + self._create_status_label_text() + + def _sync_button_state(self): + self.button_bar.install.setVisible(not self.installed) + self.button_bar.uninstall.setVisible(self.installed) + if not self.installed: + self.button_bar.disable.hide() + self.button_bar.enable.hide() + self.button_bar.update.hide() + self.button_bar.check_for_update.hide() + else: + self.button_bar.update.setVisible(self.update_info.update_available) + if self.update_info.detached_head: + self.button_bar.check_for_update.hide() + else: + self.button_bar.check_for_update.setVisible(not self.update_info.update_available) + if self.can_disable: + self.button_bar.enable.setVisible(self.disabled) + self.button_bar.disable.setVisible(not self.disabled) + else: + self.button_bar.enable.hide() + self.button_bar.disable.hide() + + def _create_status_label_text(self): + if self.installed: + installation_details = self._get_installation_details_string() + update_details = self._get_update_status_string() + message_text = f"{installation_details} {update_details}" + if self.disabled: + message_text += " [" + translate("AddonsInstaller", "Disabled") + "]" + self.message_label.setText(f"

{message_text}

") + if self.disabled: + self.message_label.setStyleSheet("color:" + warning_color_string()) + elif self.update_info.update_available: + self.message_label.setStyleSheet("color:" + attention_color_string()) + else: + self.message_label.setStyleSheet("color:" + bright_color_string()) + self.message_label.show() + elif self._there_are_warnings_to_show(): + warnings = self._get_warning_string() + self.message_label.setText(f"

{warnings}

") + self.message_label.setStyleSheet("color:" + warning_color_string()) + self.message_label.show() + else: + self.message_label.hide() + + def _get_installation_details_string(self) -> str: + version = self.installed_version + date = "" + installed_version_string = "" + if self.installed_timestamp: + date = QtCore.QLocale().toString( + QtCore.QDateTime.fromSecsSinceEpoch(int(round(self.installed_timestamp, 0))), + QtCore.QLocale.ShortFormat, + ) + if version and date: + installed_version_string += ( + translate("AddonsInstaller", "Version {version} installed on {date}").format( + version=version, date=date + ) + + ". " + ) + elif version: + installed_version_string += ( + translate("AddonsInstaller", "Version {version} installed") + "." + ).format(version=version) + elif date: + installed_version_string += ( + translate("AddonsInstaller", "Installed on {date}") + "." + ).format(date=date) + else: + installed_version_string += translate("AddonsInstaller", "Installed") + "." + return installed_version_string + + def _get_update_status_string(self) -> str: + if self.update_info.check_in_progress: + return translate("AddonsInstaller", "Update check in progress") + "." + if self.update_info.detached_head: + return ( + translate( + "AddonsInstaller", "Git tag '{}' checked out, no updates possible" + ).format(self.update_info.tag) + + "." + ) + if self.update_info.update_available: + if self.installed_branch and self.update_info.branch: + if self.installed_branch != self.update_info.branch: + return ( + translate( + "AddonsInstaller", "Currently on branch {}, name changed to {}" + ).format(self.installed_branch, self.update_info.branch) + + "." + ) + if self.update_info.version: + return ( + translate( + "AddonsInstaller", + "Currently on branch {}, update available to version {}", + ).format(self.installed_branch, str(self.update_info.version).strip()) + + "." + ) + return translate("AddonsInstaller", "Update available") + "." + if self.update_info.version: + return ( + translate("AddonsInstaller", "Update available to version {}").format( + str(self.update_info.version).strip() + ) + + "." + ) + return translate("AddonsInstaller", "Update available") + "." + return translate("AddonsInstaller", "This is the latest version available") + "." + + def _there_are_warnings_to_show(self) -> bool: + if self.disabled: + return True + if ( + self.warning_flags.obsolete + or self.warning_flags.python2 + or self.warning_flags.required_freecad_version + ): + return True + return False # TODO: Someday support optional warnings on license types + + def _get_warning_string(self) -> str: + if self.installed and self.disabled: + return translate( + "AddonsInstaller", + "WARNING: This addon is currently installed, but disabled. Use the 'enable' " + "button to re-enable.", + ) + if self.warning_flags.obsolete: + return translate("AddonsInstaller", "WARNING: This addon is obsolete") + if self.warning_flags.python2: + return translate("AddonsInstaller", "WARNING: This addon is Python 2 only") + if self.warning_flags.required_freecad_version: + return translate("AddonsInstaller", "WARNING: This addon requires FreeCAD {}").format( + self.warning_flags.required_freecad_version + ) + return "" diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_readme_browser.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_readme_browser.py new file mode 100644 index 0000000000..8049a5a434 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_readme_browser.py @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 The FreeCAD Project Association AISBL * +# * * +# * 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 * +# * . * +# * * +# *************************************************************************** + +import FreeCAD + +# Get whatever version of PySide we can +try: + import PySide # Use the FreeCAD wrapper +except ImportError: + try: + import PySide6 # Outside FreeCAD, try Qt6 first + + PySide = PySide6 + except ImportError: + import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) + + PySide = PySide2 + +from PySide import QtCore, QtGui, QtWidgets + +from typing import Optional + + +class WidgetReadmeBrowser(QtWidgets.QTextBrowser): + """A QTextBrowser widget that emits signals for each requested image resource, allowing an external controller + to load and re-deliver those images. Once all resources have been re-delivered, the original data is redisplayed + with the images in-line. Call setUrl prior to calling setMarkdown or setHtml to ensure URLs are resolved + correctly.""" + + load_resource = QtCore.Signal(str) # Str is a URL to a resource + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self.image_map = {} + self.url = "" + self.stop = False + self.setOpenExternalLinks(True) + + def setUrl(self, url: str): + """Set the base URL of the page. Used to resolve relative URLs in the page source.""" + self.url = url + + def setMarkdown(self, md: str): + """Provides an optional fallback to the markdown library for older versions of Qt (prior to 5.15) that did not + have native markdown support. Lacking that, plaintext is displayed.""" + if hasattr(super(), "setMarkdown"): + super().setMarkdown(md) + else: + try: + import markdown + + html = markdown.markdown(md) + self.setHtml(html) + except ImportError: + self.setText(md) + FreeCAD.Console.Warning( + "Qt < 5.15 and no `import markdown` -- falling back to plain text display\n" + ) + + def set_resource(self, resource_url: str, image: Optional[QtGui.QImage]): + """Once a resource has been fetched (or the fetch has failed), this method should be used to inform the widget + that the resource has been loaded. Note that the incoming image is scaled to 97% of the widget width if it is + larger than that.""" + self.image_map[resource_url] = self._ensure_appropriate_width(image) + + def loadResource(self, resource_type: int, name: QtCore.QUrl) -> object: + """Callback for resource loading. Called automatically by underlying Qt + code when external resources are needed for rendering. In particular, + here it is used to download and cache (in RAM) the images needed for the + README and Wiki pages.""" + if resource_type == QtGui.QTextDocument.ImageResource and not self.stop: + full_url = self._create_full_url(name.toString()) + if full_url not in self.image_map: + self.load_resource.emit(full_url) + self.image_map[full_url] = None + return self.image_map[full_url] + return super().loadResource(resource_type, name) + + def _ensure_appropriate_width(self, image: QtGui.QImage) -> QtGui.QImage: + ninety_seven_percent = self.width() * 0.97 + if image.width() < ninety_seven_percent: + return image + return image.scaledToWidth(ninety_seven_percent) + + def _create_full_url(self, url: str) -> str: + if url.startswith("http"): + return url + if not self.url: + return url + lhs, slash, _ = self.url.rpartition("/") + return lhs + slash + url diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py index dca81ec6f8..2bfa935bd8 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_view_selector.py @@ -119,6 +119,7 @@ class WidgetViewSelector(QtWidgets.QWidget): self.composite_button.setIcon( QtGui.QIcon.fromTheme("composite_button", QtGui.QIcon(":/icons/composite_view.svg")) ) + self.composite_button.hide() # TODO: Implement this view self.horizontal_layout.addWidget(self.compact_button) self.horizontal_layout.addWidget(self.expanded_button) diff --git a/src/Mod/AddonManager/__init__.py b/src/Mod/AddonManager/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/AddonManager/addonmanager_package_details_controller.py b/src/Mod/AddonManager/addonmanager_package_details_controller.py new file mode 100644 index 0000000000..13cb5c90b4 --- /dev/null +++ b/src/Mod/AddonManager/addonmanager_package_details_controller.py @@ -0,0 +1,258 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2024 The FreeCAD Project Association AISBL * +# * * +# * 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 * +# * . * +# * * +# *************************************************************************** + +""" Provides the PackageDetails widget. """ + +import os +from typing import Optional + +from PySide import QtCore, QtWidgets + +import addonmanager_freecad_interface as fci + +import addonmanager_utilities as utils +from addonmanager_metadata import ( + Version, + get_first_supported_freecad_version, + get_branch_from_metadata, +) +from addonmanager_workers_startup import GetMacroDetailsWorker, CheckSingleUpdateWorker +from addonmanager_git import GitManager, NoGitFound +from Addon import Addon +from change_branch import ChangeBranchDialog +from addonmanager_readme_controller import ReadmeController +from Widgets.addonmanager_widget_package_details_view import UpdateInformation, WarningFlags + +translate = fci.translate + + +class PackageDetailsController(QtCore.QObject): + """Manages the display of the package README information.""" + + back = QtCore.Signal() + install = QtCore.Signal(Addon) + uninstall = QtCore.Signal(Addon) + update = QtCore.Signal(Addon) + execute = QtCore.Signal(Addon) + update_status = QtCore.Signal(Addon) + check_for_update = QtCore.Signal(Addon) + + def __init__(self, widget=None): + super().__init__() + self.ui = widget + self.readme_controller = ReadmeController(self.ui.readme_browser) + self.worker = None + self.addon = None + self.status_update_thread = None + self.original_disabled_state = None + self.original_status = None + try: + self.git_manager = GitManager() + except NoGitFound: + self.git_manager = None + + self.ui.button_bar.back.clicked.connect(self.back.emit) + self.ui.button_bar.run_macro.clicked.connect(lambda: self.execute.emit(self.addon)) + self.ui.button_bar.install.clicked.connect(lambda: self.install.emit(self.addon)) + self.ui.button_bar.uninstall.clicked.connect(lambda: self.uninstall.emit(self.addon)) + self.ui.button_bar.update.clicked.connect(lambda: self.update.emit(self.addon)) + self.ui.button_bar.check_for_update.clicked.connect( + lambda: self.check_for_update.emit(self.addon) + ) + self.ui.button_bar.change_branch.clicked.connect(self.change_branch_clicked) + self.ui.button_bar.enable.clicked.connect(self.enable_clicked) + self.ui.button_bar.disable.clicked.connect(self.disable_clicked) + + def show_repo(self, repo: Addon) -> None: + """The main entry point for this class, shows the package details and related buttons + for the provided repo.""" + self.addon = repo + self.readme_controller.set_addon(repo) + self.original_disabled_state = self.addon.is_disabled() + + if self.worker is not None: + if not self.worker.isFinished(): + self.worker.requestInterruption() + self.worker.wait() + + installed = self.addon.status() != Addon.Status.NOT_INSTALLED + self.ui.set_installed(installed) + update_info = UpdateInformation() + if installed: + update_info.update_available = self.addon.status() == Addon.Status.UPDATE_AVAILABLE + update_info.check_in_progress = False # TODO: Implement the "check in progress" status + if repo.metadata: + update_info.branch = get_branch_from_metadata(repo.metadata) + update_info.version = repo.metadata.version + elif repo.macro: + update_info.version = repo.macro.version + self.ui.set_update_available(update_info) + self.ui.set_location(os.path.join(self.addon.mod_directory, self.addon.name)) + self.ui.set_location(os.path.join(self.addon.mod_directory, self.addon.name)) + self.ui.set_disabled(self.addon.is_disabled()) + self.ui.allow_running(repo.repo_type == Addon.Kind.MACRO) + self.ui.allow_disabling(repo.repo_type != Addon.Kind.MACRO) + + if repo.repo_type == Addon.Kind.MACRO: + self.update_macro_info(repo) + + if repo.status() == Addon.Status.UNCHECKED: + if not self.status_update_thread: + self.status_update_thread = QtCore.QThread() + self.status_create_addon_list_worker = CheckSingleUpdateWorker(repo) + self.status_create_addon_list_worker.moveToThread(self.status_update_thread) + self.status_update_thread.finished.connect( + self.status_create_addon_list_worker.deleteLater + ) + self.check_for_update.connect(self.status_create_addon_list_worker.do_work) + self.status_create_addon_list_worker.update_status.connect(self.display_repo_status) + self.status_update_thread.start() + update_info.check_in_progress = True + self.ui.set_update_available(update_info) + self.check_for_update.emit(self.addon) + + flags = WarningFlags() + flags.required_freecad_version = self.requires_newer_freecad() + flags.obsolete = repo.obsolete + flags.python2 = repo.python2 + self.ui.set_warning_flags(flags) + + def requires_newer_freecad(self) -> Optional[Version]: + """If the current package is not installed, returns the first supported version of + FreeCAD, if one is set, or None if no information is available (or if the package is + already installed).""" + + # If it's not installed, check to see if it's for a newer version of FreeCAD + if self.addon.status() == Addon.Status.NOT_INSTALLED and self.addon.metadata: + # Only hide if ALL content items require a newer version, otherwise + # it's possible that this package actually provides versions of itself + # for newer and older versions + + first_supported_version = get_first_supported_freecad_version(self.addon.metadata) + if first_supported_version is not None: + fc_version = Version(from_list=fci.Version()) + if first_supported_version > fc_version: + return first_supported_version + return None + + def set_change_branch_button_state(self): + """The change branch button is only available for installed Addons that have a .git directory + and in runs where the git is available.""" + + self.ui.button_bar.change_branch_button.hide() + + pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons") + show_switcher = pref.GetBool("ShowBranchSwitcher", False) + if not show_switcher: + return + + # Is this repo installed? If not, return. + if self.addon.status() == Addon.Status.NOT_INSTALLED: + return + + # Is it a Macro? If so, return: + if self.addon.repo_type == Addon.Kind.MACRO: + return + + # Can we actually switch branches? If not, return. + if not self.git_manager: + return + + # Is there a .git subdirectory? If not, return. + basedir = fci.getUserAppDataDir() + path_to_git = os.path.join(basedir, "Mod", self.addon.name, ".git") + if not os.path.isdir(path_to_git): + return + + # If all four above checks passed, then it's possible for us to switch + # branches, if there are any besides the one we are on: show the button + self.ui.button_bar.change_branch_button.show() + + def update_macro_info(self, repo: Addon) -> None: + if not repo.macro.url: + # We need to populate the macro information... may as well do it while the user reads + # the wiki page + self.worker = GetMacroDetailsWorker(repo) + self.worker.readme_updated.connect(self.macro_readme_updated) + self.worker.start() + + def change_branch_clicked(self) -> None: + """Loads the branch-switching dialog""" + basedir = fci.getUserAppDataDir() + path_to_repo = os.path.join(basedir, "Mod", self.addon.name) + change_branch_dialog = ChangeBranchDialog(path_to_repo, self.ui) + change_branch_dialog.branch_changed.connect(self.branch_changed) + change_branch_dialog.exec() + + def enable_clicked(self) -> None: + """Called by the Enable button, enables this Addon and updates GUI to reflect + that status.""" + self.addon.enable() + self.ui.set_disabled(False) + if self.original_disabled_state: + self.ui.set_new_disabled_status(False) + self.original_status = self.addon.status() + self.addon.set_status(Addon.Status.PENDING_RESTART) + else: + self.addon.set_status(self.original_status) + self.update_status.emit(self.addon) + + def disable_clicked(self) -> None: + """Called by the Disable button, disables this Addon and updates the GUI to + reflect that status.""" + self.addon.disable() + self.ui.set_disabled(True) + if not self.original_disabled_state: + self.ui.set_new_disabled_status(True) + self.original_status = self.addon.status() + self.addon.set_status(Addon.Status.PENDING_RESTART) + else: + self.addon.set_status(self.original_status) + self.update_status.emit(self.addon) + + def branch_changed(self, name: str) -> None: + """Displays a dialog confirming the branch changed, and tries to access the + metadata file from that branch.""" + QtWidgets.QMessageBox.information( + self.ui, + translate("AddonsInstaller", "Success"), + translate( + "AddonsInstaller", + "Branch change succeeded, please restart to use the new version.", + ), + ) + # See if this branch has a package.xml file: + basedir = fci.getUserAppDataDir() + path_to_metadata = os.path.join(basedir, "Mod", self.addon.name, "package.xml") + if os.path.isfile(path_to_metadata): + self.addon.load_metadata_file(path_to_metadata) + self.addon.installed_version = self.addon.metadata.version + else: + self.addon.repo_type = Addon.Kind.WORKBENCH + self.addon.metadata = None + self.addon.installed_version = None + self.addon.updated_timestamp = QtCore.QDateTime.currentDateTime().toSecsSinceEpoch() + self.addon.branch = name + self.addon.set_status(Addon.Status.PENDING_RESTART) + self.ui.set_new_branch(name) + self.update_status.emit(self.addon) diff --git a/src/Mod/AddonManager/addonmanager_readme_viewer.py b/src/Mod/AddonManager/addonmanager_readme_controller.py similarity index 73% rename from src/Mod/AddonManager/addonmanager_readme_viewer.py rename to src/Mod/AddonManager/addonmanager_readme_controller.py index fc3e9b19f6..1760a7ff6f 100644 --- a/src/Mod/AddonManager/addonmanager_readme_viewer.py +++ b/src/Mod/AddonManager/addonmanager_readme_controller.py @@ -23,50 +23,66 @@ """ A Qt Widget for displaying Addon README information """ -import Addon -from PySide import QtCore, QtGui, QtWidgets -from enum import Enum, auto -from html.parser import HTMLParser - -import addonmanager_freecad_interface as fci +import FreeCAD +from Addon import Addon import addonmanager_utilities as utils + +from enum import IntEnum, Enum, auto +from html.parser import HTMLParser +from typing import Optional + import NetworkManager -translate = fci.translate +translate = FreeCAD.Qt.translate + +from PySide import QtCore, QtGui -class ReadmeViewer(QtWidgets.QTextBrowser): +class ReadmeDataType(IntEnum): + PlainText = 0 + Markdown = 1 + Html = 2 - """A QTextBrowser widget that, when given an Addon, downloads the README data as appropriate - and renders it with whatever technology is available (usually Qt's Markdown renderer for - workbenches and its HTML renderer for Macros).""" - def __init__(self, parent=None): - super().__init__(parent) +class ReadmeController(QtCore.QObject): + + """A class that can provide README data from an Addon, possibly loading external resources such + as images""" + + def __init__(self, widget): + super().__init__() NetworkManager.InitializeNetworkManager() NetworkManager.AM_NETWORK_MANAGER.completed.connect(self._download_completed) self.readme_request_index = 0 self.resource_requests = {} + self.resource_failures = [] self.url = "" - self.repo: Addon.Addon = None - self.setOpenExternalLinks(True) - self.setOpenLinks(True) - self.image_map = {} + self.readme_data = None + self.readme_data_type = None + self.addon: Optional[Addon] = None self.stop = True + self.widget = widget + self.widget.load_resource.connect(self.loadResource) def set_addon(self, repo: Addon): """Set which Addon's information is displayed""" - self.setPlainText(translate("AddonsInstaller", "Loading README data...")) - self.repo = repo + self.addon = repo self.stop = False - if self.repo.repo_type == Addon.Addon.Kind.MACRO: - self.url = self.repo.macro.wiki + self.readme_data = None + if self.addon.repo_type == Addon.Kind.MACRO: + self.url = self.addon.macro.wiki if not self.url: - self.url = self.repo.macro.url + self.url = self.addon.macro.url else: self.url = utils.get_readme_url(repo) + self.widget.setUrl(self.url) + self.widget.setText( + translate("AddonsInstaller", "Loading page for {} from {}...").format( + self.addon.display_name, self.url + ) + ) self.readme_request_index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get( self.url ) @@ -77,7 +93,7 @@ class ReadmeViewer(QtWidgets.QTextBrowser): if code == 200: # HTTP success self._process_package_download(data.data().decode("utf-8")) else: - self.setPlainText( + self.widget.setText( translate( "AddonsInstaller", "Failed to download data from {} -- received response code {}.", @@ -87,47 +103,42 @@ class ReadmeViewer(QtWidgets.QTextBrowser): if code == 200: self._process_resource_download(self.resource_requests[index], data.data()) else: - self.image_map[self.resource_requests[index]] = None + FreeCAD.Console.PrintLog(f"Failed to load {self.resource_requests[index]}\n") + self.resource_failures.append(self.resource_requests[index]) del self.resource_requests[index] if not self.resource_requests: - self.set_addon(self.repo) # Trigger a reload of the page now with resources + if self.readme_data: + if self.readme_data_type == ReadmeDataType.Html: + self.widget.setHtml(self.readme_data) + elif self.readme_data_type == ReadmeDataType.Markdown: + self.widget.setMarkdown(self.readme_data) + else: + self.widget.setText(self.readme_data) + else: + self.set_addon(self.addon) # Trigger a reload of the page now with resources def _process_package_download(self, data: str): - if self.repo.repo_type == Addon.Addon.Kind.MACRO: + if self.addon.repo_type == Addon.Kind.MACRO: parser = WikiCleaner() parser.feed(data) - self.setHtml(parser.final_html) + self.readme_data = parser.final_html + self.readme_data_type = ReadmeDataType.Html + self.widget.setHtml(parser.final_html) else: - # Check for recent Qt (e.g. Qt5.15 or later). Check can be removed when - # we no longer support Ubuntu 20.04LTS for compiling. - if hasattr(self, "setMarkdown"): - self.setMarkdown(data) - else: - self.setPlainText(data) + self.readme_data = data + self.readme_data_type = ReadmeDataType.Markdown + self.widget.setMarkdown(data) def _process_resource_download(self, resource_name: str, resource_data: bytes): image = QtGui.QImage.fromData(resource_data) - if image: - self.image_map[resource_name] = self._ensure_appropriate_width(image) - else: - self.image_map[resource_name] = None + self.widget.set_resource(resource_name, image) - def loadResource(self, resource_type: int, name: QtCore.QUrl) -> object: - """Callback for resource loading. Called automatically by underlying Qt - code when external resources are needed for rendering. In particular, - here it is used to download and cache (in RAM) the images needed for the - README and Wiki pages.""" - if resource_type == QtGui.QTextDocument.ImageResource and not self.stop: - full_url = self._create_full_url(name.toString()) - if full_url not in self.image_map: - self.image_map[full_url] = None - fci.Console.PrintMessage(f"Downloading image from {full_url}...\n") - index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(full_url) - self.resource_requests[index] = full_url - return self.image_map[full_url] - return super().loadResource(resource_type, name) + def loadResource(self, full_url: str): + if full_url not in self.resource_failures: + index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(full_url) + self.resource_requests[index] = full_url - def hideEvent(self, event: QtGui.QHideEvent): + def cancel_resource_loading(self): self.stop = True for request in self.resource_requests: NetworkManager.AM_NETWORK_MANAGER.abort(request) @@ -141,12 +152,6 @@ class ReadmeViewer(QtWidgets.QTextBrowser): lhs, slash, _ = self.url.rpartition("/") return lhs + slash + url - def _ensure_appropriate_width(self, image: QtGui.QImage) -> QtGui.QImage: - ninety_seven_percent = self.width() * 0.97 - if image.width() < ninety_seven_percent: - return image - return image.scaledToWidth(ninety_seven_percent) - class WikiCleaner(HTMLParser): """This HTML parser cleans up FreeCAD Macro Wiki Page for display in a diff --git a/src/Mod/AddonManager/package_details.py b/src/Mod/AddonManager/package_details.py deleted file mode 100644 index 2b6e4a824c..0000000000 --- a/src/Mod/AddonManager/package_details.py +++ /dev/null @@ -1,559 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -# *************************************************************************** -# * * -# * Copyright (c) 2022-2023 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 * -# * . * -# * * -# *************************************************************************** - -""" Provides the PackageDetails widget. """ - -import os -from typing import Optional - -from PySide import QtCore, QtGui, QtWidgets - -import addonmanager_freecad_interface as fci - -import addonmanager_utilities as utils -from addonmanager_metadata import ( - Version, - get_first_supported_freecad_version, - get_branch_from_metadata, -) -from addonmanager_workers_startup import GetMacroDetailsWorker, CheckSingleUpdateWorker -from addonmanager_readme_viewer import ReadmeViewer -from addonmanager_git import GitManager, NoGitFound -from Addon import Addon -from change_branch import ChangeBranchDialog -from Widgets.addonmanager_widget_addon_buttons import WidgetAddonButtons - -translate = fci.translate - - -class PackageDetails(QtWidgets.QWidget): - """The PackageDetails QWidget shows package README information and provides - install, uninstall, and update buttons.""" - - back = QtCore.Signal() - install = QtCore.Signal(Addon) - uninstall = QtCore.Signal(Addon) - update = QtCore.Signal(Addon) - execute = QtCore.Signal(Addon) - update_status = QtCore.Signal(Addon) - check_for_update = QtCore.Signal(Addon) - - def __init__(self, parent=None): - super().__init__(parent) - self.ui = Ui_PackageDetails() - self.ui.setupUi(self) - - self.worker = None - self.repo = None - self.status_update_thread = None - try: - self.git_manager = GitManager() - except NoGitFound: - self.git_manager = None - - self.ui.button_bar.back_button.clicked.connect(self.back.emit) - self.ui.button_bar.run_macro_button.clicked.connect(lambda: self.execute.emit(self.repo)) - self.ui.button_bar.install_button.clicked.connect(lambda: self.install.emit(self.repo)) - self.ui.button_bar.uninstall_button.clicked.connect(lambda: self.uninstall.emit(self.repo)) - self.ui.button_bar.update_button.clicked.connect(lambda: self.update.emit(self.repo)) - self.ui.button_bar.check_for_update_button.clicked.connect( - lambda: self.check_for_update.emit(self.repo) - ) - self.ui.button_bar.change_branch_button.clicked.connect(self.change_branch_clicked) - self.ui.button_bar.enable_button.clicked.connect(self.enable_clicked) - self.ui.button_bar.disable_button.clicked.connect(self.disable_clicked) - - def show_repo(self, repo: Addon, reload: bool = False) -> None: - """The main entry point for this class, shows the package details and related buttons - for the provided repo. If reload is true, then even if this is already the current repo - the data is reloaded.""" - - # If this is the same repo we were already showing, we do not have to do the - # expensive refetch unless reload is true - if True or self.repo != repo or reload: - self.repo = repo - - if self.worker is not None: - if not self.worker.isFinished(): - self.worker.requestInterruption() - self.worker.wait() - - if repo.repo_type == Addon.Kind.MACRO: - self.show_macro(repo) - self.ui.button_bar.run_macro_button.show() - elif repo.repo_type == Addon.Kind.WORKBENCH: - self.show_workbench(repo) - self.ui.button_bar.run_macro_button.hide() - elif repo.repo_type == Addon.Kind.PACKAGE: - self.show_package(repo) - self.ui.button_bar.run_macro_button.hide() - - if repo.status() == Addon.Status.UNCHECKED: - if not self.status_update_thread: - self.status_update_thread = QtCore.QThread() - self.status_create_addon_list_worker = CheckSingleUpdateWorker(repo) - self.status_create_addon_list_worker.moveToThread(self.status_update_thread) - self.status_update_thread.finished.connect( - self.status_create_addon_list_worker.deleteLater - ) - self.check_for_update.connect(self.status_create_addon_list_worker.do_work) - self.status_create_addon_list_worker.update_status.connect(self.display_repo_status) - self.status_update_thread.start() - self.check_for_update.emit(self.repo) - - self.display_repo_status(self.repo.update_status) - - def display_repo_status(self, status): - """Updates the contents of the widget to display the current install status of the widget.""" - repo = self.repo - self.set_change_branch_button_state() - self.set_disable_button_state() - if status != Addon.Status.NOT_INSTALLED: - version = repo.installed_version - date = "" - installed_version_string = "

" - if repo.updated_timestamp: - date = QtCore.QLocale().toString( - QtCore.QDateTime.fromSecsSinceEpoch(int(round(repo.updated_timestamp, 0))), - QtCore.QLocale.ShortFormat, - ) - if version and date: - installed_version_string += ( - translate("AddonsInstaller", "Version {version} installed on {date}").format( - version=version, date=date - ) - + ". " - ) - elif version: - installed_version_string += ( - translate("AddonsInstaller", "Version {version} installed") + ". " - ).format(version=version) - elif date: - installed_version_string += ( - translate("AddonsInstaller", "Installed on {date}") + ". " - ).format(date=date) - else: - installed_version_string += translate("AddonsInstaller", "Installed") + ". " - - if status == Addon.Status.UPDATE_AVAILABLE: - if repo.metadata: - name_change = False - if repo.installed_metadata: - old_branch = get_branch_from_metadata(repo.installed_metadata) - new_branch = get_branch_from_metadata(repo.metadata) - if old_branch != new_branch: - installed_version_string += ( - "" - + translate( - "AddonsInstaller", - "Currently on branch {}, name changed to {}", - ).format(old_branch, new_branch) - ) + ". " - name_change = True - if not name_change: - installed_version_string += ( - "" - + translate( - "AddonsInstaller", - "On branch {}, update available to version", - ).format(repo.branch) - + " " - ) - installed_version_string += str(repo.metadata.version) - installed_version_string += "." - elif repo.macro and repo.macro.version: - installed_version_string += ( - "" + translate("AddonsInstaller", "Update available to version") + " " - ) - installed_version_string += repo.macro.version - installed_version_string += "." - else: - installed_version_string += ( - "" - + translate( - "AddonsInstaller", - "An update is available", - ) - + "." - ) - elif status == Addon.Status.NO_UPDATE_AVAILABLE: - detached_head = False - branch = repo.branch - if self.git_manager and repo.repo_type != Addon.Kind.MACRO: - basedir = fci.getUserAppDataDir() - moddir = os.path.join(basedir, "Mod", repo.name) - repo_path = os.path.join(moddir, ".git") - if os.path.exists(repo_path): - branch = self.git_manager.current_branch(repo_path) - if self.git_manager.detached_head(repo_path): - tag = self.git_manager.current_tag(repo_path) - branch = tag - detached_head = True - if detached_head: - installed_version_string += ( - translate( - "AddonsInstaller", - "Git tag '{}' checked out, no updates possible", - ).format(branch) - + "." - ) - else: - installed_version_string += ( - translate( - "AddonsInstaller", - "This is the latest version available for branch {}", - ).format(branch) - + "." - ) - elif status == Addon.Status.PENDING_RESTART: - installed_version_string += ( - translate("AddonsInstaller", "Updated, please restart FreeCAD to use") + "." - ) - elif status == Addon.Status.UNCHECKED: - pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons") - autocheck = pref.GetBool("AutoCheck", False) - if autocheck: - installed_version_string += ( - translate("AddonsInstaller", "Update check in progress") + "." - ) - else: - installed_version_string += ( - translate("AddonsInstaller", "Automatic update checks disabled") + "." - ) - - installed_version_string += "

" - self.ui.labelPackageDetails.setText(installed_version_string) - if repo.status() == Addon.Status.UPDATE_AVAILABLE: - self.ui.labelPackageDetails.setStyleSheet("color:" + utils.attention_color_string()) - else: - self.ui.labelPackageDetails.setStyleSheet("color:" + utils.bright_color_string()) - self.ui.labelPackageDetails.show() - - if repo.macro is not None: - moddir = fci.getUserMacroDir(True) - else: - basedir = fci.getUserAppDataDir() - moddir = os.path.join(basedir, "Mod", repo.name) - installationLocationString = ( - translate("AddonsInstaller", "Installation location") - + ": " - + os.path.normpath(moddir) - ) - - self.ui.labelInstallationLocation.setText(installationLocationString) - self.ui.labelInstallationLocation.show() - else: - self.ui.labelPackageDetails.hide() - self.ui.labelInstallationLocation.hide() - - if status == Addon.Status.NOT_INSTALLED: - self.ui.button_bar.install_button.show() - self.ui.button_bar.uninstall_button.hide() - self.ui.button_bar.update_button.hide() - self.ui.button_bar.check_for_update_button.hide() - elif status == Addon.Status.NO_UPDATE_AVAILABLE: - self.ui.button_bar.install_button.hide() - self.ui.button_bar.uninstall_button.show() - self.ui.button_bar.update_button.hide() - self.ui.button_bar.check_for_update_button.hide() - elif status == Addon.Status.UPDATE_AVAILABLE: - self.ui.button_bar.install_button.hide() - self.ui.button_bar.uninstall_button.show() - self.ui.button_bar.update_button.show() - self.ui.button_bar.check_for_update_button.hide() - elif status == Addon.Status.UNCHECKED: - self.ui.button_bar.install_button.hide() - self.ui.button_bar.uninstall_button.show() - self.ui.button_bar.update_button.hide() - self.ui.button_bar.check_for_update_button.show() - elif status == Addon.Status.PENDING_RESTART: - self.ui.button_bar.install_button.hide() - self.ui.button_bar.uninstall_button.show() - self.ui.button_bar.update_button.hide() - self.ui.button_bar.check_for_update_button.hide() - elif status == Addon.Status.CANNOT_CHECK: - self.ui.button_bar.install_button.hide() - self.ui.button_bar.uninstall_button.show() - self.ui.button_bar.update_button.show() - self.ui.button_bar.check_for_update_button.hide() - - required_version = self.requires_newer_freecad() - if repo.obsolete: - self.ui.labelWarningInfo.show() - self.ui.labelWarningInfo.setText( - "

" + translate("AddonsInstaller", "WARNING: This addon is obsolete") + "

" - ) - self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string()) - elif repo.python2: - self.ui.labelWarningInfo.show() - self.ui.labelWarningInfo.setText( - "

" - + translate("AddonsInstaller", "WARNING: This addon is Python 2 Only") - + "

" - ) - self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string()) - elif required_version: - self.ui.labelWarningInfo.show() - self.ui.labelWarningInfo.setText( - "

" - + translate("AddonsInstaller", "WARNING: This addon requires FreeCAD ") - + required_version - + "

" - ) - self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string()) - elif repo.is_disabled(): - self.ui.labelWarningInfo.show() - self.ui.labelWarningInfo.setText( - "

" - + translate( - "AddonsInstaller", - "WARNING: This addon is currently installed, but disabled. Use the 'enable' button to re-enable.", - ) - + "

" - ) - self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string()) - - else: - self.ui.labelWarningInfo.hide() - - def requires_newer_freecad(self) -> Optional[Version]: - """If the current package is not installed, returns the first supported version of - FreeCAD, if one is set, or None if no information is available (or if the package is - already installed).""" - - # If it's not installed, check to see if it's for a newer version of FreeCAD - if self.repo.status() == Addon.Status.NOT_INSTALLED and self.repo.metadata: - # Only hide if ALL content items require a newer version, otherwise - # it's possible that this package actually provides versions of itself - # for newer and older versions - - first_supported_version = get_first_supported_freecad_version(self.repo.metadata) - if first_supported_version is not None: - fc_version = Version(from_list=fci.Version()) - if first_supported_version > fc_version: - return first_supported_version - return None - - def set_change_branch_button_state(self): - """The change branch button is only available for installed Addons that have a .git directory - and in runs where the git is available.""" - - self.ui.button_bar.change_branch_button.hide() - - pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons") - show_switcher = pref.GetBool("ShowBranchSwitcher", False) - if not show_switcher: - return - - # Is this repo installed? If not, return. - if self.repo.status() == Addon.Status.NOT_INSTALLED: - return - - # Is it a Macro? If so, return: - if self.repo.repo_type == Addon.Kind.MACRO: - return - - # Can we actually switch branches? If not, return. - if not self.git_manager: - return - - # Is there a .git subdirectory? If not, return. - basedir = fci.getUserAppDataDir() - path_to_git = os.path.join(basedir, "Mod", self.repo.name, ".git") - if not os.path.isdir(path_to_git): - return - - # If all four above checks passed, then it's possible for us to switch - # branches, if there are any besides the one we are on: show the button - self.ui.button_bar.change_branch_button.show() - - def set_disable_button_state(self): - """Set up the enable/disable button based on the enabled/disabled state of the addon""" - self.ui.button_bar.enable_button.hide() - self.ui.button_bar.disable_button.hide() - status = self.repo.status() - if status != Addon.Status.NOT_INSTALLED: - disabled = self.repo.is_disabled() - if disabled: - self.ui.button_bar.enable_button.show() - else: - self.ui.button_bar.disable_button.show() - - def show_workbench(self, repo: Addon) -> None: - """loads information of a given workbench""" - - self.ui.textBrowserReadMe.set_addon(repo) - - def show_package(self, repo: Addon) -> None: - """Show the details for a package (a repo with a package.xml metadata file)""" - - self.ui.textBrowserReadMe.set_addon(repo) - - def show_macro(self, repo: Addon) -> None: - """loads information of a given macro""" - - if not repo.macro.url: - # We need to populate the macro information... may as well do it while the user reads the wiki page - self.worker = GetMacroDetailsWorker(repo) - self.worker.readme_updated.connect(self.macro_readme_updated) - self.worker.start() - else: - self.macro_readme_updated() - - def macro_readme_updated(self): - """Update the display of a Macro's README data.""" - - self.ui.textBrowserReadMe.set_addon(self.repo) - - def change_branch_clicked(self) -> None: - """Loads the branch-switching dialog""" - basedir = fci.getUserAppDataDir() - path_to_repo = os.path.join(basedir, "Mod", self.repo.name) - change_branch_dialog = ChangeBranchDialog(path_to_repo, self) - change_branch_dialog.branch_changed.connect(self.branch_changed) - change_branch_dialog.exec() - - def enable_clicked(self) -> None: - """Called by the Enable button, enables this Addon and updates GUI to reflect - that status.""" - self.repo.enable() - self.repo.set_status(Addon.Status.PENDING_RESTART) - self.set_disable_button_state() - self.update_status.emit(self.repo) - self.ui.labelWarningInfo.show() - self.ui.labelWarningInfo.setText( - "

" - + translate( - "AddonsInstaller", - "This Addon will be enabled next time you restart FreeCAD.", - ) - + "

" - ) - self.ui.labelWarningInfo.setStyleSheet("color:" + utils.bright_color_string()) - - def disable_clicked(self) -> None: - """Called by the Disable button, disables this Addon and updates the GUI to - reflect that status.""" - self.repo.disable() - self.repo.set_status(Addon.Status.PENDING_RESTART) - self.set_disable_button_state() - self.update_status.emit(self.repo) - self.ui.labelWarningInfo.show() - self.ui.labelWarningInfo.setText( - "

" - + translate( - "AddonsInstaller", - "This Addon will be disabled next time you restart FreeCAD.", - ) - + "

" - ) - self.ui.labelWarningInfo.setStyleSheet("color:" + utils.attention_color_string()) - - def branch_changed(self, name: str) -> None: - """Displays a dialog confirming the branch changed, and tries to access the - metadata file from that branch.""" - QtWidgets.QMessageBox.information( - self, - translate("AddonsInstaller", "Success"), - translate( - "AddonsInstaller", - "Branch change succeeded, please restart to use the new version.", - ), - ) - # See if this branch has a package.xml file: - basedir = fci.getUserAppDataDir() - path_to_metadata = os.path.join(basedir, "Mod", self.repo.name, "package.xml") - if os.path.isfile(path_to_metadata): - self.repo.load_metadata_file(path_to_metadata) - self.repo.installed_version = self.repo.metadata.version - else: - self.repo.repo_type = Addon.Kind.WORKBENCH - self.repo.metadata = None - self.repo.installed_version = None - self.repo.updated_timestamp = QtCore.QDateTime.currentDateTime().toSecsSinceEpoch() - self.repo.branch = name - self.repo.set_status(Addon.Status.PENDING_RESTART) - - installed_version_string = "

" - installed_version_string += translate( - "AddonsInstaller", "Changed to git ref '{}' -- please restart to use Addon." - ).format(name) - installed_version_string += "

" - self.ui.labelPackageDetails.setText(installed_version_string) - self.ui.labelPackageDetails.setStyleSheet("color:" + utils.attention_color_string()) - self.update_status.emit(self.repo) - - -class Ui_PackageDetails(object): - """The generated UI from the Qt Designer UI file""" - - def setupUi(self, PackageDetails): - if not PackageDetails.objectName(): - PackageDetails.setObjectName("PackageDetails") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(PackageDetails) - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.layoutDetailsBackButton = QtWidgets.QHBoxLayout() - self.layoutDetailsBackButton.setObjectName("layoutDetailsBackButton") - - self.button_bar = WidgetAddonButtons(PackageDetails) - self.layoutDetailsBackButton.addWidget(self.button_bar) - - self.verticalLayout_2.addLayout(self.layoutDetailsBackButton) - - self.labelPackageDetails = QtWidgets.QLabel(PackageDetails) - self.labelPackageDetails.hide() - - self.verticalLayout_2.addWidget(self.labelPackageDetails) - - self.labelInstallationLocation = QtWidgets.QLabel(PackageDetails) - self.labelInstallationLocation.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) - self.labelInstallationLocation.hide() - - self.verticalLayout_2.addWidget(self.labelInstallationLocation) - - self.labelWarningInfo = QtWidgets.QLabel(PackageDetails) - self.labelWarningInfo.hide() - - self.verticalLayout_2.addWidget(self.labelWarningInfo) - - sizePolicy1 = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding - ) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - - self.textBrowserReadMe = ReadmeViewer(PackageDetails) - self.textBrowserReadMe.setObjectName("textBrowserReadMe") - - self.verticalLayout_2.addWidget(self.textBrowserReadMe) - - self.retranslateUi(PackageDetails) - - QtCore.QMetaObject.connectSlotsByName(PackageDetails) - - # setupUi - - def retranslateUi(self, _): - pass - - # retranslateUi diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 018721728b59aa3b425a87ab8c4edd4c0fcd4768 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 8 Feb 2024 20:25:33 -0500 Subject: [PATCH 15/25] Addon Manager/Tests: Update tests --- .../AddonManagerTest/data/depends_on_all_workbenches.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/AddonManager/AddonManagerTest/data/depends_on_all_workbenches.xml b/src/Mod/AddonManager/AddonManagerTest/data/depends_on_all_workbenches.xml index 79a5c6145a..2d705d5d40 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/depends_on_all_workbenches.xml +++ b/src/Mod/AddonManager/AddonManagerTest/data/depends_on_all_workbenches.xml @@ -14,6 +14,7 @@ MyFirstWorkbench Resources/icons/PackageIcon.svg Arch + Assembly DraftWB FEM WB MeshWorkbench From b46631994c82266fd9f90854025feee88bead848 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 9 Feb 2024 11:17:46 -0500 Subject: [PATCH 16/25] Addon Manager: Calculate width for filter box --- .../Widgets/addonmanager_widget_filter_selector.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py index c90c9d70f8..7def8903eb 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py @@ -96,6 +96,7 @@ class WidgetFilterSelector(QtWidgets.QComboBox): self._setup_connections() self.retranslateUi(None) self.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + self.extra_padding = 64 def _setup_ui(self): self._build_menu() @@ -190,8 +191,17 @@ class WidgetFilterSelector(QtWidgets.QComboBox): def _setup_connections(self): self.activated.connect(self._selected) + def _adjust_dropdown_width(self): + max_width = 0 + font_metrics = self.fontMetrics() + for index in range(self.count()): + width = font_metrics.width(self.itemText(index)) + max_width = max(max_width, width) + self.view().setMinimumWidth(max_width + self.extra_padding) + def retranslateUi(self, _): self._build_menu() + self._adjust_dropdown_width() def _selected(self, row: int): if row == 0: From 6d6317757a863664e41a5126fe9a04562dd66af1 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 9 Feb 2024 12:27:03 -0500 Subject: [PATCH 17/25] Addon Manager: Cleanup warnings --- .../Widgets/addonmanager_widget_filter_selector.py | 2 +- .../AddonManager/Widgets/addonmanager_widget_progress_bar.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py index 7def8903eb..943f77951a 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_filter_selector.py @@ -92,11 +92,11 @@ class WidgetFilterSelector(QtWidgets.QComboBox): super().__init__(parent) self.addon_type_index = 0 self.installation_status_index = 0 + self.extra_padding = 64 self._setup_ui() self._setup_connections() self.retranslateUi(None) self.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) - self.extra_padding = 64 def _setup_ui(self): self._build_menu() diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py index f52d73b806..a2f6bdebce 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py @@ -72,7 +72,7 @@ class WidgetProgressBar(QtWidgets.QWidget): def _setup_ui(self): self.vertical_layout = QtWidgets.QVBoxLayout(self) - self.horizontal_layout = QtWidgets.QHBoxLayout(self) + self.horizontal_layout = QtWidgets.QHBoxLayout() self.progress_bar = QtWidgets.QProgressBar(self) self.status_label = QtWidgets.QLabel(self) self.stop_button = QtWidgets.QToolButton(self) @@ -87,7 +87,6 @@ class WidgetProgressBar(QtWidgets.QWidget): self.horizontal_layout.addWidget(self.stop_button) self.vertical_layout.setContentsMargins(0, 0, 0, 0) self.horizontal_layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(self.vertical_layout) def set_status(self, status: str): self.status_label.setText(status) From 43cb58f79e01916f96b4c10f798223f70eeed923 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 9 Feb 2024 19:09:59 -0500 Subject: [PATCH 18/25] Addon Manager: Fixes for license handling --- src/Mod/AddonManager/Addon.py | 2 + src/Mod/AddonManager/NetworkManager.py | 43 +++++++++++---- .../AddonManager/Resources/AddonManager.qrc | 10 ++-- .../licenses/{CC0v1.txt => CC0-1.0.txt} | 0 .../{GPLv2.txt => GPL-2.0-or-later.txt} | 0 .../{GPLv3.txt => GPL-3.0-or-later.txt} | 0 .../{LGPLv2.1.txt => LGPL-2.1-or-later.txt} | 0 .../{LGPLv3.txt => LGPL-3.0-or-later.txt} | 0 .../addonmanager_devmode_license_selector.py | 12 ++--- src/Mod/AddonManager/addonmanager_licenses.py | 53 ++++++++++++++++++- src/Mod/AddonManager/addonmanager_macro.py | 14 +++-- .../AddonManager/addonmanager_macro_parser.py | 12 +++++ src/Mod/AddonManager/addonmanager_metadata.py | 8 ++- .../addonmanager_workers_utility.py | 4 +- src/Mod/AddonManager/package_list.py | 39 ++++++++++++-- 15 files changed, 162 insertions(+), 35 deletions(-) rename src/Mod/AddonManager/Resources/licenses/{CC0v1.txt => CC0-1.0.txt} (100%) rename src/Mod/AddonManager/Resources/licenses/{GPLv2.txt => GPL-2.0-or-later.txt} (100%) rename src/Mod/AddonManager/Resources/licenses/{GPLv3.txt => GPL-3.0-or-later.txt} (100%) rename src/Mod/AddonManager/Resources/licenses/{LGPLv2.1.txt => LGPL-2.1-or-later.txt} (100%) rename src/Mod/AddonManager/Resources/licenses/{LGPLv3.txt => LGPL-3.0-or-later.txt} (100%) diff --git a/src/Mod/AddonManager/Addon.py b/src/Mod/AddonManager/Addon.py index 59a360b6e3..9af58937dd 100644 --- a/src/Mod/AddonManager/Addon.py +++ b/src/Mod/AddonManager/Addon.py @@ -227,6 +227,8 @@ class Addon: self._cached_license = self.metadata.license elif self.stats and self.stats.license: self._cached_license = self.stats.license + elif self.macro and self.macro.license: + self._cached_license = self.macro.license return self._cached_license @classmethod diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index d3481187f8..98a1be16be 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -315,7 +315,11 @@ if HAVE_QTNETWORK: reply.readyRead.connect(self.__ready_to_read) reply.downloadProgress.connect(self.__download_progress) - def submit_unmonitored_get(self, url: str) -> int: + def submit_unmonitored_get( + self, + url: str, + timeout_ms: int = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant, + ) -> int: """Adds this request to the queue, and returns an index that can be used by calling code in conjunction with the completed() signal to handle the results of the call. All data is kept in memory, and the completed() call includes a direct handle to the bytes returned. It @@ -324,12 +328,18 @@ if HAVE_QTNETWORK: current_index = next(self.counting_iterator) # A thread-safe counter # Use a queue because we can only put things on the QNAM from the main event loop thread self.queue.put( - QueueItem(current_index, self.__create_get_request(url), track_progress=False) + QueueItem( + current_index, self.__create_get_request(url, timeout_ms), track_progress=False + ) ) self.__request_queued.emit() return current_index - def submit_monitored_get(self, url: str) -> int: + def submit_monitored_get( + self, + url: str, + timeout_ms: int = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant, + ) -> int: """Adds this request to the queue, and returns an index that can be used by calling code in conjunction with the progress_made() and progress_completed() signals to handle the results of the call. All data is cached to disk, and progress is reported periodically @@ -340,12 +350,18 @@ if HAVE_QTNETWORK: current_index = next(self.counting_iterator) # A thread-safe counter # Use a queue because we can only put things on the QNAM from the main event loop thread self.queue.put( - QueueItem(current_index, self.__create_get_request(url), track_progress=True) + QueueItem( + current_index, self.__create_get_request(url, timeout_ms), track_progress=True + ) ) self.__request_queued.emit() return current_index - def blocking_get(self, url: str) -> Optional[QtCore.QByteArray]: + def blocking_get( + self, + url: str, + timeout_ms: int = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant, + ) -> Optional[QtCore.QByteArray]: """Submits a GET request to the QNetworkAccessManager and block until it is complete""" current_index = next(self.counting_iterator) # A thread-safe counter @@ -353,7 +369,9 @@ if HAVE_QTNETWORK: self.synchronous_complete[current_index] = False self.queue.put( - QueueItem(current_index, self.__create_get_request(url), track_progress=False) + QueueItem( + current_index, self.__create_get_request(url, timeout_ms), track_progress=False + ) ) self.__request_queued.emit() while True: @@ -388,7 +406,7 @@ if HAVE_QTNETWORK: ) self.synchronous_complete[index] = True - def __create_get_request(self, url: str) -> QtNetwork.QNetworkRequest: + def __create_get_request(self, url: str, timeout_ms: int) -> QtNetwork.QNetworkRequest: """Construct a network request to a given URL""" request = QtNetwork.QNetworkRequest(QtCore.QUrl(url)) request.setAttribute( @@ -400,6 +418,7 @@ if HAVE_QTNETWORK: QtNetwork.QNetworkRequest.CacheLoadControlAttribute, QtNetwork.QNetworkRequest.PreferNetwork, ) + request.setTransferTimeout(timeout_ms) return request def abort_all(self): @@ -428,7 +447,8 @@ if HAVE_QTNETWORK: authenticator: QtNetwork.QAuthenticator, ): """If proxy authentication is required, attempt to authenticate. If the GUI is running this displays - a window asking for credentials. If the GUI is not running, it prompts on the command line.""" + a window asking for credentials. If the GUI is not running, it prompts on the command line. + """ if HAVE_FREECAD and FreeCAD.GuiUp: proxy_authentication = FreeCADGui.PySideUic.loadUi( os.path.join(os.path.dirname(__file__), "proxy_authentication.ui") @@ -463,6 +483,9 @@ if HAVE_QTNETWORK: def __follow_redirect(self, url): """Used with the QNetworkAccessManager to follow redirects.""" sender = self.sender() + current_index = -1 + timeout_ms = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant + # TODO: Figure out what the actual timeout value should be from the original request if sender: for index, reply in self.replies.items(): if reply == sender: @@ -470,7 +493,8 @@ if HAVE_QTNETWORK: break sender.abort() - self.__launch_request(current_index, self.__create_get_request(url)) + if current_index != -1: + self.__launch_request(current_index, self.__create_get_request(url, timeout_ms)) def __on_ssl_error(self, reply: str, errors: List[str] = None): """Called when an SSL error occurs: prints the error information.""" @@ -620,7 +644,6 @@ def InitializeNetworkManager(): if __name__ == "__main__": - app = QtCore.QCoreApplication() InitializeNetworkManager() diff --git a/src/Mod/AddonManager/Resources/AddonManager.qrc b/src/Mod/AddonManager/Resources/AddonManager.qrc index a1f12a12d8..a2a0b75b85 100644 --- a/src/Mod/AddonManager/Resources/AddonManager.qrc +++ b/src/Mod/AddonManager/Resources/AddonManager.qrc @@ -69,11 +69,11 @@ licenses/Apache-2.0.txt licenses/BSD-2-Clause.txt licenses/BSD-3-Clause.txt - licenses/CC0v1.txt - licenses/GPLv2.txt - licenses/GPLv3.txt - licenses/LGPLv2.1.txt - licenses/LGPLv3.txt + licenses/CC0-1.0.txt + licenses/GPL-2.0-or-later.txt + licenses/GPL-3.0-or-later.txt + licenses/LGPL-2.1-or-later.txt + licenses/LGPL-3.0-or-later.txt licenses/MIT.txt licenses/MPL-2.0.txt licenses/spdx.json diff --git a/src/Mod/AddonManager/Resources/licenses/CC0v1.txt b/src/Mod/AddonManager/Resources/licenses/CC0-1.0.txt similarity index 100% rename from src/Mod/AddonManager/Resources/licenses/CC0v1.txt rename to src/Mod/AddonManager/Resources/licenses/CC0-1.0.txt diff --git a/src/Mod/AddonManager/Resources/licenses/GPLv2.txt b/src/Mod/AddonManager/Resources/licenses/GPL-2.0-or-later.txt similarity index 100% rename from src/Mod/AddonManager/Resources/licenses/GPLv2.txt rename to src/Mod/AddonManager/Resources/licenses/GPL-2.0-or-later.txt diff --git a/src/Mod/AddonManager/Resources/licenses/GPLv3.txt b/src/Mod/AddonManager/Resources/licenses/GPL-3.0-or-later.txt similarity index 100% rename from src/Mod/AddonManager/Resources/licenses/GPLv3.txt rename to src/Mod/AddonManager/Resources/licenses/GPL-3.0-or-later.txt diff --git a/src/Mod/AddonManager/Resources/licenses/LGPLv2.1.txt b/src/Mod/AddonManager/Resources/licenses/LGPL-2.1-or-later.txt similarity index 100% rename from src/Mod/AddonManager/Resources/licenses/LGPLv2.1.txt rename to src/Mod/AddonManager/Resources/licenses/LGPL-2.1-or-later.txt diff --git a/src/Mod/AddonManager/Resources/licenses/LGPLv3.txt b/src/Mod/AddonManager/Resources/licenses/LGPL-3.0-or-later.txt similarity index 100% rename from src/Mod/AddonManager/Resources/licenses/LGPLv3.txt rename to src/Mod/AddonManager/Resources/licenses/LGPL-3.0-or-later.txt diff --git a/src/Mod/AddonManager/addonmanager_devmode_license_selector.py b/src/Mod/AddonManager/addonmanager_devmode_license_selector.py index 3aa6da6973..72db565694 100644 --- a/src/Mod/AddonManager/addonmanager_devmode_license_selector.py +++ b/src/Mod/AddonManager/addonmanager_devmode_license_selector.py @@ -75,23 +75,23 @@ class LicenseSelector: "The 3-Clause BSD License", "https://opensource.org/licenses/BSD-3-Clause", ), - "CC0v1": ( + "CC0-1.0": ( "No Rights Reserved/Public Domain", "https://creativecommons.org/choose/zero/", ), - "GPLv2": ( + "GPL-2.0-or-later": ( "GNU General Public License version 2", "https://opensource.org/licenses/GPL-2.0", ), - "GPLv3": ( + "GPL-3.0-or-later": ( "GNU General Public License version 3", "https://opensource.org/licenses/GPL-3.0", ), - "LGPLv2.1": ( + "LGPL-2.1-or-later": ( "GNU Lesser General Public License version 2.1", "https://opensource.org/licenses/LGPL-2.1", ), - "LGPLv3": ( + "LGPL-3.0-or-later": ( "GNU Lesser General Public License version 3", "https://opensource.org/licenses/LGPL-3.0", ), @@ -129,7 +129,7 @@ class LicenseSelector: 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") + short_code = self.pref.GetString("devModeLastSelectedLicense", "LGPL-2.1-or-later") self.set_license(short_code) def exec(self, short_code: str = None, license_path: str = "") -> Optional[Tuple[str, str]]: diff --git a/src/Mod/AddonManager/addonmanager_licenses.py b/src/Mod/AddonManager/addonmanager_licenses.py index 7577ec8891..1dcc40b87a 100644 --- a/src/Mod/AddonManager/addonmanager_licenses.py +++ b/src/Mod/AddonManager/addonmanager_licenses.py @@ -42,6 +42,8 @@ except ImportError: from PySide import QtCore +import addonmanager_freecad_interface as fci + class SPDXLicenseManager: """A class that loads a list of licenses from an internal Qt resource and provides access to @@ -74,15 +76,28 @@ class SPDXLicenseManager: """Check to see if the license is OSI-approved, according to the SPDX database. Returns False if the license is not in the database, or is not marked as "isOsiApproved".""" if spdx_id not in self.license_data: + fci.Console.PrintWarning( + f"WARNING: License ID {spdx_id} is not in the SPDX license " + f"list. The Addon author must correct their metadata.\n" + ) return False - return self.license_data[spdx_id]["isOsiApproved"] + return ( + "isOsiApproved" in self.license_data[spdx_id] + and self.license_data[spdx_id]["isOsiApproved"] + ) def is_fsf_libre(self, spdx_id: str) -> bool: """Check to see if the license is FSF Free/Libre, according to the SPDX database. Returns False if the license is not in the database, or is not marked as "isFsfLibre".""" if spdx_id not in self.license_data: + fci.Console.PrintWarning( + f"WARNING: License ID {spdx_id} is not in the SPDX license " + f"list. The Addon author must correct their metadata.\n" + ) return False - return self.license_data[spdx_id]["isFsfLibre"] + return ( + "isFsfLibre" in self.license_data[spdx_id] and self.license_data[spdx_id]["isFsfLibre"] + ) def name(self, spdx_id: str) -> str: if spdx_id not in self.license_data: @@ -114,6 +129,40 @@ class SPDXLicenseManager: return "" return self.license_data[spdx_id]["detailsUrl"] + def normalize(self, license_string: str) -> str: + """Given a potentially non-compliant license string, attempt to normalize it to match an + SPDX record. Takes a conservative view and tries not to over-expand stated rights (e.g. + it will select 'GPL-3.0-only' rather than 'GPL-3.0-or-later' when given just GPL3).""" + if self.name(license_string): + return license_string + fci.Console.PrintLog( + f"Attempting to normalize non-compliant license '" f"{license_string}'... " + ) + normed = license_string.replace("lgpl", "LGPL").replace("gpl", "GPL") + normed = ( + normed.replace(" ", "-") + .replace("v", "-") + .replace("GPL2", "GPL-2") + .replace("GPL3", "GPL-3") + ) + if self.name(normed): + fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") + return normed + # If it still doesn't match, try some other things + while "--" in normed: + normed = license_string.replace("--", "-") + + if self.name(normed): + fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") + return normed + if not normed.endswith(".0"): + normed += ".0" + if self.name(normed): + fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") + return normed + fci.Console.PrintLog(f"failed to normalize (typo in ID or invalid version number??)\n") + return license_string # We failed to normalize this one + _LICENSE_MANAGER = None # Internal use only, see get_license_manager() diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index b01a7fde78..06b981583d 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -67,6 +67,7 @@ class Macro: self.raw_code_url = "" self.wiki = "" self.version = "" + self.license = "" self.date = "" self.src_filename = "" self.filename_from_url = "" @@ -111,8 +112,8 @@ class Macro: def is_installed(self): """Returns True if this macro is currently installed (that is, if it exists - in the user macro directory), or False if it is not. Both the exact filename, - as well as the filename prefixed with "Macro", are considered an installation + in the user macro directory), or False if it is not. Both the exact filename + and the filename prefixed with "Macro", are considered an installation of this macro. """ if self.on_git and not self.src_filename: @@ -159,6 +160,9 @@ class Macro: code = self._fetch_raw_code(p) if not code: code = self._read_code_from_wiki(p) + if not self.license: + # The default license on the wiki is CC-BY-3.0 (which is non-Libre and not OSI-approved) + self.license = "CC-BY-3.0" if not code: self._console.PrintWarning( translate("AddonsInstaller", "Unable to fetch the code of this macro.") + "\n" @@ -227,7 +231,7 @@ class Macro: code = re.findall(r"
(.*?)
", p.replace("\n", "--endl--")) if code: # take the biggest code block - code = sorted(code, key=len)[-1] + code = str(sorted(code, key=len)[-1]) code = code.replace("--endl--", "\n") # Clean HTML escape codes. code = unescape(code) @@ -327,7 +331,7 @@ class Macro: self.other_files.append(self.icon) def _copy_other_files(self, macro_dir, warnings) -> bool: - """Copy any specified "other files" into the install directory""" + """Copy any specified "other files" into the installation directory""" base_dir = os.path.dirname(self.src_filename) for other_file in self.other_files: if not other_file: @@ -382,7 +386,7 @@ class Macro: ) def parse_wiki_page_for_icon(self, page_data: str) -> None: - """Attempt to find a url for the icon in the wiki page. Sets self.icon if + """Attempt to find the url for the icon in the wiki page. Sets 'self.icon' if found.""" # Method 1: the text "toolbar icon" appears on the page, and provides a direct diff --git a/src/Mod/AddonManager/addonmanager_macro_parser.py b/src/Mod/AddonManager/addonmanager_macro_parser.py index 86d829bbe7..5c77037574 100644 --- a/src/Mod/AddonManager/addonmanager_macro_parser.py +++ b/src/Mod/AddonManager/addonmanager_macro_parser.py @@ -36,8 +36,10 @@ except ImportError: try: import FreeCAD + from addonmanager_licenses import get_license_manager except ImportError: FreeCAD = None + get_license_manager = None class DummyThread: @@ -63,6 +65,7 @@ class MacroParser: "other_files": [""], "author": "", "date": "", + "license": "", "icon": "", "xpm": "", } @@ -83,6 +86,8 @@ class MacroParser: "__files__": "other_files", "__author__": "author", "__date__": "date", + "__license__": "license", + "__licence__": "license", # accept either spelling "__icon__": "icon", "__xpm__": "xpm", } @@ -185,6 +190,8 @@ class MacroParser: self.parse_results[value] = match_group if value == "comment": self._cleanup_comment() + elif value == "license": + self._cleanup_license() elif isinstance(self.parse_results[value], list): self.parse_results[value] = [of.strip() for of in match_group.split(",")] else: @@ -197,6 +204,11 @@ class MacroParser: if len(self.parse_results["comment"]) > 512: self.parse_results["comment"] = self.parse_results["comment"][:511] + "…" + def _cleanup_license(self): + if get_license_manager is not None: + lm = get_license_manager() + self.parse_results["license"] = lm.normalize(self.parse_results["license"]) + def _apply_special_handling(self, key: str, line: str): # Macro authors are supposed to be providing strings here, but in some # cases they are not doing so. If this is the "__version__" tag, try diff --git a/src/Mod/AddonManager/addonmanager_metadata.py b/src/Mod/AddonManager/addonmanager_metadata.py index 213b0e2c5e..1afb733454 100644 --- a/src/Mod/AddonManager/addonmanager_metadata.py +++ b/src/Mod/AddonManager/addonmanager_metadata.py @@ -30,6 +30,9 @@ from dataclasses import dataclass, field from enum import IntEnum, auto from typing import Tuple, Dict, List, Optional +from addonmanager_licenses import get_license_manager +import addonmanager_freecad_interface as fci + try: # If this system provides a secure parser, use that: import defusedxml.ElementTree as ET @@ -315,7 +318,10 @@ class MetadataReader: @staticmethod def _parse_license(child: ET.Element) -> License: file = child.attrib["file"] if "file" in child.attrib else "" - return License(name=child.text, file=file) + license_id = child.text + lm = get_license_manager() + license_id = lm.normalize(license_id) + return License(name=license_id, file=file) @staticmethod def _parse_url(child: ET.Element) -> Url: diff --git a/src/Mod/AddonManager/addonmanager_workers_utility.py b/src/Mod/AddonManager/addonmanager_workers_utility.py index eaa8e5c67e..48b8d360bd 100644 --- a/src/Mod/AddonManager/addonmanager_workers_utility.py +++ b/src/Mod/AddonManager/addonmanager_workers_utility.py @@ -55,7 +55,9 @@ class ConnectionChecker(QtCore.QThread): url = "https://api.github.com/zen" self.done = False NetworkManager.AM_NETWORK_MANAGER.completed.connect(self.connection_data_received) - self.request_id = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(url) + self.request_id = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get( + url, timeout_ms=10000 + ) while not self.done: if QtCore.QThread.currentThread().isInterruptionRequested(): FreeCAD.Console.PrintLog("Connection check cancelled\n") diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index 3d9f310346..849aa1d457 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -560,12 +560,41 @@ class PackageListFilter(QtCore.QSortFilterProxyModel): return False # If it is not an OSI-approved license, check to see if we are hiding those - if self.hide_non_OSI_approved and not license_manager.is_osi_approved(data.license): - return False + if self.hide_non_OSI_approved or self.hide_non_FSF_libre: + if not data.license: + return False + licenses_to_check = [] + if type(data.license) is str: + licenses_to_check.append(data.license) + elif type(data.license) is list: + for license_id in data.license: + if type(license_id) is str: + licenses_to_check.append(license_id) + else: + licenses_to_check.append(license_id.name) + else: + licenses_to_check.append(data.license.name) - # If it is not an FSF Free/Libre license, check to see if we are hiding those - if self.hide_non_FSF_libre and not license_manager.is_fsf_libre(data.license): - return False + fsf_libre = False + osi_approved = False + for license_id in licenses_to_check: + if not osi_approved and license_manager.is_osi_approved(license_id): + osi_approved = True + if not fsf_libre and license_manager.is_fsf_libre(license_id): + fsf_libre = True + if self.hide_non_OSI_approved and not osi_approved: + FreeCAD.Console.PrintLog( + f"Hiding addon {data.name} because its license, {licenses_to_check}, " + f"is " + f"not OSI approved\n" + ) + return False + if self.hide_non_FSF_libre and not fsf_libre: + FreeCAD.Console.PrintLog( + f"Hiding addon {data.name} because its license, {licenses_to_check}, is " + f"not FSF Libre\n" + ) + return False # If it's not installed, check to see if it's for a newer version of FreeCAD if ( From 057bdda46b4d19319d80b48eeca79e291dffef7f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 10 Feb 2024 11:48:29 -0500 Subject: [PATCH 19/25] Addon Manager: Bug fixes and license cleanup --- src/Mod/AddonManager/Addon.py | 8 +++-- src/Mod/AddonManager/AddonManager.py | 5 ++-- src/Mod/AddonManager/NetworkManager.py | 17 ++++++----- src/Mod/AddonManager/addonmanager_licenses.py | 29 +++++++++++++------ src/Mod/AddonManager/addonmanager_macro.py | 3 -- .../AddonManager/addonmanager_utilities.py | 2 +- .../addonmanager_workers_installation.py | 3 +- .../addonmanager_workers_startup.py | 6 ++-- 8 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/Mod/AddonManager/Addon.py b/src/Mod/AddonManager/Addon.py index 9af58937dd..68166312b8 100644 --- a/src/Mod/AddonManager/Addon.py +++ b/src/Mod/AddonManager/Addon.py @@ -223,12 +223,16 @@ class Addon: @property def license(self): if not self._cached_license: + self._cached_license = "UNLICENSED" if self.metadata and self.metadata.license: self._cached_license = self.metadata.license elif self.stats and self.stats.license: self._cached_license = self.stats.license - elif self.macro and self.macro.license: - self._cached_license = self.macro.license + elif self.macro: + if self.macro.license: + self._cached_license = self.macro.license + elif self.macro.on_wiki: + self._cached_license = "CC-BY-3.0" return self._cached_license @classmethod diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 74333a3c51..ee73ccc8de 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -550,8 +550,9 @@ class CommandAddonManager: ) self.startup() - # Recaching implies checking for updates, regardless of the user's autocheck option - self.startup_sequence.remove(self.check_updates) + # Re-caching implies checking for updates, regardless of the user's autocheck option + if self.check_updates in self.startup_sequence: + self.startup_sequence.remove(self.check_updates) self.startup_sequence.append(self.force_check_updates) def on_package_updated(self, repo: Addon) -> None: diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index 98a1be16be..acd3997360 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -391,7 +391,8 @@ if HAVE_QTNETWORK: def __synchronous_process_completion( self, index: int, code: int, data: QtCore.QByteArray ) -> None: - """Check the return status of a completed process, and handle its returned data (if any).""" + """Check the return status of a completed process, and handle its returned data (if + any).""" with self.synchronous_lock: if index in self.synchronous_complete: if code == 200: @@ -406,7 +407,8 @@ if HAVE_QTNETWORK: ) self.synchronous_complete[index] = True - def __create_get_request(self, url: str, timeout_ms: int) -> QtNetwork.QNetworkRequest: + @staticmethod + def __create_get_request(url: str, timeout_ms: int) -> QtNetwork.QNetworkRequest: """Construct a network request to a given URL""" request = QtNetwork.QNetworkRequest(QtCore.QUrl(url)) request.setAttribute( @@ -560,21 +562,20 @@ if HAVE_QTNETWORK: # This can happen during a cancellation operation: silently do nothing return - if reply.error() == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError: - # Silently do nothing - return - index = None for key, value in self.replies.items(): if reply == value: index = key break if index is None: - print(f"Lost net request for {reply.url()}") return response_code = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute) - self.queue.task_done() + if response_code == 301: # Permanently moved -- this is a redirect, bail out + return + if reply.error() != QtNetwork.QNetworkReply.NetworkError.OperationCanceledError: + # It this was not a timeout, make sure we mark the queue task done + self.queue.task_done() if reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: if index in self.monitored_connections: # Make sure to read any remaining data diff --git a/src/Mod/AddonManager/addonmanager_licenses.py b/src/Mod/AddonManager/addonmanager_licenses.py index 1dcc40b87a..5781c093a2 100644 --- a/src/Mod/AddonManager/addonmanager_licenses.py +++ b/src/Mod/AddonManager/addonmanager_licenses.py @@ -75,6 +75,8 @@ class SPDXLicenseManager: def is_osi_approved(self, spdx_id: str) -> bool: """Check to see if the license is OSI-approved, according to the SPDX database. Returns False if the license is not in the database, or is not marked as "isOsiApproved".""" + if spdx_id == "UNLICENSED" or spdx_id == "UNLICENCED" or spdx_id.startswith("SEE LIC"): + return False if spdx_id not in self.license_data: fci.Console.PrintWarning( f"WARNING: License ID {spdx_id} is not in the SPDX license " @@ -89,6 +91,8 @@ class SPDXLicenseManager: def is_fsf_libre(self, spdx_id: str) -> bool: """Check to see if the license is FSF Free/Libre, according to the SPDX database. Returns False if the license is not in the database, or is not marked as "isFsfLibre".""" + if spdx_id == "UNLICENSED" or spdx_id == "UNLICENCED" or spdx_id.startswith("SEE LIC"): + return False if spdx_id not in self.license_data: fci.Console.PrintWarning( f"WARNING: License ID {spdx_id} is not in the SPDX license " @@ -100,6 +104,10 @@ class SPDXLicenseManager: ) def name(self, spdx_id: str) -> str: + if spdx_id == "UNLICENSED": + return "All rights reserved" + if spdx_id.startswith("SEE LIC"): # "SEE LICENSE IN" or "SEE LICENCE IN" + return f"Custom license: {spdx_id}" if spdx_id not in self.license_data: return "" return self.license_data[spdx_id]["name"] @@ -145,21 +153,24 @@ class SPDXLicenseManager: .replace("GPL2", "GPL-2") .replace("GPL3", "GPL-3") ) - if self.name(normed): + or_later = "" + if normed.endswith("+"): + normed = normed[:-1] + or_later = "-or-later" + if self.name(normed + or_later): fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") - return normed + return normed + or_later # If it still doesn't match, try some other things while "--" in normed: - normed = license_string.replace("--", "-") + normed = normed.replace("--", "-") - if self.name(normed): + if self.name(normed + or_later): fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") - return normed - if not normed.endswith(".0"): - normed += ".0" - if self.name(normed): + return normed + or_later + normed += ".0" + if self.name(normed + or_later): fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") - return normed + return normed + or_later fci.Console.PrintLog(f"failed to normalize (typo in ID or invalid version number??)\n") return license_string # We failed to normalize this one diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index 06b981583d..93e59f6b0e 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -160,9 +160,6 @@ class Macro: code = self._fetch_raw_code(p) if not code: code = self._read_code_from_wiki(p) - if not self.license: - # The default license on the wiki is CC-BY-3.0 (which is non-Libre and not OSI-approved) - self.license = "CC-BY-3.0" if not code: self._console.PrintWarning( translate("AddonsInstaller", "Unable to fetch the code of this macro.") + "\n" diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 41d6a81229..8b3f2582f6 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -380,7 +380,7 @@ def blocking_get(url: str, method=None) -> bytes: p = b"" if fci.FreeCADGui and method is None or method == "networkmanager": NetworkManager.InitializeNetworkManager() - p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url) + p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url, 10000) # 10 second timeout if p: try: p = p.data() diff --git a/src/Mod/AddonManager/addonmanager_workers_installation.py b/src/Mod/AddonManager/addonmanager_workers_installation.py index e1bcf1c226..1b2f902e53 100644 --- a/src/Mod/AddonManager/addonmanager_workers_installation.py +++ b/src/Mod/AddonManager/addonmanager_workers_installation.py @@ -87,7 +87,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): current_thread = QtCore.QThread.currentThread() for repo in self.repos: - if repo.url and utils.recognized_git_location(repo): + if not repo.macro and repo.url and utils.recognized_git_location(repo): # package.xml index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get( utils.construct_git_url(repo, "package.xml") @@ -120,7 +120,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread): while self.requests: if current_thread.isInterruptionRequested(): - NetworkManager.AM_NETWORK_MANAGER.completed.disconnect(self.download_completed) for request in self.requests: NetworkManager.AM_NETWORK_MANAGER.abort(request) return diff --git a/src/Mod/AddonManager/addonmanager_workers_startup.py b/src/Mod/AddonManager/addonmanager_workers_startup.py index 0797d1f122..e08bf2f98c 100644 --- a/src/Mod/AddonManager/addonmanager_workers_startup.py +++ b/src/Mod/AddonManager/addonmanager_workers_startup.py @@ -93,7 +93,7 @@ class CreateAddonListWorker(QtCore.QThread): def _get_freecad_addon_repo_data(self): # update info lists p = NetworkManager.AM_NETWORK_MANAGER.blocking_get( - "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/addonflags.json" + "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/addonflags.json", 5000 ) if p: p = p.data().decode("utf8") @@ -203,7 +203,7 @@ class CreateAddonListWorker(QtCore.QThread): def _get_official_addons(self): # querying official addons p = NetworkManager.AM_NETWORK_MANAGER.blocking_get( - "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/.gitmodules" + "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/.gitmodules", 5000 ) if not p: return @@ -369,7 +369,7 @@ class CreateAddonListWorker(QtCore.QThread): """ p = NetworkManager.AM_NETWORK_MANAGER.blocking_get( - "https://wiki.freecad.org/Macros_recipes" + "https://wiki.freecad.org/Macros_recipes", 5000 ) if not p: # The Qt Python translation extractor doesn't support splitting this string (yet) From 2cc096e11bfea57ea3f2f6127689ed67573e5397 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 10 Feb 2024 14:54:22 -0500 Subject: [PATCH 20/25] Addon manager: Update test to check license --- src/Mod/AddonManager/AddonManagerTest/app/test_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/AddonManager/AddonManagerTest/app/test_metadata.py b/src/Mod/AddonManager/AddonManagerTest/app/test_metadata.py index 6c430f72a9..b572327d79 100644 --- a/src/Mod/AddonManager/AddonManagerTest/app/test_metadata.py +++ b/src/Mod/AddonManager/AddonManagerTest/app/test_metadata.py @@ -546,7 +546,7 @@ class TestMetadataReaderIntegration(unittest.TestCase): self.assertEqual(Version("1.0.1"), metadata.version) self.assertEqual("2022-01-07", metadata.date) self.assertEqual("Resources/icons/PackageIcon.svg", metadata.icon) - self.assertListEqual([License(name="LGPLv2.1", file="LICENSE")], metadata.license) + self.assertListEqual([License(name="LGPL-2.1", file="LICENSE")], metadata.license) self.assertListEqual( [Contact(name="FreeCAD Developer", email="developer@freecad.org")], metadata.maintainer, From d846b1ab9b7c18359932f0bea467101862fa8b32 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 10 Feb 2024 14:54:50 -0500 Subject: [PATCH 21/25] Addon Manager: Preference to hide unlicensed --- src/Mod/AddonManager/AddonManagerOptions.ui | 16 ++++++++++++++++ .../addonmanager_preferences_defaults.json | 3 +++ src/Mod/AddonManager/package_list.py | 18 ++++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Mod/AddonManager/AddonManagerOptions.ui b/src/Mod/AddonManager/AddonManagerOptions.ui index 3bd3b7e591..8d4ecb45e9 100644 --- a/src/Mod/AddonManager/AddonManagerOptions.ui +++ b/src/Mod/AddonManager/AddonManagerOptions.ui @@ -90,6 +90,22 @@ installed addons will be checked for available updates
+ + + + Hide Addons without a license + + + true + + + HideUnlicensed + + + Addons + + + diff --git a/src/Mod/AddonManager/addonmanager_preferences_defaults.json b/src/Mod/AddonManager/addonmanager_preferences_defaults.json index 99a953bc55..07d16d0e15 100644 --- a/src/Mod/AddonManager/addonmanager_preferences_defaults.json +++ b/src/Mod/AddonManager/addonmanager_preferences_defaults.json @@ -14,6 +14,9 @@ "HideNewerFreeCADRequired": true, "HideObsolete": true, "HidePy2": true, + "HideNonOSIApproved": false, + "HideNonFSFFreeLibre": false, + "HideUnlicensed": false, "KnownPythonVersions": "[]", "LastCacheUpdate": "never", "MacroCacheUpdateFrequency": 7, diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index 849aa1d457..8d8f8a07a8 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -96,8 +96,11 @@ class PackageList(QtWidgets.QWidget): self.item_filter.setHidePy2(pref.GetBool("HidePy2", True)) self.item_filter.setHideObsolete(pref.GetBool("HideObsolete", True)) self.item_filter.setHideNonOSIApproved(pref.GetBool("HideNonOSIApproved", True)) - self.item_filter.setHideNonFSFLibre(pref.GetBool("HideNonFSFFreeLibre", True)) - self.item_filter.setHideNewerFreeCADRequired(pref.GetBool("HideNewerFreeCADRequired", True)) + self.item_filter.setHideNonFSFLibre(pref.GetBool("HideNonFSFFreeLibre", False)) + self.item_filter.setHideNewerFreeCADRequired( + pref.GetBool("HideNewerFreeCADRequired", False) + ) + self.item_filter.setHideUnlicensed(pref.GetBool("HideUnlicensed", False)) def on_listPackages_clicked(self, index: QtCore.QModelIndex): """Determine what addon was selected and emit the itemSelected signal with it as @@ -473,6 +476,7 @@ class PackageListFilter(QtCore.QSortFilterProxyModel): self.hide_py2 = False self.hide_non_OSI_approved = False self.hide_non_FSF_libre = False + self.hide_unlicensed = False self.hide_newer_freecad_required = False def setPackageFilter( @@ -509,6 +513,11 @@ class PackageListFilter(QtCore.QSortFilterProxyModel): self.hide_non_FSF_libre = hide self.invalidateFilter() + def setHideUnlicensed(self, hide: bool) -> None: + """Sets whether to hide addons without a specified license""" + self.hide_unlicensed = hide + self.invalidateFilter() + def setHideNewerFreeCADRequired(self, hide_nfr: bool) -> None: """Sets whether to hide packages that have indicated they need a newer version of FreeCAD than the one currently running.""" @@ -559,6 +568,11 @@ class PackageListFilter(QtCore.QSortFilterProxyModel): if self.hide_obsolete and data.obsolete: return False + if self.hide_unlicensed: + if not data.license or data.license in ["UNLICENSED", "UNLICENCED"]: + FreeCAD.Console.PrintLog(f"Hiding {data.name} because it has no license set\n") + return False + # If it is not an OSI-approved license, check to see if we are hiding those if self.hide_non_OSI_approved or self.hide_non_FSF_libre: if not data.license: From e9484e2bc781c2602125b0d2259a4ef2da378a98 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 10 Feb 2024 21:37:59 -0600 Subject: [PATCH 22/25] Addon Manager/Tests: Update license string in tests --- src/Mod/AddonManager/AddonManagerTest/data/combination.xml | 2 +- .../AddonManagerTest/data/depends_on_all_workbenches.xml | 2 +- src/Mod/AddonManager/AddonManagerTest/data/good_package.xml | 2 +- src/Mod/AddonManager/AddonManagerTest/data/macro_only.xml | 2 +- src/Mod/AddonManager/AddonManagerTest/data/prefpack_only.xml | 2 +- .../AddonManagerTest/data/test_version_detection.xml | 2 +- src/Mod/AddonManager/AddonManagerTest/data/workbench_only.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Mod/AddonManager/AddonManagerTest/data/combination.xml b/src/Mod/AddonManager/AddonManagerTest/data/combination.xml index 3b75db30b7..8f095046f5 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/combination.xml +++ b/src/Mod/AddonManager/AddonManagerTest/data/combination.xml @@ -5,7 +5,7 @@ 1.0.1 2022-01-07 FreeCAD Developer - LGPLv2.1 + LGPL-2.1 https://github.com/chennes/FreeCAD-Package https://github.com/chennes/FreeCAD-Package/blob/main/README.md Resources/icons/PackageIcon.svg diff --git a/src/Mod/AddonManager/AddonManagerTest/data/depends_on_all_workbenches.xml b/src/Mod/AddonManager/AddonManagerTest/data/depends_on_all_workbenches.xml index 2d705d5d40..e0d22ca2ad 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/depends_on_all_workbenches.xml +++ b/src/Mod/AddonManager/AddonManagerTest/data/depends_on_all_workbenches.xml @@ -5,7 +5,7 @@ 1.0.1 2022-01-07 FreeCAD Developer - LGPLv2.1 + LGPL-2.1 https://github.com/chennes/FreeCAD-Package https://github.com/chennes/FreeCAD-Package/blob/main/README.md diff --git a/src/Mod/AddonManager/AddonManagerTest/data/good_package.xml b/src/Mod/AddonManager/AddonManagerTest/data/good_package.xml index e484aa2ced..3be6edb282 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/good_package.xml +++ b/src/Mod/AddonManager/AddonManagerTest/data/good_package.xml @@ -5,7 +5,7 @@ 1.0.1 2022-01-07 FreeCAD Developer - LGPLv2.1 + LGPL-2.1 https://github.com/chennes/FreeCAD-Package https://github.com/chennes/FreeCAD-Package/blob/main/README.md Resources/icons/PackageIcon.svg diff --git a/src/Mod/AddonManager/AddonManagerTest/data/macro_only.xml b/src/Mod/AddonManager/AddonManagerTest/data/macro_only.xml index c8e47fa224..b20295cc9f 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/macro_only.xml +++ b/src/Mod/AddonManager/AddonManagerTest/data/macro_only.xml @@ -5,7 +5,7 @@ 1.0.1 2022-01-07 FreeCAD Developer - LGPLv2.1 + LGPL-2.1 https://github.com/chennes/FreeCAD-Package https://github.com/chennes/FreeCAD-Package/blob/main/README.md Resources/icons/PackageIcon.svg diff --git a/src/Mod/AddonManager/AddonManagerTest/data/prefpack_only.xml b/src/Mod/AddonManager/AddonManagerTest/data/prefpack_only.xml index 5affea4837..8e8dc5c741 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/prefpack_only.xml +++ b/src/Mod/AddonManager/AddonManagerTest/data/prefpack_only.xml @@ -5,7 +5,7 @@ 1.0.1 2022-01-07 FreeCAD Developer - LGPLv2.1 + LGPL-2.1 https://github.com/chennes/FreeCAD-Package https://github.com/chennes/FreeCAD-Package/blob/main/README.md diff --git a/src/Mod/AddonManager/AddonManagerTest/data/test_version_detection.xml b/src/Mod/AddonManager/AddonManagerTest/data/test_version_detection.xml index dfd578e0b2..0856dac73d 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/test_version_detection.xml +++ b/src/Mod/AddonManager/AddonManagerTest/data/test_version_detection.xml @@ -5,7 +5,7 @@ 1.0.1 2022-01-07 FreeCAD Developer - LGPLv2.1 + LGPL-2.1 https://github.com/chennes/FreeCAD-Package https://github.com/chennes/FreeCAD-Package/blob/main/README.md diff --git a/src/Mod/AddonManager/AddonManagerTest/data/workbench_only.xml b/src/Mod/AddonManager/AddonManagerTest/data/workbench_only.xml index 26ac759665..719e117611 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/workbench_only.xml +++ b/src/Mod/AddonManager/AddonManagerTest/data/workbench_only.xml @@ -5,7 +5,7 @@ 1.0.1 2022-01-07 FreeCAD Developer - LGPLv2.1 + LGPL-2.1 https://github.com/chennes/FreeCAD-Package https://github.com/chennes/FreeCAD-Package/blob/main/README.md From 5556a4d04ca47c3dfc05024a852e001d15042d13 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 11 Feb 2024 15:55:07 -0600 Subject: [PATCH 23/25] Addon Manager: Qt < 5.15 fallback for network timeout --- src/Mod/AddonManager/NetworkManager.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index acd3997360..1aac2b4a73 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -102,6 +102,12 @@ except ImportError: if HAVE_QTNETWORK: + # Added in Qt 5.15 + if hasattr(QtNetwork.QNetworkRequest, "DefaultTransferTimeoutConstant"): + default_timeout = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant + else: + default_timeout = 30000 + class QueueItem: """A container for information about an item in the network queue.""" @@ -318,7 +324,7 @@ if HAVE_QTNETWORK: def submit_unmonitored_get( self, url: str, - timeout_ms: int = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant, + timeout_ms: int = default_timeout, ) -> int: """Adds this request to the queue, and returns an index that can be used by calling code in conjunction with the completed() signal to handle the results of the call. All data is @@ -338,7 +344,7 @@ if HAVE_QTNETWORK: def submit_monitored_get( self, url: str, - timeout_ms: int = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant, + timeout_ms: int = default_timeout, ) -> int: """Adds this request to the queue, and returns an index that can be used by calling code in conjunction with the progress_made() and progress_completed() signals to handle the @@ -360,7 +366,7 @@ if HAVE_QTNETWORK: def blocking_get( self, url: str, - timeout_ms: int = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant, + timeout_ms: int = default_timeout, ) -> Optional[QtCore.QByteArray]: """Submits a GET request to the QNetworkAccessManager and block until it is complete""" @@ -486,7 +492,7 @@ if HAVE_QTNETWORK: """Used with the QNetworkAccessManager to follow redirects.""" sender = self.sender() current_index = -1 - timeout_ms = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant + timeout_ms = default_timeout # TODO: Figure out what the actual timeout value should be from the original request if sender: for index, reply in self.replies.items(): From 2ebc941458cfb4f2b406067bf0e6f0559a49b9f9 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 11 Feb 2024 16:20:54 -0600 Subject: [PATCH 24/25] Addon Manager: Protect call to setTransferTimeout Unsupported by Qt 5.12 --- src/Mod/AddonManager/NetworkManager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index 1aac2b4a73..9e92b9e800 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -426,7 +426,9 @@ if HAVE_QTNETWORK: QtNetwork.QNetworkRequest.CacheLoadControlAttribute, QtNetwork.QNetworkRequest.PreferNetwork, ) - request.setTransferTimeout(timeout_ms) + if hasattr(request, "setTransferTimeout"): + # Added in Qt 5.15 + request.setTransferTimeout(timeout_ms) return request def abort_all(self): From 838b73c36bd64d3b5161e6af48d710ca90331238 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 11 Feb 2024 17:18:58 -0600 Subject: [PATCH 25/25] Addon Manager: Fix abort_all() code --- src/Mod/AddonManager/NetworkManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index 9e92b9e800..89531ff4ee 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -433,8 +433,8 @@ if HAVE_QTNETWORK: def abort_all(self): """Abort ALL network calls in progress, including clearing the queue""" - for reply in self.replies: - if reply.isRunning(): + for reply in self.replies.values(): + if reply.abort().isRunning(): reply.abort() while True: try: