From f46e9cf3e0d34fdd130b18587399e442431bb0e6 Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Tue, 14 May 2024 07:31:57 +0200 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20Results=20tree=20uses=20`View`?= =?UTF-8?q?=20component=20and=20displays=20filter=20input=20fields.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/gui.py | 95 ++++++++++++++++++++--------- tidal_dl_ng/helper/gui.py | 124 +++++++++++++++++++++++++++++++++++++- tidal_dl_ng/ui/main.py | 25 +++----- tidal_dl_ng/ui/main.ui | 43 +------------ 4 files changed, 198 insertions(+), 89 deletions(-) diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index aaeaed6..d8c2b4c 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -1,19 +1,21 @@ import math import sys import time -from collections.abc import Callable +from collections.abc import Callable, Sequence +from PySide6.QtGui import QStandardItem from requests.exceptions import HTTPError from tidal_dl_ng import __version__, update_available from tidal_dl_ng.dialog import DialogLogin, DialogPreferences, DialogVersion from tidal_dl_ng.helper.gui import ( + FilterHeader, + HumanProxyModel, get_queue_download_media, get_queue_download_quality, get_results_media_item, get_user_list_media_item, set_queue_download_media, - set_results_media, set_user_list_media, ) from tidal_dl_ng.helper.path import get_format_template, resource_path @@ -64,6 +66,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): tray: QtWidgets.QSystemTrayIcon spinner: QtWaitingSpinner cover_url_current: str = "" + model_tr_results: QtGui.QStandardItemModel = QtGui.QStandardItemModel() + proxy_tr_results: HumanProxyModel s_spinner_start: QtCore.Signal = QtCore.Signal(QtWidgets.QWidget) s_spinner_stop: QtCore.Signal = QtCore.Signal() pb_item: QtWidgets.QProgressBar @@ -98,7 +102,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.settings = Settings() self._init_threads() - self._init_tree_results(self.tr_results) + self._init_tree_results_model(self.model_tr_results) + self._init_tree_results(self.tr_results, self.model_tr_results) self._init_tree_lists(self.tr_lists_user) self._init_tree_queue(self.tr_queue_download) self._init_info() @@ -213,7 +218,30 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.cb_search_type.setCurrentIndex(2) - def _init_tree_results(self, tree: QtWidgets.QTableWidget): + def handle_filter_activated(self, tree: QtWidgets.QTreeView): + header = tree.header() + filters = [] + + for i in range(header.count()): + text: str = header.filterText(i) + + if text: + filters.append((i, text)) + + proxy = tree.model() + proxy.filters = filters + + def _init_tree_results(self, tree: QtWidgets.QTreeView, model: QtGui.QStandardItemModel) -> None: + header = FilterHeader(tree) + self.proxy_tr_results = HumanProxyModel(self) + + tree.setHeader(header) + tree.setModel(model) + self.proxy_tr_results.setSourceModel(model) + tree.setModel(self.proxy_tr_results) + header.set_filter_boxes(model.columnCount()) + header.filter_activated.connect(lambda: self.handle_filter_activated(tree)) + ## tree.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder) tree.setColumnHidden(1, True) tree.setColumnWidth(2, 150) @@ -224,6 +252,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) + def _init_tree_results_model(self, model: QtGui.QStandardItemModel) -> None: + model.setColumnCount(7) + model.setRowCount(0) + model.setHorizontalHeaderLabels(["#", "obj", "Artist", "Title", "Album", "Duration", "Quality"]) + def _init_tree_queue(self, tree: QtWidgets.QTableWidget): tree.setColumnHidden(1, True) tree.setColumnWidth(2, 200) @@ -357,7 +390,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.queue_download_media(queue_dl_item) def search_populate_results(self, query: str, type_media: SearchTypes): - self.tr_results.clear() + self.model_tr_results.removeRows(0, self.model_tr_results.rowCount()) results: [ResultItem] = self.search(query, [type_media]) @@ -365,20 +398,22 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def populate_tree_results(self, results: [ResultItem], parent: QtWidgets.QTreeWidgetItem = None): if not parent: - self.tr_results.clear() + self.model_tr_results.removeRows(0, self.model_tr_results.rowCount()) # Count how many digits the list length has, count_digits: int = int(math.log10(len(results) if results else 1)) + 1 for item in results: - child = self.populate_tree_result_child(item=item, index_count_digits=count_digits) + child: tuple = self.populate_tree_result_child(item=item, index_count_digits=count_digits) if parent: parent.addChild(child) else: self.s_tr_results_add_top_level_item.emit(child) - def populate_tree_result_child(self, item: [Track | Video | Mix | Album | Playlist], index_count_digits: int): + def populate_tree_result_child( + self, item: [Track | Video | Mix | Album | Playlist], index_count_digits: int + ) -> Sequence[QStandardItem]: duration: str = "" # TODO: Duration needs to be calculated later to properly fill with zeros. @@ -391,26 +426,30 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): index: str = str(item.position + 1).zfill(index_count_digits) # Populate child - child: QtWidgets.QTreeWidgetItem = QtWidgets.QTreeWidgetItem() - child.setText(0, index) - set_results_media(child, item.obj) - child.setText(2, item.artist) - child.setText(3, item.title) - child.setText(4, item.album) - child.setText(5, duration) - child.setText(6, item.quality) + child_index: QtGui.QStandardItem = QtGui.QStandardItem(index) + # TODO: Move to own method + child_obj: QtGui.QStandardItem = QtGui.QStandardItem() + + child_obj.setData(item.obj, QtCore.Qt.ItemDataRole.UserRole) + # set_results_media(child, item.obj) + + child_artist: QtGui.QStandardItem = QtGui.QStandardItem(item.artist) + child_title: QtGui.QStandardItem = QtGui.QStandardItem(item.title) + child_album: QtGui.QStandardItem = QtGui.QStandardItem(item.album) + child_duration: QtGui.QStandardItem = QtGui.QStandardItem(duration) + child_quality: QtGui.QStandardItem = QtGui.QStandardItem(item.quality) if isinstance(item.obj, Mix | Playlist | Album | Artist): # Add a disabled dummy child, so expansion arrow will appear. This Child will be replaced on expansion. - child_dummy: QtWidgets.QTreeWidgetItem = QtWidgets.QTreeWidgetItem() + child_dummy: QtGui.QStandardItem = QtGui.QStandardItem() - child_dummy.setDisabled(True) - child.addChild(child_dummy) + child_dummy.setEnabled(False) + child_index.appendRow(child_dummy) - return child + return child_index, child_obj, child_artist, child_title, child_album, child_duration, child_quality - def on_tr_results_add_top_level_item(self, widget_item: QtWidgets.QTreeWidgetItem): - self.tr_results.addTopLevelItem(widget_item) + def on_tr_results_add_top_level_item(self, item_child: Sequence[QtGui.QStandardItem]): + self.model_tr_results.appendRow(item_child) def on_settings_save(self): self.settings.save() @@ -625,11 +664,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.a_version.triggered.connect(self.on_version) self.a_preferences.triggered.connect(self.on_preferences) self.a_logout.triggered.connect(self.on_logout) - self.a_updates_check.triggered.connect(lambda x: self.on_update_check(False)) + self.a_updates_check.triggered.connect(lambda: self.on_update_check(False)) # Results - self.tr_results.itemExpanded.connect(self.on_tr_results_expanded) - self.tr_results.itemClicked.connect(self.on_result_item_clicked) + self.tr_results.expanded.connect(self.on_tr_results_expanded) + self.tr_results.clicked.connect(self.on_result_item_clicked) # Download Queue self.tr_queue_download.itemClicked.connect(self.on_queue_download_item_clicked) @@ -678,8 +717,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.list_items_show_result(media_list) self.cover_show(media_list) - def on_result_item_clicked(self, item: QtWidgets.QTreeWidgetItem, column: int) -> None: - media: Track | Video | Album | Artist = get_results_media_item(item) + def on_result_item_clicked(self, index: QtCore.QModelIndex) -> None: + media: Track | Video | Album | Artist = get_results_media_item( + index, self.proxy_tr_results, self.model_tr_results + ) self.cover_show(media) diff --git a/tidal_dl_ng/helper/gui.py b/tidal_dl_ng/helper/gui.py index 610be80..834d9b6 100644 --- a/tidal_dl_ng/helper/gui.py +++ b/tidal_dl_ng/helper/gui.py @@ -1,8 +1,12 @@ -from PySide6 import QtCore, QtWidgets +import re + +from PySide6 import QtCore, QtGui, QtWidgets from tidalapi import Album, Mix, Playlist, Track, UserPlaylist, Video from tidalapi.artist import Artist from tidalapi.media import Quality +from tidal_dl_ng.helper.gui import HumanProxyModel + def get_table_data( item: QtWidgets.QTreeWidgetItem, column: int @@ -18,8 +22,12 @@ def get_table_text(item: QtWidgets.QTreeWidgetItem, column: int) -> str: return result -def get_results_media_item(item: QtWidgets.QTreeWidgetItem) -> Track | Video | Album | Artist | Playlist | Mix: - result: Track | Video | Album | Artist | Playlist | Mix = get_table_data(item, 1) +def get_results_media_item( + index: QtCore.QModelIndex, proxy: HumanProxyModel, model: QtGui.QStandardItemModel +) -> Track | Video | Album | Artist | Playlist | Mix: + # Switch column to "obj" column and map proxy data to our model. + item: QtGui.QStandardItem = model.itemFromIndex(proxy.mapToSource(index.siblingAtColumn(1))) + result: Track | Video | Album | Artist = item.data(QtCore.Qt.ItemDataRole.UserRole) return result @@ -66,3 +74,113 @@ def set_queue_download_media( item: QtWidgets.QTreeWidgetItem, media: Mix | Playlist | UserPlaylist | Track | Video | Album | Artist ): set_table_data(item, media, 1) + + +class FilterHeader(QtWidgets.QHeaderView): + filter_activated = QtCore.Signal() + + def __init__(self, parent): + super().__init__(QtCore.Qt.Horizontal, parent) + self._editors = [] + self._padding = 4 + self.setStretchLastSection(True) + self.setSectionResizeMode(QtWidgets.QHeaderView.Stretch) + self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) + self.setSortIndicatorShown(False) + self.setSectionsMovable(True) + self.sectionResized.connect(self.adjust_positions) + parent.horizontalScrollBar().valueChanged.connect(self.adjust_positions) + + def set_filter_boxes(self, count): + while self._editors: + editor = self._editors.pop() + editor.deleteLater() + + for _ in range(count): + editor = QtWidgets.QLineEdit(self.parent()) + editor.setPlaceholderText("Filter") + editor.setClearButtonEnabled(True) + editor.returnPressed.connect(self.filter_activated.emit) + self._editors.append(editor) + + self.adjust_positions() + + def sizeHint(self): + size = super().sizeHint() + if self._editors: + height = self._editors[0].sizeHint().height() + size.setHeight(size.height() + height + self._padding) + + return size + + def updateGeometries(self): + if self._editors: + height = self._editors[0].sizeHint().height() + self.setViewportMargins(0, 0, 0, height + self._padding) + else: + self.setViewportMargins(0, 0, 0, 0) + + super().updateGeometries() + self.adjust_positions() + + def adjust_positions(self): + for index, editor in enumerate(self._editors): + height = editor.sizeHint().height() + + editor.move(self.sectionPosition(index) - self.offset() + 2, height + (self._padding // 2)) + editor.resize(self.sectionSize(index), height) + + def filter_text(self, index) -> str: + if 0 <= index < len(self._editors): + return self._editors[index].text() + + return "" + + def set_filter_text(self, index, text): + if 0 <= index < len(self._editors): + self._editors[index].setText(text) + + def clear_filters(self): + for editor in self._editors: + editor.clear() + + +class HumanProxyModel(QtCore.QSortFilterProxyModel): + def _human_key(self, key): + parts = re.split(r"(\d*\.\d+|\d+)", key) + + return tuple((e.swapcase() if i % 2 == 0 else float(e)) for i, e in enumerate(parts)) + + def lessThan(self, source_left, source_right): + data_left = source_left.data() + data_right = source_right.data() + + if isinstance(data_left, str) and isinstance(data_right, str): + return self._human_key(data_left) < self._human_key(data_right) + + return super().lessThan(source_left, source_right) + + @property + def filters(self): + if not hasattr(self, "_filters"): + self._filters = [] + + return self._filters + + @filters.setter + def filters(self, filters): + print("filters() called.") + + self._filters = filters + + self.invalidateFilter() + + def filterAcceptsRow(self, sourceRow, sourceParent) -> bool: + for i, text in self.filters: + if 0 <= i < self.columnCount(): + ix = self.sourceModel().index(sourceRow, i, sourceParent) + data = ix.data() + if str(data) in text: + return False + + return True diff --git a/tidal_dl_ng/ui/main.py b/tidal_dl_ng/ui/main.py index 0a999c8..9c1525d 100644 --- a/tidal_dl_ng/ui/main.py +++ b/tidal_dl_ng/ui/main.py @@ -1,7 +1,7 @@ ################################################################################ ## Form generated from reading UI file 'main.ui' ## -## Created by: Qt User Interface Compiler version 6.6.2 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -21,6 +21,7 @@ from PySide6.QtWidgets import ( QPushButton, QSizePolicy, QStatusBar, + QTreeView, QTreeWidget, QTreeWidgetItem, QVBoxLayout, @@ -223,7 +224,7 @@ class Ui_MainWindow: self.lv_search_result.addLayout(self.lh_search) - self.tr_results = QTreeWidget(self.w_central) + self.tr_results = QTreeView(self.w_central) self.tr_results.setObjectName("tr_results") self.tr_results.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tr_results.setProperty("showDropIndicator", False) @@ -232,8 +233,6 @@ class Ui_MainWindow: self.tr_results.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tr_results.setIndentation(10) self.tr_results.setSortingEnabled(True) - self.tr_results.header().setProperty("showSortIndicator", True) - self.tr_results.header().setStretchLastSection(False) self.lv_search_result.addWidget(self.tr_results) @@ -572,22 +571,14 @@ class Ui_MainWindow: self.pb_reload_user_lists.setText(QCoreApplication.translate("MainWindow", "Reload", None)) self.pb_download_list.setText(QCoreApplication.translate("MainWindow", "Download List", None)) - ___qtreewidgetitem4 = self.tr_results.headerItem() - ___qtreewidgetitem4.setText(6, QCoreApplication.translate("MainWindow", "Quality", None)) - ___qtreewidgetitem4.setText(5, QCoreApplication.translate("MainWindow", "Duration", None)) - ___qtreewidgetitem4.setText(4, QCoreApplication.translate("MainWindow", "Album", None)) - ___qtreewidgetitem4.setText(3, QCoreApplication.translate("MainWindow", "Title", None)) - ___qtreewidgetitem4.setText(2, QCoreApplication.translate("MainWindow", "Artist", None)) - ___qtreewidgetitem4.setText(1, QCoreApplication.translate("MainWindow", "obj", None)) - ___qtreewidgetitem4.setText(0, QCoreApplication.translate("MainWindow", "#", None)) self.te_debug.setPlaceholderText(QCoreApplication.translate("MainWindow", "Logs...", None)) self.l_pm_cover.setText("") self.l_h_queue_download.setText(QCoreApplication.translate("MainWindow", "Download Queue", None)) - ___qtreewidgetitem5 = self.tr_queue_download.headerItem() - ___qtreewidgetitem5.setText(4, QCoreApplication.translate("MainWindow", "Quality", None)) - ___qtreewidgetitem5.setText(3, QCoreApplication.translate("MainWindow", "Type", None)) - ___qtreewidgetitem5.setText(2, QCoreApplication.translate("MainWindow", "Name", None)) - ___qtreewidgetitem5.setText(1, QCoreApplication.translate("MainWindow", "obj", None)) + ___qtreewidgetitem4 = self.tr_queue_download.headerItem() + ___qtreewidgetitem4.setText(4, QCoreApplication.translate("MainWindow", "Quality", None)) + ___qtreewidgetitem4.setText(3, QCoreApplication.translate("MainWindow", "Type", None)) + ___qtreewidgetitem4.setText(2, QCoreApplication.translate("MainWindow", "Name", None)) + ___qtreewidgetitem4.setText(1, QCoreApplication.translate("MainWindow", "obj", None)) self.pb_queue_download_remove.setText(QCoreApplication.translate("MainWindow", "Remove", None)) self.pb_queue_download_clear_finished.setText(QCoreApplication.translate("MainWindow", "Clear Finished", None)) self.pb_queue_download_clear_all.setText(QCoreApplication.translate("MainWindow", "Clear All", None)) diff --git a/tidal_dl_ng/ui/main.ui b/tidal_dl_ng/ui/main.ui index a62d77b..0ad3693 100644 --- a/tidal_dl_ng/ui/main.ui +++ b/tidal_dl_ng/ui/main.ui @@ -294,7 +294,7 @@ - + QAbstractItemView::NoEditTriggers @@ -316,47 +316,6 @@ true - - true - - - false - - - - # - - - - - obj - - - - - Artist - - - - - Title - - - - - Album - - - - - Duration - - - - - Quality - - From 4353eaf0a54845dbe16d3954485fd51a3dfef3b7 Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Tue, 14 May 2024 07:32:19 +0200 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=91=B7=20Removed=20unused=20method.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/gui.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index d8c2b4c..16e1103 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -729,11 +729,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.cover_show(media) - def on_download_item_clicked(self, item: QtWidgets.QTreeWidgetItem, column: int) -> None: - media: Track | Video | Album | Artist = get_results_media_item(item) - - self.cover_show(media) - def cover_show(self, media: Album | Playlist | Track | Video | Album | Artist) -> None: cover_url: str From 5a013f518076dee10ac3e44c01e51a775ab2e16b Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Tue, 14 May 2024 07:34:18 +0200 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=91=B7=20Import=20changed.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/helper/gui.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tidal_dl_ng/helper/gui.py b/tidal_dl_ng/helper/gui.py index 834d9b6..82b7f7b 100644 --- a/tidal_dl_ng/helper/gui.py +++ b/tidal_dl_ng/helper/gui.py @@ -5,8 +5,6 @@ from tidalapi import Album, Mix, Playlist, Track, UserPlaylist, Video from tidalapi.artist import Artist from tidalapi.media import Quality -from tidal_dl_ng.helper.gui import HumanProxyModel - def get_table_data( item: QtWidgets.QTreeWidgetItem, column: int @@ -23,7 +21,7 @@ def get_table_text(item: QtWidgets.QTreeWidgetItem, column: int) -> str: def get_results_media_item( - index: QtCore.QModelIndex, proxy: HumanProxyModel, model: QtGui.QStandardItemModel + index: QtCore.QModelIndex, proxy: QtCore.QSortFilterProxyModel, model: QtGui.QStandardItemModel ) -> Track | Video | Album | Artist | Playlist | Mix: # Switch column to "obj" column and map proxy data to our model. item: QtGui.QStandardItem = model.itemFromIndex(proxy.mapToSource(index.siblingAtColumn(1))) From 79e60a0ac723cc6b307b726de25a001e96f6cdfa Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Wed, 15 May 2024 06:33:46 +0200 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=A8=20Able=20to=20place=20results=20t?= =?UTF-8?q?o=20queue.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/gui.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index 16e1103..ff853a4 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -218,30 +218,30 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.cb_search_type.setCurrentIndex(2) - def handle_filter_activated(self, tree: QtWidgets.QTreeView): - header = tree.header() + def handle_filter_activated(self): + header: FilterHeader = self.tr_results.header() filters = [] for i in range(header.count()): - text: str = header.filterText(i) + text: str = header.filter_text(i) if text: filters.append((i, text)) - proxy = tree.model() - proxy.filters = filters + proxy_model: HumanProxyModel = self.tr_results.model() + proxy_model.filters = filters def _init_tree_results(self, tree: QtWidgets.QTreeView, model: QtGui.QStandardItemModel) -> None: - header = FilterHeader(tree) - self.proxy_tr_results = HumanProxyModel(self) + header: FilterHeader = FilterHeader(tree) + self.proxy_tr_results: HumanProxyModel = HumanProxyModel(self) tree.setHeader(header) tree.setModel(model) self.proxy_tr_results.setSourceModel(model) tree.setModel(self.proxy_tr_results) header.set_filter_boxes(model.columnCount()) - header.filter_activated.connect(lambda: self.handle_filter_activated(tree)) - ## + header.filter_activated.connect(self.handle_filter_activated) + ## Styling tree.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder) tree.setColumnHidden(1, True) tree.setColumnWidth(2, 150) @@ -802,13 +802,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): # TODO: Must happen in main thread. Do not thread this. def on_download_results(self) -> None: - items: [QtWidgets.QTreeWidgetItem | None] = self.tr_results.selectedItems() + items: [HumanProxyModel | None] = self.tr_results.selectionModel().selectedRows() if len(items) == 0: logger_gui.error("Please select a row first.") else: for item in items: - media: Track | Album | Playlist | Video | Artist = get_results_media_item(item) + media: Track | Album | Playlist | Video | Artist = get_results_media_item(item, self.proxy_tr_results, self.model_tr_results) queue_dl_item: QueueDownloadItem = self.media_to_queue_download_model(media) if queue_dl_item: From b257f48d6a91b341580a940263c6c6f1609f893d Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Wed, 15 May 2024 08:33:39 +0200 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20Filtering=20keeps=20top=20level?= =?UTF-8?q?=20item=20shown.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/gui.py | 23 ++++++++++++++--------- tidal_dl_ng/helper/gui.py | 22 ++++++++++++++++------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index ff853a4..4a70840 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -396,7 +396,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.populate_tree_results(results) - def populate_tree_results(self, results: [ResultItem], parent: QtWidgets.QTreeWidgetItem = None): + def populate_tree_results(self, results: [ResultItem], parent: QtGui.QStandardItem = None): if not parent: self.model_tr_results.removeRows(0, self.model_tr_results.rowCount()) @@ -407,7 +407,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): child: tuple = self.populate_tree_result_child(item=item, index_count_digits=count_digits) if parent: - parent.addChild(child) + parent.appendRow(child) else: self.s_tr_results_add_top_level_item.emit(child) @@ -751,7 +751,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self, media_list: Album | Playlist | Mix | Artist | None = None, point: QtCore.QPoint | None = None, - parent: QtWidgets.QTreeWidgetItem = None, + parent: QtGui.QStandardItem = None, ) -> None: if point: item = self.tr_lists_user.itemAt(point) @@ -808,7 +808,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): logger_gui.error("Please select a row first.") else: for item in items: - media: Track | Album | Playlist | Video | Artist = get_results_media_item(item, self.proxy_tr_results, self.model_tr_results) + media: Track | Album | Playlist | Video | Artist = get_results_media_item( + item, self.proxy_tr_results, self.model_tr_results + ) queue_dl_item: QueueDownloadItem = self.media_to_queue_download_model(media) if queue_dl_item: @@ -957,15 +959,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def on_preferences(self) -> None: DialogPreferences(settings=self.settings, settings_save=self.s_settings_save, parent=self) - def on_tr_results_expanded(self, list_item: QtWidgets.QTreeWidgetItem) -> None: + def on_tr_results_expanded(self, index: QtCore.QModelIndex) -> None: # If the child is a dummy the list_item has not been expanded before - load_children: bool = list_item.child(0).isDisabled() + item: QtGui.QStandardItem = self.model_tr_results.itemFromIndex(self.proxy_tr_results.mapToSource(index)) + load_children: bool = not item.child(0, 0).isEnabled() if load_children: - list_item.removeChild(list_item.child(0)) - media_list: [Mix | Album | Playlist | Artist] = list_item.data(1, QtCore.Qt.ItemDataRole.UserRole) + item.removeRow(0) + media_list: [Mix | Album | Playlist | Artist] = get_results_media_item( + index, self.proxy_tr_results, self.model_tr_results + ) - self.list_items_show_result(media_list=media_list, parent=list_item) + self.list_items_show_result(media_list=media_list, parent=item) def button_reload_status(self, status: bool): button_text: str = "Reloading..." diff --git a/tidal_dl_ng/helper/gui.py b/tidal_dl_ng/helper/gui.py index 82b7f7b..82a44d8 100644 --- a/tidal_dl_ng/helper/gui.py +++ b/tidal_dl_ng/helper/gui.py @@ -105,8 +105,10 @@ class FilterHeader(QtWidgets.QHeaderView): def sizeHint(self): size = super().sizeHint() + if self._editors: height = self._editors[0].sizeHint().height() + size.setHeight(size.height() + height + self._padding) return size @@ -114,6 +116,7 @@ class FilterHeader(QtWidgets.QHeaderView): def updateGeometries(self): if self._editors: height = self._editors[0].sizeHint().height() + self.setViewportMargins(0, 0, 0, height + self._padding) else: self.setViewportMargins(0, 0, 0, 0) @@ -167,18 +170,25 @@ class HumanProxyModel(QtCore.QSortFilterProxyModel): @filters.setter def filters(self, filters): - print("filters() called.") - self._filters = filters self.invalidateFilter() - def filterAcceptsRow(self, sourceRow, sourceParent) -> bool: + def filterAcceptsRow(self, source_row: int, source_parent: QtCore.QModelIndex) -> bool: + model = self.sourceModel() + source_index = model.index(source_row, 0, source_parent) + + # Show top level children + for child_row in range(model.rowCount(source_index)): + if self.filterAcceptsRow(child_row, source_index): + return True + + # Filter for actual needle for i, text in self.filters: if 0 <= i < self.columnCount(): - ix = self.sourceModel().index(sourceRow, i, sourceParent) + ix = self.sourceModel().index(source_row, i, source_parent) data = ix.data() - if str(data) in text: - return False + + return bool(text.lower() in str(data).lower()) return True From 8fc6298d2878c2e447e8dbfb15b50cb5a250fdd4 Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Wed, 15 May 2024 09:02:08 +0200 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9C=A8=20Filter=20are=20AND=20and=20not?= =?UTF-8?q?=20OR=20linked.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/helper/gui.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tidal_dl_ng/helper/gui.py b/tidal_dl_ng/helper/gui.py index 82a44d8..0506491 100644 --- a/tidal_dl_ng/helper/gui.py +++ b/tidal_dl_ng/helper/gui.py @@ -177,6 +177,7 @@ class HumanProxyModel(QtCore.QSortFilterProxyModel): def filterAcceptsRow(self, source_row: int, source_parent: QtCore.QModelIndex) -> bool: model = self.sourceModel() source_index = model.index(source_row, 0, source_parent) + result: [bool] = [] # Show top level children for child_row in range(model.rowCount(source_index)): @@ -189,6 +190,11 @@ class HumanProxyModel(QtCore.QSortFilterProxyModel): ix = self.sourceModel().index(source_row, i, source_parent) data = ix.data() - return bool(text.lower() in str(data).lower()) + # Append results to list to enable an AND operator for filtering. + result.append(bool(text.lower() in str(data).lower())) - return True + # If no filter set, just set the result to True. + if not result: + result.append(True) + + return all(result)