freecad-cam/Mod/AddonManager/package_list.py
2026-02-01 01:59:24 +01:00

771 lines
34 KiB
Python

# 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 *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
""" Defines the PackageList QWidget for displaying a list of Addons. """
import datetime
import threading
import FreeCAD
from PySide import QtCore, QtGui, QtWidgets
from Addon import Addon
from compact_view import Ui_CompactView
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_control_bar import WidgetViewControlBar, SortOptions
from Widgets.addonmanager_widget_view_selector import AddonManagerDisplayStyle
from Widgets.addonmanager_widget_filter_selector import StatusFilter, Filter
from Widgets.addonmanager_widget_progress_bar import WidgetProgressBar
from addonmanager_licenses import get_license_manager
translate = FreeCAD.Qt.translate
# pylint: disable=too-few-public-methods
class PackageList(QtWidgets.QWidget):
"""A widget that shows a list of packages and various widgets to control the
display of the list"""
itemSelected = QtCore.Signal(Addon)
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_PackageList()
self.ui.setupUi(self)
self.item_filter = PackageListFilter()
self.ui.listPackages.setModel(self.item_filter)
self.item_delegate = PackageListItemDelegate(self.ui.listPackages)
self.ui.listPackages.setItemDelegate(self.item_delegate)
self.ui.listPackages.clicked.connect(self.on_listPackages_clicked)
self.ui.view_bar.filter_changed.connect(self.update_status_filter)
self.ui.view_bar.search_changed.connect(self.item_filter.setFilterRegularExpression)
self.ui.view_bar.sort_changed.connect(self.item_filter.setSortRole)
self.ui.view_bar.sort_changed.connect(self.item_delegate.set_sort)
self.ui.view_bar.sort_order_changed.connect(lambda order: self.item_filter.sort(0, order))
# Set up the view the same as the last time:
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
package_type = pref.GetInt("PackageTypeSelection", 0)
status = pref.GetInt("StatusSelection", 0)
search_string = pref.GetString("SearchString", "")
self.ui.view_bar.filter_selector.set_contents_filter(package_type)
self.ui.view_bar.filter_selector.set_status_filter(status)
if search_string:
self.ui.view_bar.search.filter_line_edit.setText(search_string)
self.item_filter.setPackageFilter(package_type)
self.item_filter.setStatusFilter(status)
# Pre-init of other members:
self.item_model = None
def setModel(self, model):
"""This is a model-view-controller widget: set its model."""
self.item_model = model
self.item_filter.setSourceModel(self.item_model)
self.item_filter.setSortRole(SortOptions.Alphabetical)
self.item_filter.sort(0, QtCore.Qt.AscendingOrder)
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
style = pref.GetInt("ViewStyle", AddonManagerDisplayStyle.EXPANDED)
self.set_view_style(style)
self.ui.view_bar.view_selector.set_current_view(style)
self.item_filter.setHidePy2(pref.GetBool("HidePy2", False))
self.item_filter.setHideObsolete(pref.GetBool("HideObsolete", False))
self.item_filter.setHideNonOSIApproved(pref.GetBool("HideNonOSIApproved", False))
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 select_addon(self, addon_name: str):
for index, addon in enumerate(self.item_model.repos):
if addon.name == addon_name:
row_index = self.item_model.createIndex(index, 0)
if self.item_filter.filterAcceptsRow(index):
self.ui.listPackages.setCurrentIndex(row_index)
else:
FreeCAD.Console.PrintLog(
f"Addon {addon_name} is not visible given current "
"filter: not selecting it."
)
return
FreeCAD.Console.PrintLog(f"Could not find addon '{addon_name}' to select it")
def on_listPackages_clicked(self, index: QtCore.QModelIndex):
"""Determine what addon was selected and emit the itemSelected signal with it as
an argument."""
source_selection = self.item_filter.mapToSource(index)
selected_repo = self.item_model.repos[source_selection.row()]
self.itemSelected.emit(selected_repo)
def update_status_filter(self, new_filter: Filter) -> None:
"""hide/show rows corresponding to the specified 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("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"""
if self.item_model:
self.item_model.layoutAboutToBeChanged.emit()
self.item_delegate.set_view(style)
if style == AddonManagerDisplayStyle.COMPACT or style == AddonManagerDisplayStyle.COMPOSITE:
self.ui.listPackages.setSpacing(2)
self.ui.listPackages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerItem)
self.ui.listPackages.verticalScrollBar().setSingleStep(-1)
else:
self.ui.listPackages.setSpacing(5)
self.ui.listPackages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.ui.listPackages.verticalScrollBar().setSingleStep(24)
if self.item_model:
self.item_model.layoutChanged.emit()
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetInt("ViewStyle", style)
class PackageListItemModel(QtCore.QAbstractListModel):
"""The model for use with the PackageList class."""
repos = []
write_lock = threading.Lock()
DataAccessRole = QtCore.Qt.UserRole
def rowCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
"""The number of rows"""
if parent.isValid():
return 0
return len(self.repos)
def columnCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
"""Only one column, always returns 1."""
if parent.isValid():
return 0
return 1
def data(self, index: QtCore.QModelIndex, role: int = QtCore.Qt.DisplayRole):
"""Get the data for a given index and role."""
if not index.isValid():
return None
row = index.row()
if role == QtCore.Qt.ToolTipRole:
tooltip = ""
if self.repos[row].repo_type == Addon.Kind.PACKAGE:
tooltip = translate("AddonsInstaller", "Click for details about package {}").format(
self.repos[row].display_name
)
elif self.repos[row].repo_type == Addon.Kind.WORKBENCH:
tooltip = translate(
"AddonsInstaller", "Click for details about workbench {}"
).format(self.repos[row].display_name)
elif self.repos[row].repo_type == Addon.Kind.MACRO:
tooltip = translate("AddonsInstaller", "Click for details about macro {}").format(
self.repos[row].display_name
)
return tooltip
if role == PackageListItemModel.DataAccessRole:
return self.repos[row]
# Sorting
if role == SortOptions.Alphabetical:
return self.repos[row].display_name
if role == SortOptions.LastUpdated:
update_date = self.repos[row].update_date
if update_date and hasattr(update_date, "timestamp"):
return update_date.timestamp()
return 0
if role == SortOptions.DateAdded:
if self.repos[row].stats and self.repos[row].stats.date_created:
return self.repos[row].stats.date_created.timestamp()
return 0
if role == SortOptions.Stars:
if self.repos[row].stats and self.repos[row].stats.stars:
return self.repos[row].stats.stars
return 0
if role == SortOptions.Score:
return self.repos[row].score
def headerData(self, _unused1, _unused2, _role=QtCore.Qt.DisplayRole):
"""No header in this implementation: always returns None."""
return None
def append_item(self, repo: Addon) -> None:
"""Adds this addon to the end of the model. Thread safe."""
if repo in self.repos:
# Cowardly refuse to insert the same repo a second time
return
with self.write_lock:
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.repos.append(repo)
self.endInsertRows()
def clear(self) -> None:
"""Clear the model, removing all rows. Thread safe."""
if self.rowCount() > 0:
with self.write_lock:
self.beginRemoveRows(QtCore.QModelIndex(), 0, self.rowCount() - 1)
self.repos = []
self.endRemoveRows()
def reload_item(self, repo: Addon) -> None:
"""Sets the addon data for the given addon (based on its name)"""
for index, item in enumerate(self.repos):
if item.name == repo.name:
with self.write_lock:
self.repos[index] = repo
return
class CompactView(QtWidgets.QWidget):
"""A single-line view of the package information"""
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_CompactView()
self.ui.setupUi(self)
class ExpandedView(QtWidgets.QWidget):
"""A multi-line view of the package information"""
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_ExpandedView()
self.ui.setupUi(self)
class PackageListItemDelegate(QtWidgets.QStyledItemDelegate):
"""Render the repo data as a formatted region"""
def __init__(self, parent=None):
super().__init__(parent)
self.displayStyle = AddonManagerDisplayStyle.EXPANDED
self.sort_order = SortOptions.Alphabetical
self.expanded = ExpandedView()
self.compact = CompactView()
self.widget = self.expanded
def set_view(self, style: AddonManagerDisplayStyle) -> None:
"""Set the view of to style"""
if not self.displayStyle == style:
self.displayStyle = style
def set_sort(self, sort: SortOptions) -> None:
"""When sorting by various things, we display the thing that's being sorted on."""
if not self.sort_order == sort:
self.sort_order = sort
def sizeHint(self, _option, index):
"""Attempt to figure out the correct height for the widget based on its
current contents."""
self.update_content(index)
return self.widget.sizeHint()
def update_content(self, index):
"""Creates the display of the content for a given index."""
repo = index.data(PackageListItemModel.DataAccessRole)
if self.displayStyle == AddonManagerDisplayStyle.EXPANDED:
self.widget = self.expanded
self._setup_expanded_view(repo)
elif self.displayStyle == AddonManagerDisplayStyle.COMPACT:
self.widget = self.compact
self._setup_compact_view(repo)
elif self.displayStyle == AddonManagerDisplayStyle.COMPOSITE:
self.widget = self.compact # For now re-use the compact list
self._setup_composite_view(repo)
self.widget.adjustSize()
def _setup_expanded_view(self, addon: Addon) -> None:
self.widget.ui.labelPackageName.setText(f"<h1>{addon.display_name}</h1>")
self.widget.ui.labelIcon.setPixmap(addon.icon.pixmap(QtCore.QSize(48, 48)))
self.widget.ui.labelStatus.setText(self.get_expanded_update_string(addon))
self.widget.ui.labelIcon.setText("")
self.widget.ui.labelTags.setText("")
if addon.metadata:
self.widget.ui.labelDescription.setText(addon.metadata.description)
self.widget.ui.labelVersion.setText(f"<i>v{addon.metadata.version}</i>")
self._set_package_maintainer_label(addon)
elif addon.macro:
self.widget.ui.labelDescription.setText(addon.macro.comment)
self._set_macro_version_label(addon)
self._set_macro_maintainer_label(addon)
else:
self.widget.ui.labelDescription.setText("")
self.widget.ui.labelMaintainer.setText("")
self.widget.ui.labelVersion.setText("")
if addon.tags:
self.widget.ui.labelTags.setText(
translate("AddonsInstaller", "Tags") + ": " + ", ".join(addon.tags)
)
if self.sort_order == SortOptions.Alphabetical:
self.widget.ui.labelSort.setText("")
else:
self.widget.ui.labelSort.setText(self._get_sort_label_text(addon))
def _setup_compact_view(self, addon: Addon) -> None:
self.widget.ui.labelPackageName.setText(f"<b>{addon.display_name}</b>")
self.widget.ui.labelIcon.setPixmap(addon.icon.pixmap(QtCore.QSize(16, 16)))
self.widget.ui.labelStatus.setText(self.get_compact_update_string(addon))
self.widget.ui.labelIcon.setText("")
if addon.metadata:
self.widget.ui.labelVersion.setText(f"<i>v{addon.metadata.version}</i>")
elif addon.macro:
self._set_macro_version_label(addon)
else:
self.widget.ui.labelVersion.setText("")
if self.sort_order == SortOptions.Alphabetical:
description = self._get_compact_description(addon)
self.widget.ui.labelDescription.setText(description)
else:
self.widget.ui.labelDescription.setText(self._get_sort_label_text(addon))
def _setup_composite_view(self, addon: Addon) -> None:
self.widget.ui.labelPackageName.setText(f"<b>{addon.display_name}</b>")
self.widget.ui.labelIcon.setPixmap(addon.icon.pixmap(QtCore.QSize(16, 16)))
self.widget.ui.labelStatus.setText(self.get_compact_update_string(addon))
self.widget.ui.labelIcon.setText("")
if addon.metadata:
self.widget.ui.labelVersion.setText(f"<i>v{addon.metadata.version}</i>")
elif addon.macro:
self._set_macro_version_label(addon)
else:
self.widget.ui.labelVersion.setText("")
if self.sort_order != SortOptions.Alphabetical:
self.widget.ui.labelDescription.setText(self._get_sort_label_text(addon))
else:
self.widget.ui.labelDescription.setText("")
def _set_package_maintainer_label(self, addon: Addon):
maintainers = addon.metadata.maintainer
maintainers_string = ""
if len(maintainers) == 1:
maintainers_string = (
translate("AddonsInstaller", "Maintainer")
+ f": {maintainers[0].name} <{maintainers[0].email}>"
)
elif len(maintainers) > 1:
n = len(maintainers)
maintainers_string = translate("AddonsInstaller", "Maintainers:", "", n)
for maintainer in maintainers:
maintainers_string += f"\n{maintainer.name} <{maintainer.email}>"
self.widget.ui.labelMaintainer.setText(maintainers_string)
def _set_macro_maintainer_label(self, repo: Addon):
if repo.macro.author:
caption = translate("AddonsInstaller", "Author")
self.widget.ui.labelMaintainer.setText(caption + ": " + repo.macro.author)
else:
self.widget.ui.labelMaintainer.setText("")
def _set_macro_version_label(self, addon: Addon):
version_string = ""
if addon.macro.version:
version_string = addon.macro.version + " "
if addon.macro.on_wiki:
version_string += "(wiki)"
elif addon.macro.on_git:
version_string += "(git)"
else:
version_string += "(unknown source)"
self.widget.ui.labelVersion.setText("<i>" + version_string + "</i>")
def _get_sort_label_text(self, addon: Addon) -> str:
if self.sort_order == SortOptions.Alphabetical:
return ""
elif self.sort_order == SortOptions.Stars:
if addon.stats and addon.stats.stars and addon.stats.stars > 0:
return translate("AddonsInstaller", "{} ★ on GitHub").format(addon.stats.stars)
return translate("AddonsInstaller", "No ★, or not on GitHub")
elif self.sort_order == SortOptions.DateAdded:
if addon.stats and addon.stats.date_created:
epoch_seconds = addon.stats.date_created.timestamp()
qdt = QtCore.QDateTime.fromSecsSinceEpoch(int(epoch_seconds)).date()
time_string = QtCore.QLocale().toString(qdt, QtCore.QLocale.ShortFormat)
return translate("AddonsInstaller", "Created ") + time_string
return ""
elif self.sort_order == SortOptions.LastUpdated:
update_date = addon.update_date
if update_date:
epoch_seconds = update_date.timestamp()
qdt = QtCore.QDateTime.fromSecsSinceEpoch(int(epoch_seconds)).date()
time_string = QtCore.QLocale().toString(qdt, QtCore.QLocale.ShortFormat)
return translate("AddonsInstaller", "Updated ") + time_string
return ""
elif self.sort_order == SortOptions.Score:
return translate("AddonsInstaller", "Score: ") + str(addon.score)
return ""
def _get_compact_description(self, addon: Addon) -> str:
description = ""
if addon.metadata:
description = addon.metadata.description
elif addon.macro and addon.macro.comment:
description = addon.macro.comment
trimmed_text, _, _ = description.partition(".")
return trimmed_text.replace("\n", " ")
@staticmethod
def get_compact_update_string(repo: Addon) -> str:
"""Get a single-line string listing details about the installed version and
date"""
result = ""
if repo.status() == Addon.Status.UNCHECKED:
result = translate("AddonsInstaller", "Installed")
elif repo.status() == Addon.Status.NO_UPDATE_AVAILABLE:
result = translate("AddonsInstaller", "Up-to-date")
elif repo.status() == Addon.Status.UPDATE_AVAILABLE:
result = translate("AddonsInstaller", "Update available")
elif repo.status() == Addon.Status.PENDING_RESTART:
result = translate("AddonsInstaller", "Pending restart")
if repo.is_disabled():
style = "style='color:" + utils.warning_color_string() + "; font-weight:bold;'"
result += f"<span {style}> [" + translate("AddonsInstaller", "DISABLED") + "]</span>"
return result
@staticmethod
def get_expanded_update_string(repo: Addon) -> str:
"""Get a multi-line string listing details about the installed version and
date"""
result = ""
installed_version_string = ""
if repo.status() != Addon.Status.NOT_INSTALLED:
if repo.installed_version or repo.installed_metadata:
installed_version_string = (
"<br/>" + translate("AddonsInstaller", "Installed version") + ": "
)
if repo.installed_metadata:
installed_version_string += str(repo.installed_metadata.version)
elif repo.installed_version:
installed_version_string += str(repo.installed_version)
else:
installed_version_string = "<br/>" + translate("AddonsInstaller", "Unknown version")
installed_date_string = ""
if repo.updated_timestamp:
installed_date_string = "<br/>" + translate("AddonsInstaller", "Installed on") + ": "
installed_date_string += QtCore.QLocale().toString(
QtCore.QDateTime.fromSecsSinceEpoch(int(round(repo.updated_timestamp, 0))),
QtCore.QLocale.ShortFormat,
)
available_version_string = ""
if repo.metadata:
available_version_string = (
"<br/>" + translate("AddonsInstaller", "Available version") + ": "
)
available_version_string += str(repo.metadata.version)
if repo.status() == Addon.Status.UNCHECKED:
result = translate("AddonsInstaller", "Installed")
result += installed_version_string
result += installed_date_string
elif repo.status() == Addon.Status.NO_UPDATE_AVAILABLE:
result = translate("AddonsInstaller", "Up-to-date")
result += installed_version_string
result += installed_date_string
elif repo.status() == Addon.Status.UPDATE_AVAILABLE:
result = translate("AddonsInstaller", "Update available")
result += installed_version_string
result += installed_date_string
result += available_version_string
elif repo.status() == Addon.Status.PENDING_RESTART:
result = translate("AddonsInstaller", "Pending restart")
if repo.is_disabled():
style = "style='color:" + utils.warning_color_string() + "; font-weight:bold;'"
result += (
f"<br/><span {style}>[" + translate("AddonsInstaller", "DISABLED") + "]</span>"
)
return result
def paint(
self,
painter: QtGui.QPainter,
option: QtWidgets.QStyleOptionViewItem,
_: QtCore.QModelIndex,
):
"""Main paint function: renders this widget into a given rectangle,
successively drawing all of its children."""
painter.save()
self.widget.resize(option.rect.size())
painter.translate(option.rect.topLeft())
self.widget.render(
painter, QtCore.QPoint(), QtGui.QRegion(), QtWidgets.QWidget.DrawChildren
)
painter.restore()
class PackageListFilter(QtCore.QSortFilterProxyModel):
"""Handle filtering the item list on various criteria"""
def __init__(self):
super().__init__()
self.package_type = 0 # Default to showing everything
self.status = 0 # Default to showing any
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_unlicensed = False
self.hide_newer_freecad_required = False
def setPackageFilter(
self, package_type: int
) -> None: # 0=All, 1=Workbenches, 2=Macros, 3=Preference Packs
"""Set the package filter to package_type and refreshes."""
self.package_type = package_type
self.invalidateFilter()
def setStatusFilter(
self, status: int
) -> None: # 0=Any, 1=Installed, 2=Not installed, 3=Update available
"""Sets the status filter to status and refreshes."""
self.status = status
self.invalidateFilter()
def setHidePy2(self, hide_py2: bool) -> None:
"""Sets whether to hide Python 2-only Addons"""
self.hide_py2 = hide_py2
self.invalidateFilter()
def setHideObsolete(self, hide_obsolete: bool) -> None:
"""Sets whether to hide Addons marked obsolete"""
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 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."""
self.hide_newer_freecad_required = hide_nfr
self.invalidateFilter()
# def lessThan(self, left_in, right_in) -> bool:
# """Enable sorting of display name (not case-sensitive)."""
#
# left = self.sourceModel().data(left_in, self.sortRole)
# right = self.sourceModel().data(right_in, self.sortRole)
#
# return left.display_name.lower() < right.display_name.lower()
def filterAcceptsRow(self, row, _parent=QtCore.QModelIndex()):
"""Do the actual filtering (called automatically by Qt when drawing the list)"""
index = self.sourceModel().createIndex(row, 0)
data = self.sourceModel().data(index, PackageListItemModel.DataAccessRole)
if self.package_type == 1:
if not data.contains_workbench():
return False
elif self.package_type == 2:
if not data.contains_macro():
return False
elif self.package_type == 3:
if not data.contains_preference_pack():
return False
if self.status == StatusFilter.INSTALLED:
if data.status() == Addon.Status.NOT_INSTALLED:
return False
elif self.status == StatusFilter.NOT_INSTALLED:
if data.status() != Addon.Status.NOT_INSTALLED:
return False
elif self.status == StatusFilter.UPDATE_AVAILABLE:
if data.status() != Addon.Status.UPDATE_AVAILABLE:
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 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 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:
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)
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 (
data.status() == Addon.Status.NOT_INSTALLED
and self.hide_newer_freecad_required
and data.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(data.metadata)
if first_supported_version is not None:
current_fc_version = Version(from_list=FreeCAD.Version())
if first_supported_version > current_fc_version:
return False
name = data.display_name
desc = data.description
if hasattr(self, "filterRegularExpression"): # Added in Qt 5.12
re = self.filterRegularExpression()
if re.isValid():
re.setPatternOptions(QtCore.QRegularExpression.CaseInsensitiveOption)
if re.match(name).hasMatch():
return True
if re.match(desc).hasMatch():
return True
if data.macro and data.macro.comment and re.match(data.macro.comment).hasMatch():
return True
for tag in data.tags:
if re.match(tag).hasMatch():
return True
return False
# Only get here for Qt < 5.12
re = self.filterRegExp()
if re.isValid():
re.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
if re.indexIn(name) != -1:
return True
if re.indexIn(desc) != -1:
return True
if data.macro and data.macro.comment and re.indexIn(data.macro.comment) != -1:
return True
for tag in data.tags:
if re.indexIn(tag) != -1:
return True
return False
# pylint: disable=attribute-defined-outside-init, missing-function-docstring
class Ui_PackageList:
"""The contents of the PackageList widget"""
def setupUi(self, form):
if not form.objectName():
form.setObjectName("PackageList")
self.verticalLayout = QtWidgets.QVBoxLayout(form)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.view_bar = WidgetViewControlBar(form)
self.view_bar.setObjectName("ViewControlBar")
self.horizontalLayout_6.addWidget(self.view_bar)
self.verticalLayout.addLayout(self.horizontalLayout_6)
self.listPackages = QtWidgets.QListView(form)
self.listPackages.setObjectName("listPackages")
self.listPackages.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.listPackages.setProperty("showDropIndicator", False)
self.listPackages.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.listPackages.setResizeMode(QtWidgets.QListView.Adjust)
self.listPackages.setUniformItemSizes(False)
self.listPackages.setAlternatingRowColors(True)
self.listPackages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.listPackages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.listPackages.verticalScrollBar().setSingleStep(24)
self.verticalLayout.addWidget(self.listPackages)
self.progressBar = WidgetProgressBar()
self.verticalLayout.addWidget(self.progressBar)
QtCore.QMetaObject.connectSlotsByName(form)