Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,20 @@ def contains_other(self) -> bool:
"""Determine if this package contains an "other" content item"""
return self.contains_packaged_content("other")

def contains_no_generated_content(self) -> bool:
"""Determines if this package explicitly contains no generated content"""

if self.repo_type != Addon.Kind.PACKAGE:
return False

if not self.metadata:
return False

if not self.metadata.contains:
return False

return self.metadata.contains.generated_content == False

def walk_dependency_tree(self, all_repos: Dict[str, "Addon"], deps: Dependencies):
"""Compute the total dependency tree for this repo (recursive)
- all_repos is a dictionary of repos, keyed on the name of the repo
Expand Down
74 changes: 65 additions & 9 deletions Widgets/addonmanager_widget_filter_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"""Defines a QWidget-derived class for displaying the view selection buttons."""

from enum import IntEnum
from typing import Set

from addonmanager_freecad_interface import translate

Expand All @@ -35,6 +36,7 @@ class FilterType(IntEnum):

PACKAGE_CONTENTS = 0
INSTALLATION_STATUS = 1
CONTAINS = 2


class StatusFilter(IntEnum):
Expand All @@ -57,10 +59,16 @@ class ContentFilter(IntEnum):
OTHER = 5


class ContainsFilter(IntEnum):

NO_GENERATED_CONTENT = 0


class Filter:
def __init__(self):
self.status_filter = StatusFilter.ANY
self.content_filter = ContentFilter.ANY
self.contains_filter: Set[ContainsFilter] = set()


class WidgetFilterSelector(QtWidgets.QComboBox):
Expand All @@ -71,6 +79,7 @@ class WidgetFilterSelector(QtWidgets.QComboBox):
def __init__(self, parent: QtWidgets.QWidget = None):
super().__init__(parent)
self.addon_type_index = 0
self.contains_index = 0
self.installation_status_index = 0
self.extra_padding = 64
self._setup_ui()
Expand Down Expand Up @@ -128,6 +137,13 @@ def _build_menu(self):
translate("AddonsInstaller", "Update available"),
(FilterType.INSTALLATION_STATUS, StatusFilter.UPDATE_AVAILABLE),
)
self.insertSeparator(self.count())
self.addItem(translate("AddonsInstaller", "Contains"))
self.contains_index = self.count() - 1
self.addItem(
translate("AddonsInstaller", "No generated content"),
(FilterType.CONTAINS, ContainsFilter.NO_GENERATED_CONTENT),
)
model: QtCore.QAbstractItemModel = self.model()
for row in range(model.rowCount()):
if row <= self.addon_type_index:
Expand All @@ -137,6 +153,11 @@ def _build_menu(self):
item.setCheckState(QtCore.Qt.Unchecked)
elif row == self.installation_status_index:
model.item(row).setEnabled(False)
elif row < self.contains_index:
item = model.item(row)
item.setCheckState(QtCore.Qt.Unchecked)
elif row == self.contains_index:
model.item(row).setEnabled(False)
else:
item = model.item(row)
item.setCheckState(QtCore.Qt.Unchecked)
Expand Down Expand Up @@ -176,6 +197,18 @@ def set_status_filter(self, status_filter: StatusFilter):
item.setCheckState(QtCore.Qt.Unchecked)
self._update_first_row_text()

def set_contains_filter(self, filter: Set[ContainsFilter]):
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.CONTAINS:
if user_data[1] in filter:
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)

Expand All @@ -194,7 +227,11 @@ def retranslateUi(self, _):
def _selected(self, row: int):
if row == 0:
return
if row == self.installation_status_index or row == self.addon_type_index:
if (
row == self.installation_status_index
or row == self.addon_type_index
or row == self.contains_index
):
self.setCurrentIndex(0)
return
model = self.model()
Expand All @@ -207,10 +244,18 @@ def _selected(self, row: int):
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)

if selected_row_type == FilterType.CONTAINS:
if user_data[1] == selected_data[1]:
if item.checkState() == QtCore.Qt.Checked:
item.setCheckState(QtCore.Qt.Unchecked)
else:
item.setCheckState(QtCore.Qt.Checked)
else:
item.setCheckState(QtCore.Qt.Unchecked)
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()
Expand All @@ -221,11 +266,22 @@ def _emit_current_filter(self):
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]
if data:

if 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]

if data[0] == FilterType.CONTAINS:
if item.checkState() == QtCore.Qt.Checked:
if data[1] not in new_filter.contains_filter:
new_filter.contains_filter.add(data[1])
else:
if data[1] in new_filter.contains_filter:
new_filter.contains_filter.remove(data[1])

self.filter_changed.emit(new_filter)

def _update_first_row_text(self):
Expand Down
21 changes: 21 additions & 0 deletions addonmanager_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
import xml.etree.ElementTree as ET


@dataclass
class Contains:
generated_content: None | bool = True


@dataclass
class Contact:
name: str
Expand Down Expand Up @@ -215,6 +220,7 @@ class Metadata:
freecadmax: Version = None
pythonmin: Version = None
content: Dict[str, List[Metadata]] = field(default_factory=dict) # Recursive def.
contains: None | Contains = None


def get_first_supported_freecad_version(metadata: Metadata) -> Optional[Version]:
Expand Down Expand Up @@ -313,6 +319,8 @@ def _parse_child_element(namespace: str, child: ET.Element, metadata: Metadata):
metadata.__dict__[tag].append(MetadataReader._parse_dependency(child))
elif tag == "content":
MetadataReader._parse_content(namespace, metadata, child)
elif tag == "contains":
metadata.contains = MetadataReader._parse_contains(child, metadata.name)

@staticmethod
def _parse_contact(child: ET.Element) -> Contact:
Expand Down Expand Up @@ -387,6 +395,19 @@ def _create_node(namespace, child) -> Metadata:
MetadataReader._parse_child_element(namespace, content_child, new_content_item)
return new_content_item

@staticmethod
def _parse_contains(child: ET.Element, name: str) -> Contains:
value = child.attrib["generated_content"] if "generated_content" in child.attrib else None

state = None

if value in ["true", "false"]:
state = value == "true"
else:
print(f"Unrecognized value ( '{ value }' ) for generated_content for addon '{ name }'")

return Contains(generated_content=state)


class MetadataWriter:
"""Utility class for serializing a Metadata object into the package.xml standard
Expand Down
1 change: 1 addition & 0 deletions addonmanager_preferences_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"MacroUpdateStatsURL": "https://addons.freecad.org/macro_update_stats.json",
"NoProxyCheck": false,
"PackageTypeSelection": 0,
"ContainsSelection": "" ,
"ProxyUrl": "",
"SearchString": "",
"SelectedAddon": "",
Expand Down
29 changes: 28 additions & 1 deletion package_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

"""Defines the PackageList QWidget for displaying a list of Addons."""
import threading
from typing import Set

import addonmanager_freecad_interface as fci
from PySideWrapper import QtCore, QtGui, QtWidgets
Expand All @@ -35,7 +36,7 @@
import addonmanager_utilities as utils
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_filter_selector import StatusFilter, Filter, ContainsFilter
from Widgets.addonmanager_widget_progress_bar import Progress, WidgetProgressBar
from addonmanager_licenses import get_license_manager

Expand Down Expand Up @@ -73,12 +74,21 @@ def __init__(self, parent=None):

# Set up the view the same as the last time:
package_type = fci.Preferences().get("PackageTypeSelection")
contains: str = fci.Preferences().get("ContainsSelection")
status = fci.Preferences().get("StatusSelection")
search_string = fci.Preferences().get("SearchString")

contains_filter = set()

if len(contains) > 0:
contains_filter = set([ContainsFilter(int(value)) for value in contains.split(",")])

self.ui.view_bar.filter_selector.set_contents_filter(package_type)
self.ui.view_bar.filter_selector.set_contains_filter(contains_filter)
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.setContainsFilter(contains_filter)
self.item_filter.setPackageFilter(package_type)
self.item_filter.setStatusFilter(status)

Expand Down Expand Up @@ -126,8 +136,13 @@ def update_status_filter(self, new_filter: Filter) -> None:

self.item_filter.setStatusFilter(new_filter.status_filter)
self.item_filter.setPackageFilter(new_filter.content_filter)
self.item_filter.setContainsFilter(new_filter.contains_filter)

contains = ",".join([str(value) for value in new_filter.contains_filter])

fci.Preferences().set("StatusSelection", new_filter.status_filter)
fci.Preferences().set("PackageTypeSelection", new_filter.content_filter)
fci.Preferences().set("ContainsSelection", contains)
self.item_filter.invalidateFilter()

def set_view_style(self, style: AddonManagerDisplayStyle) -> None:
Expand Down Expand Up @@ -573,11 +588,15 @@ def paint(


class PackageListFilter(QtCore.QSortFilterProxyModel):

contains: Set[ContainsFilter]

"""Handle filtering the item list on various criteria"""

def __init__(self):
super().__init__()
self.package_type = 0 # Default to showing everything
self.contains = set()
self.status = 0 # Default to showing any
self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.hide_non_OSI_approved = False
Expand All @@ -592,6 +611,10 @@ def setPackageFilter(
self.package_type = package_type
self.invalidateFilter()

def setContainsFilter(self, filter: Set[ContainsFilter]) -> None:
self.contains = filter
self.invalidateFilter()

def setStatusFilter(
self, status: int
) -> None: # 0=Any, 1=Installed, 2=Not installed, 3=Update available
Expand Down Expand Up @@ -645,6 +668,10 @@ def filterAcceptsRow(self, row, _parent=QtCore.QModelIndex()):
if data.status() != Addon.Status.UPDATE_AVAILABLE:
return False

if ContainsFilter.NO_GENERATED_CONTENT in self.contains:
if not data.contains_no_generated_content():
return False

license_manager = get_license_manager()
if data.status() == Addon.Status.NOT_INSTALLED:

Expand Down
Loading