diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..80c8292 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..306f5f3 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..ed491e5 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,63 @@ + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..cc5462d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a6525a8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..292ffec --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.idea/tidal-dl-ng.iml b/.idea/tidal-dl-ng.iml new file mode 100644 index 0000000..64c736c --- /dev/null +++ b/.idea/tidal-dl-ng.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..dcb6b8c --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tidal_dl_ng/constants.py b/tidal_dl_ng/constants.py index 802fedf..e5a877f 100644 --- a/tidal_dl_ng/constants.py +++ b/tidal_dl_ng/constants.py @@ -52,6 +52,14 @@ class QueueDownloadStatus(StrEnum): Skipped: str = "↪️" +class SearchTypes(StrEnum): + Track: str = "Track" + Video: str = "Video" + Playlist: str = "Playlist" + Album: str = "Album" + Artist: str = "Artist" + + FAVORITES: {} = { "fav_videos": {"name": "Videos", "function_name": "videos"}, "fav_tracks": {"name": "Tracks", "function_name": "tracks"}, diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index 870b658..8341dad 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -48,7 +48,10 @@ import sys import time from collections.abc import Callable, Sequence +from copy import deepcopy +from functools import partial +from PySide6.QtGui import QAction from requests.exceptions import HTTPError from tidalapi.session import LinkLogin @@ -148,6 +151,10 @@ def __init__(self, tidal: Tidal | None = None): # XStream.stderr().messageWritten.connect(self._log_output) self.settings = Settings() + # Default result tree list context menu items + self.tr_results_headers_ctx_items: list[str] = [] + # Loads previously saved Result Tree Header from settings + self._load_rt_from_settings() self._init_threads() self._init_tree_results_model(self.model_tr_results) @@ -272,7 +279,18 @@ def _populate_search_types(self, ui_target: QtWidgets.QComboBox, options: Search if item: ui_target.addItem(item.__name__, item) - self.cb_search_type.setCurrentIndex(2) + search_type = self.settings.data.search_type.capitalize() + # sets the last used Search type + self.cb_search_type.setCurrentIndex(self.cb_search_type.findText(search_type)) + # self.cb_search_type.setCurrentIndex(2) + + def _save_cb_search_type(self): + """ + Save the last used search type when changing it + """ + search_type = self.cb_search_type.currentText() + self.settings.data.search_type = search_type + self.settings.save() def handle_filter_activated(self): header: FilterHeader = self.tr_results.header() @@ -307,6 +325,26 @@ def _init_tree_results(self, tree: QtWidgets.QTreeView, model: QtGui.QStandardIt # Connect the contextmenu tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) tree.customContextMenuRequested.connect(self.menu_context_tree_results) + # Connect Header visibility context menu + tree.header().setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + tree.header().customContextMenuRequested.connect(self.menu_context_tree_headers) + + hidden = [] + labels_column: [str] = deepcopy(self.tr_results_headers_ctx_items) + for index, item in enumerate(labels_column): + if "obj" in item: + continue + if "❌" in item: + if "#" not in item: + index += 1 + hidden.append(index) + labels_column[index] = item.replace("❌", "") + elif "✅" in item: + if "#" not in item: + index += 1 + labels_column[index] = item.replace("✅", "") + for i in hidden: + tree.setColumnHidden(i, True) def _init_tree_results_model(self, model: QtGui.QStandardItemModel) -> None: labels_column: [str] = ["#", "obj", "Artist", "Title", "Album", "Duration", "Quality", "Date Added"] @@ -315,6 +353,44 @@ def _init_tree_results_model(self, model: QtGui.QStandardItemModel) -> None: model.setRowCount(0) model.setHorizontalHeaderLabels(labels_column) + def _load_rt_from_settings(self): + settings = self.settings.data + for i in settings.rt_header_ctx.split(","): + if "obj" in i: + continue + self.tr_results_headers_ctx_items.append(i) + + def menu_context_tree_headers(self): + """ + Adds context menu to the header of the result tree list + """ + menu = QtWidgets.QMenu() + for m in self.tr_results_headers_ctx_items: + action = QAction(m, self) + action.triggered.connect(partial(self._toggle_header_section_hidden, m)) + menu.addAction(action) + + menu.exec(QtGui.QCursor.pos()) + + def _toggle_header_section_hidden(self, item: str): + """ + Toggles result tree list columns visibility + Then save the changes to settings + """ + index = self.tr_results_headers_ctx_items.index(item) + is_visible = "✅" in item + new_label = item.replace("✅" if is_visible else "❌", "❌" if is_visible else "✅") + self.tr_results_headers_ctx_items[index] = new_label + if "#" not in item: + index += 1 + + self.tr_results.header().setSectionHidden(index, is_visible) + updated_tr_results_headers_ctx_items = deepcopy(self.tr_results_headers_ctx_items) + updated_tr_results_headers_ctx_items.insert(1, "✅obj") + self.settings.data.rt_header_ctx = "" + self.settings.data.rt_header_ctx = ",".join(updated_tr_results_headers_ctx_items) + self.settings.save() + def _init_tree_queue(self, tree: QtWidgets.QTableWidget): tree.setColumnHidden(1, True) tree.setColumnWidth(2, 200) @@ -759,6 +835,7 @@ def _init_signals(self): self.pb_search.clicked.connect( lambda: self.search_populate_results(self.l_search.text(), self.cb_search_type.currentData()) ) + self.cb_search_type.currentIndexChanged.connect(lambda: self._save_cb_search_type()) self.cb_quality_audio.currentIndexChanged.connect(self.on_quality_set_audio) self.cb_quality_video.currentIndexChanged.connect(self.on_quality_set_video) self.tr_lists_user.itemClicked.connect(self.on_list_items_show) diff --git a/tidal_dl_ng/model/cfg.py b/tidal_dl_ng/model/cfg.py index 69c394d..7a6f70a 100644 --- a/tidal_dl_ng/model/cfg.py +++ b/tidal_dl_ng/model/cfg.py @@ -3,7 +3,7 @@ from dataclasses_json import dataclass_json from tidalapi import Quality -from tidal_dl_ng.constants import CoverDimensions, QualityVideo +from tidal_dl_ng.constants import CoverDimensions, QualityVideo, SearchTypes @dataclass_json @@ -45,6 +45,8 @@ class Settings: symlink_to_track: bool = False playlist_create: bool = False metadata_replay_gain: bool = True + search_type: SearchTypes = SearchTypes.Track + rt_header_ctx: str = "✅#,✅obj,✅Artist,✅Title,✅Album,✅Duration,✅Quality,✅Date Added" @dataclass_json @@ -99,6 +101,8 @@ class HelpSettings: ) playlist_create: str = "Creates a '_playlist.m3u8' file for downloaded albums, playlists and mixes." metadata_replay_gain: str = "Replay gain information will be written to metadata." + search_type: str = "Search for tracks by artist, album or track name." + rt_header_ctx: str = "Result Tree List columns visibility." @dataclass_json