mirror of
https://github.com/NinjaCheetah/NUSGet.git
synced 2025-04-25 15:11:02 -04:00
164 lines
6.6 KiB
Python
164 lines
6.6 KiB
Python
# "modules/tree.py", licensed under the MIT license
|
|
# Copyright 2024-2025 NinjaCheetah
|
|
|
|
from modules.core import TitleData
|
|
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt, QSortFilterProxyModel
|
|
from PySide6.QtGui import QIcon
|
|
|
|
|
|
class TreeItem:
|
|
def __init__(self, data, parent=None, metadata=None):
|
|
self.data = data
|
|
self.parent = parent
|
|
self.children = []
|
|
self.metadata = metadata
|
|
|
|
def add_child(self, item):
|
|
self.children.append(item)
|
|
|
|
def child(self, row):
|
|
return self.children[row]
|
|
|
|
def child_count(self):
|
|
return len(self.children)
|
|
|
|
def column_count(self):
|
|
return len(self.data)
|
|
|
|
def data_at(self, column):
|
|
if 0 <= column < len(self.data):
|
|
return self.data[column]
|
|
return None
|
|
|
|
def row(self):
|
|
if self.parent:
|
|
return self.parent.children.index(self)
|
|
return 0
|
|
|
|
|
|
class NUSGetTreeModel(QAbstractItemModel):
|
|
def __init__(self, data, parent=None, root_name=""):
|
|
super().__init__(parent)
|
|
self.root_item = TreeItem([root_name])
|
|
self.setup_model_data(data, self.root_item)
|
|
|
|
def setup_model_data(self, title, parent):
|
|
if isinstance(title, dict):
|
|
for key, value in title.items():
|
|
if isinstance(value, list):
|
|
key_item = TreeItem([key, ""], parent)
|
|
parent.add_child(key_item)
|
|
for entry in value:
|
|
tid = entry.get("TID")
|
|
name = entry.get("Name")
|
|
versions = entry.get("Versions", {})
|
|
if tid:
|
|
tid_item = TreeItem([f"{tid} - {name}", ""], key_item, entry.get("Ticket"))
|
|
key_item.add_child(tid_item)
|
|
for region, version_list in versions.items():
|
|
region_item = TreeItem([region, ""], tid_item)
|
|
tid_item.add_child(region_item)
|
|
for version in version_list:
|
|
danger = entry.get("Danger") if entry.get("Danger") is not None else ""
|
|
archive_name = (entry.get("Archive Name") if entry.get("Archive Name") is not None
|
|
else entry.get("Name").replace(" ", "-"))
|
|
metadata = TitleData(entry.get("TID"), entry.get("Name"), archive_name,
|
|
version, entry.get("Ticket"), region, key, danger)
|
|
public_versions = entry.get("Public Versions")
|
|
if public_versions is not None:
|
|
try:
|
|
public_ver = public_versions[str(version)]
|
|
version_str = f"v{version} ({public_ver})"
|
|
except KeyError:
|
|
version_str = f"v{version}"
|
|
else:
|
|
version_str = f"v{version}"
|
|
version_item = TreeItem([version_str, ""], region_item, metadata)
|
|
region_item.add_child(version_item)
|
|
|
|
def rowCount(self, parent=QModelIndex()):
|
|
if parent.isValid():
|
|
parent_item = parent.internalPointer()
|
|
else:
|
|
parent_item = self.root_item
|
|
return parent_item.child_count()
|
|
|
|
def columnCount(self, parent=QModelIndex()):
|
|
return self.root_item.column_count()
|
|
|
|
def data(self, index, role=Qt.DisplayRole):
|
|
if not index.isValid():
|
|
return None
|
|
|
|
item = index.internalPointer()
|
|
|
|
if role == Qt.DisplayRole:
|
|
item = index.internalPointer()
|
|
return item.data_at(index.column())
|
|
|
|
if role == Qt.DecorationRole and index.column() == 0:
|
|
# Check for icons based on the "Ticket" metadata only at the TID level
|
|
if item.metadata is not None and isinstance(item.metadata, bool):
|
|
if item.metadata is True:
|
|
return QIcon.fromTheme("dialog-ok")
|
|
else:
|
|
return QIcon.fromTheme("dialog-cancel")
|
|
return None
|
|
|
|
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
|
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
|
return self.root_item.data_at(section)
|
|
return None
|
|
|
|
def index(self, row, column, parent=QModelIndex()):
|
|
if not self.hasIndex(row, column, parent):
|
|
return QModelIndex()
|
|
|
|
if not parent.isValid():
|
|
parent_item = self.root_item
|
|
else:
|
|
parent_item = parent.internalPointer()
|
|
|
|
child_item = parent_item.child(row)
|
|
if child_item:
|
|
return self.createIndex(row, column, child_item)
|
|
return QModelIndex()
|
|
|
|
def parent(self, index):
|
|
if not index.isValid():
|
|
return QModelIndex()
|
|
|
|
child_item = index.internalPointer()
|
|
parent_item = child_item.parent
|
|
|
|
if parent_item == self.root_item:
|
|
return QModelIndex()
|
|
|
|
return self.createIndex(parent_item.row(), 0, parent_item)
|
|
|
|
class TIDFilterProxyModel(QSortFilterProxyModel):
|
|
def filterAcceptsRow(self, source_row, source_parent):
|
|
source_model = self.sourceModel()
|
|
index = source_model.index(source_row, 0, source_parent)
|
|
item = index.internalPointer()
|
|
|
|
filter_text = self.filterRegularExpression().pattern().lower()
|
|
# If the item matches what the user searched for.
|
|
if item and filter_text in item.data_at(0).lower():
|
|
return True
|
|
# Check if children match, though I don't think this matters because the children of a title will always just
|
|
# be regions.
|
|
for i in range(source_model.rowCount(index)):
|
|
if self.filterAcceptsRow(i, index):
|
|
return True
|
|
# Keep the regions and versions under those for any titles that matched, so you can actually download from the
|
|
# search results.
|
|
parent_item = index.parent().internalPointer() if index.parent().isValid() else None
|
|
if parent_item and filter_text in parent_item.data_at(0).lower():
|
|
return True
|
|
else:
|
|
grandparent_item = index.parent().parent().internalPointer() if index.parent().parent().isValid() else None
|
|
if grandparent_item and filter_text in grandparent_item.data_at(0).lower():
|
|
return True
|
|
return False
|