summaryrefslogtreecommitdiff
path: root/tidal_dl_ng/gui/trees_results.py
diff options
context:
space:
mode:
authorWarry <FunWarry@users.noreply.github.com>2025-12-04 12:03:31 +0100
committerWarry <FunWarry@users.noreply.github.com>2025-12-04 12:03:31 +0100
commit75868b32d07aafe46b69649d2da0dc2c833f112d (patch)
tree970a50f4cbf141af0e5e0856e087e347f3b5be48 /tidal_dl_ng/gui/trees_results.py
parent428d34056be16ed9d28597253b333dfbd7f21a47 (diff)
downloadtidal-dl-ng-75868b32d07aafe46b69649d2da0dc2c833f112d.tar.gz
refactor: Restructure GUI components into separate modules for improved maintainability
Diffstat (limited to 'tidal_dl_ng/gui/trees_results.py')
-rw-r--r--tidal_dl_ng/gui/trees_results.py232
1 files changed, 232 insertions, 0 deletions
diff --git a/tidal_dl_ng/gui/trees_results.py b/tidal_dl_ng/gui/trees_results.py
new file mode 100644
index 0000000..0658078
--- /dev/null
+++ b/tidal_dl_ng/gui/trees_results.py
@@ -0,0 +1,232 @@
+"""Trees and results mixin for MainWindow.
+
+Handles tree view population and results display.
+"""
+
+import contextlib
+import math
+from collections.abc import Callable, Sequence
+
+from PySide6 import QtCore, QtGui, QtWidgets
+from tidalapi import Album, Artist, Mix, Playlist, Track, UserPlaylist, Video
+from tidalapi.playlist import Folder
+
+from tidal_dl_ng.constants import FAVORITES, TidalLists
+from tidal_dl_ng.helper.gui import (
+ HumanProxyModel,
+ get_results_media_item,
+ get_user_list_media_item,
+ set_user_list_media,
+)
+from tidal_dl_ng.helper.tidal import favorite_function_factory, items_results_all, user_media_lists
+from tidal_dl_ng.model.gui_data import ResultItem
+
+
+class TreesResultsMixin:
+ """Mixin containing tree view and results management methods."""
+
+ def handle_filter_activated(self) -> None:
+ """Handle activation of filter headers in the results tree."""
+ header = self.tr_results.header()
+ filters: list[tuple[int, str]] = []
+ for i in range(header.count()):
+ text: str = header.filter_text(i)
+ if text:
+ filters.append((i, text))
+
+ proxy_model: HumanProxyModel = self.tr_results.model()
+ proxy_model.filters = filters
+
+ def populate_tree_results(self, results: list[ResultItem], parent: QtGui.QStandardItem | None = None) -> None:
+ """Populate the results tree with ResultItem objects."""
+ if not parent:
+ self.model_tr_results.removeRows(0, self.model_tr_results.rowCount())
+
+ count_digits: int = int(math.log10(len(results) if results else 1)) + 1
+
+ for item in results:
+ child: tuple = self.populate_tree_result_child(item=item, index_count_digits=count_digits)
+
+ if parent:
+ parent.appendRow(child)
+ else:
+ self.s_tr_results_add_top_level_item.emit(child)
+
+ def populate_tree_result_child(self, item: ResultItem, index_count_digits: int) -> Sequence[QtGui.QStandardItem]:
+ """Create a row of QStandardItems for a ResultItem."""
+ duration: str = ""
+
+ if item.duration_sec > -1:
+ m, s = divmod(item.duration_sec, 60)
+ duration: str = f"{m:02d}:{s:02d}"
+
+ index: str = str(item.position + 1).zfill(index_count_digits)
+
+ child_index: QtGui.QStandardItem = QtGui.QStandardItem(index)
+ child_obj: QtGui.QStandardItem = QtGui.QStandardItem()
+ child_obj.setData(item.obj, QtCore.Qt.ItemDataRole.UserRole)
+
+ 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)
+ child_date: QtGui.QStandardItem = QtGui.QStandardItem(
+ item.date_user_added if item.date_user_added != "" else item.date_release
+ )
+
+ child_downloaded: QtGui.QStandardItem = QtGui.QStandardItem()
+ if isinstance(item.obj, Track):
+ track_id = str(item.obj.id)
+ if self.history_service.is_downloaded(track_id):
+ child_downloaded.setText("✅")
+ child_downloaded.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+
+ if isinstance(item.obj, Mix | Playlist | Album | Artist):
+ child_dummy: QtGui.QStandardItem = QtGui.QStandardItem()
+ child_dummy.setEnabled(False)
+ child_index.appendRow(child_dummy)
+
+ return (
+ child_index,
+ child_obj,
+ child_artist,
+ child_title,
+ child_album,
+ child_duration,
+ child_quality,
+ child_date,
+ child_downloaded,
+ )
+
+ def on_tr_results_add_top_level_item(self, item_child: Sequence[QtGui.QStandardItem]):
+ """Add a top-level item to the results tree model."""
+ self.model_tr_results.appendRow(item_child)
+
+ def on_tr_results_expanded(self, index: QtCore.QModelIndex) -> None:
+ """Handle the event when a result item group is expanded."""
+ self.thread_it(self.tr_results_expanded, index)
+
+ def tr_results_expanded(self, index: QtCore.QModelIndex) -> None:
+ """Load and display the children of an expanded result item."""
+ 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:
+ item.removeRow(0)
+ media_list = get_results_media_item(index, self.proxy_tr_results, self.model_tr_results)
+
+ self.s_spinner_start.emit(self.tr_results)
+
+ try:
+ self.list_items_show_result(media_list=media_list, parent=item)
+ finally:
+ self.s_spinner_stop.emit()
+
+ def list_items_show_result(
+ self,
+ media_list: Album | Playlist | Mix | Artist | None = None,
+ point: QtCore.QPoint | None = None,
+ parent: QtGui.QStandardItem | None = None,
+ favorite_function: Callable | None = None,
+ ) -> None:
+ """Populate the results tree with the items of a media list."""
+ if point:
+ item = self.tr_lists_user.itemAt(point)
+ media_list = get_user_list_media_item(item)
+
+ if favorite_function or isinstance(media_list, str):
+ if isinstance(media_list, str):
+ favorite_function = favorite_function_factory(self.tidal, media_list)
+ media_items: list[Track | Video | Album] = favorite_function()
+ else:
+ media_items: list[Track | Video | Album] = items_results_all(self.tidal.session, media_list)
+
+ result: list[ResultItem] = self.search_manager.search_result_to_model(media_items)
+ self.populate_tree_results(result, parent=parent)
+
+ def tidal_user_lists(self) -> None:
+ """Fetch and emit user playlists, mixes, and favorites from Tidal."""
+ self.s_spinner_start.emit(self.tr_lists_user)
+ self.s_pb_reload_status.emit(False)
+
+ user_all: dict[str, list] = user_media_lists(self.tidal.session)
+ self.s_populate_tree_lists.emit(user_all)
+
+ def on_populate_tree_lists(self, user_lists: dict[str, list]) -> None:
+ """Populate the user lists tree with playlists, mixes, and favorites."""
+ twi_playlists: QtWidgets.QTreeWidgetItem = self.tr_lists_user.findItems(
+ TidalLists.Playlists, QtCore.Qt.MatchExactly, 0
+ )[0]
+ twi_mixes: QtWidgets.QTreeWidgetItem = self.tr_lists_user.findItems(
+ TidalLists.Mixes, QtCore.Qt.MatchExactly, 0
+ )[0]
+ twi_favorites: QtWidgets.QTreeWidgetItem = self.tr_lists_user.findItems(
+ TidalLists.Favorites, QtCore.Qt.MatchExactly, 0
+ )[0]
+
+ for twi in [twi_playlists, twi_mixes]:
+ for i in reversed(range(twi.childCount())):
+ twi.removeChild(twi.child(i))
+
+ for item in user_lists.get("playlists", []):
+ if isinstance(item, Folder):
+ twi_child = QtWidgets.QTreeWidgetItem(twi_playlists)
+ name: str = f"📁 {item.name}"
+ info: str = f"({item.total_number_of_items} items)" if item.total_number_of_items else ""
+ twi_child.setText(0, name)
+ set_user_list_media(twi_child, item)
+ twi_child.setText(2, info)
+
+ dummy_child = QtWidgets.QTreeWidgetItem(twi_child)
+ dummy_child.setDisabled(True)
+ elif isinstance(item, UserPlaylist | Playlist):
+ twi_child = QtWidgets.QTreeWidgetItem(twi_playlists)
+ name: str = item.name if getattr(item, "name", None) is not None else ""
+ description: str = f" {item.description}" if item.description else ""
+ info: str = f"({item.num_tracks + item.num_videos} Tracks){description}"
+ twi_child.setText(0, name)
+ set_user_list_media(twi_child, item)
+ twi_child.setText(2, info)
+
+ for item in user_lists.get("mixes", []):
+ if isinstance(item, Mix):
+ twi_child = QtWidgets.QTreeWidgetItem(twi_mixes)
+ name: str = item.title
+ info: str = item.sub_title
+ twi_child.setText(0, name)
+ set_user_list_media(twi_child, item)
+ twi_child.setText(2, info)
+
+ for i in reversed(range(twi_favorites.childCount())):
+ twi_favorites.removeChild(twi_favorites.child(i))
+
+ for key, favorite in FAVORITES.items():
+ twi_child = QtWidgets.QTreeWidgetItem(twi_favorites)
+ name: str = favorite["name"]
+ info: str = ""
+
+ twi_child.setText(0, name)
+ set_user_list_media(twi_child, key)
+ twi_child.setText(2, info)
+
+ self.s_spinner_stop.emit()
+ self.s_pb_reload_status.emit(True)
+
+ def on_track_hover_confirmed(self, media: Track | Video | Album | Mix | Playlist | Artist) -> None:
+ """Handle confirmed hover event over a track (after debounce delay)."""
+ if not media:
+ return
+
+ self.info_tab_widget.update_on_hover(media)
+
+ if self.cover_manager:
+ self.cover_manager.load_cover(media, use_cache_check=True)
+
+ def on_track_hover_left(self) -> None:
+ """Handle hover leaving the track list."""
+ with contextlib.suppress(Exception):
+ self.info_tab_widget.revert_to_selection()
+
+ if self.info_tab_widget.current_media_selected and self.cover_manager:
+ self.cover_manager.load_cover(self.info_tab_widget.current_media_selected, use_cache_check=True)