diff options
| author | Warry <FunWarry@users.noreply.github.com> | 2025-12-04 12:03:31 +0100 |
|---|---|---|
| committer | Warry <FunWarry@users.noreply.github.com> | 2025-12-04 12:03:31 +0100 |
| commit | 75868b32d07aafe46b69649d2da0dc2c833f112d (patch) | |
| tree | 970a50f4cbf141af0e5e0856e087e347f3b5be48 /tidal_dl_ng/gui/main_window.py | |
| parent | 428d34056be16ed9d28597253b333dfbd7f21a47 (diff) | |
| download | tidal-dl-ng-75868b32d07aafe46b69649d2da0dc2c833f112d.tar.gz | |
refactor: Restructure GUI components into separate modules for improved maintainability
Diffstat (limited to 'tidal_dl_ng/gui/main_window.py')
| -rw-r--r-- | tidal_dl_ng/gui/main_window.py | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/tidal_dl_ng/gui/main_window.py b/tidal_dl_ng/gui/main_window.py new file mode 100644 index 0000000..33806dc --- /dev/null +++ b/tidal_dl_ng/gui/main_window.py @@ -0,0 +1,224 @@ +"""Main window for TIDAL Downloader Next Generation. + +This module combines all GUI functionality through mixins to create +the main application window. +""" + +from collections.abc import Callable + +from PySide6 import QtCore, QtGui, QtWidgets +from tidalapi import Quality + +from tidal_dl_ng.cache import TrackExtrasCache +from tidal_dl_ng.config import HandlingApp, Settings, Tidal +from tidal_dl_ng.constants import QualityVideo +from tidal_dl_ng.download import Download +from tidal_dl_ng.gui.context_menus import ContextMenusMixin +from tidal_dl_ng.gui.covers import CoverManager +from tidal_dl_ng.gui.downloads import DownloadsMixin +from tidal_dl_ng.gui.history import HistoryMixin +from tidal_dl_ng.gui.initialization import InitializationMixin +from tidal_dl_ng.gui.playlist import GuiPlaylistManager +from tidal_dl_ng.gui.progress import ProgressMixin +from tidal_dl_ng.gui.queue import GuiQueueManager +from tidal_dl_ng.gui.search import GuiSearchManager +from tidal_dl_ng.gui.signals import SignalsMixin +from tidal_dl_ng.gui.tidal_session import TidalSessionMixin +from tidal_dl_ng.gui.track_extras import TrackExtrasMixin +from tidal_dl_ng.gui.trees_results import TreesResultsMixin +from tidal_dl_ng.gui.ui_helpers import UIHelpersMixin +from tidal_dl_ng.gui.updates import UpdatesMixin +from tidal_dl_ng.helper.gui import HumanProxyModel +from tidal_dl_ng.helper.hover_manager import HoverManager +from tidal_dl_ng.history import HistoryService +from tidal_dl_ng.logger import XStream, logger_gui +from tidal_dl_ng.ui.info_tab_widget import InfoTabWidget +from tidal_dl_ng.ui.main import Ui_MainWindow +from tidal_dl_ng.worker import Worker + + +# TODO: Make more use of Exceptions +class MainWindow( + QtWidgets.QMainWindow, + Ui_MainWindow, + InitializationMixin, + TidalSessionMixin, + SignalsMixin, + ProgressMixin, + UIHelpersMixin, + TrackExtrasMixin, + UpdatesMixin, + DownloadsMixin, + TreesResultsMixin, + ContextMenusMixin, + HistoryMixin, +): + """Main application window for TIDAL Downloader Next Generation. + + Handles GUI setup, user interactions, and download logic through + a combination of mixins for better code organization. + """ + + # Type hints for class attributes + settings: Settings + tidal: Tidal + dl: Download + history_service: HistoryService + threadpool: QtCore.QThreadPool + tray: QtWidgets.QSystemTrayIcon + spinners: dict + cover_url_current: str = "" + shutdown: bool = False + model_tr_results: QtGui.QStandardItemModel = QtGui.QStandardItemModel() + proxy_tr_results: HumanProxyModel + info_tab_widget: InfoTabWidget + hover_manager: HoverManager + queue_manager: GuiQueueManager + playlist_manager: GuiPlaylistManager + search_manager: GuiSearchManager + cover_manager: CoverManager + track_extras_cache: TrackExtrasCache + _pending_extras_workers: dict[str, Worker] + _track_extras_callbacks: dict[str, Callable] + + # Qt Signals + s_spinner_start: QtCore.Signal = QtCore.Signal(QtWidgets.QWidget) + s_spinner_stop: QtCore.Signal = QtCore.Signal() + s_track_extras_ready: QtCore.Signal = QtCore.Signal(str, object) + s_invoke_callback: QtCore.Signal = QtCore.Signal(str, object) + pb_item: QtWidgets.QProgressBar + s_item_advance: QtCore.Signal = QtCore.Signal(float) + s_item_name: QtCore.Signal = QtCore.Signal(str) + s_list_name: QtCore.Signal = QtCore.Signal(str) + pb_list: QtWidgets.QProgressBar + s_list_advance: QtCore.Signal = QtCore.Signal(float) + s_pb_reset: QtCore.Signal = QtCore.Signal() + s_populate_tree_lists: QtCore.Signal = QtCore.Signal(dict) + s_populate_folder_children: QtCore.Signal = QtCore.Signal(object, list, list) + s_statusbar_message: QtCore.Signal = QtCore.Signal(object) + s_tr_results_add_top_level_item: QtCore.Signal = QtCore.Signal(object) + s_settings_save: QtCore.Signal = QtCore.Signal() + s_pb_reload_status: QtCore.Signal = QtCore.Signal(bool) + s_update_check: QtCore.Signal = QtCore.Signal(bool) + s_update_show: QtCore.Signal = QtCore.Signal(bool, bool, object) + s_queue_download_item_downloading: QtCore.Signal = QtCore.Signal(object) + s_queue_download_item_finished: QtCore.Signal = QtCore.Signal(object) + s_queue_download_item_failed: QtCore.Signal = QtCore.Signal(object) + s_queue_download_item_skipped: QtCore.Signal = QtCore.Signal(object) + + def __init__(self, tidal: Tidal | None = None) -> None: + """Initialize the main window and all components. + + Args: + tidal (Tidal | None): Optional Tidal session object. + """ + super().__init__() + self.setupUi(self) + self.setWindowTitle("TIDAL Downloader Next Generation!") + + # Initialize settings first + self.settings = Settings() + + # Initialize managers that depend on settings + self.queue_manager = GuiQueueManager(self) + self.playlist_manager = GuiPlaylistManager(self) + self.search_manager = GuiSearchManager(self) + self.info_tab_widget = InfoTabWidget(self, self.tabWidget) + + # Logging redirect + XStream.stdout().messageWritten.connect(self._log_output) + + self.history_service = HistoryService() + + # Core components + self._init_threads() + self._init_gui() + self.track_extras_cache = TrackExtrasCache() + self._pending_extras_workers: dict[str, Worker] = {} + self._track_extras_callbacks: dict[str, Callable] = {} + + # Managers that have dependencies + self.cover_manager = CoverManager(self, self.threadpool, self.info_tab_widget) + + # Initialize the rest of the UI + self.info_tab_widget.set_track_extras_provider(self.get_track_extras) + self._init_tree_results_model(self.model_tr_results) + self._init_tree_results(self.tr_results, self.model_tr_results) + self.playlist_manager.init_ui() + self.queue_manager.init_ui() + self._init_tree_lists(self.tr_lists_user) + self._init_tree_queue(self.tr_queue_download) + self._init_info() + self._init_progressbar() + self._populate_quality(self.cb_quality_audio, Quality) + self._populate_quality(self.cb_quality_video, QualityVideo) + + from tidalapi.session import SearchTypes + + self._populate_search_types(self.cb_search_type, SearchTypes) + + self.apply_settings(self.settings) + self._init_menu_actions() + self._init_signals() + + # Connect signal for invoking track extras callbacks + self.s_invoke_callback.connect(self._on_invoke_callback) + + # Connect playlist manager signals + self.s_populate_tree_lists.connect(self.on_populate_tree_lists) + + self.init_tidal(tidal) + + logger_gui.debug("All setup.") + + def closeEvent(self, event: QtGui.QCloseEvent) -> None: + """Handle the close event of the main window. + + Args: + event (QtGui.QCloseEvent): The close event. + """ + # Save the main window size and position + self.settings.data.window_x = self.x() + self.settings.data.window_y = self.y() + self.settings.data.window_w = self.width() + self.settings.data.window_h = self.height() + self.settings.save() + + self.shutdown = True + + handling_app: HandlingApp = HandlingApp() + handling_app.event_abort.set() + + event.accept() + + def apply_settings(self, settings: Settings) -> None: + """Apply user settings to the GUI. + + Args: + settings (Settings): The settings object. + """ + quality_audio = getattr(getattr(settings, "data", None), "quality_audio", 1) + quality_video = getattr(getattr(settings, "data", None), "quality_video", 0) + elements = [ + {"element": self.cb_quality_audio, "setting": quality_audio, "default_id": 1}, + {"element": self.cb_quality_video, "setting": quality_video, "default_id": 0}, + ] + + for item in elements: + idx = item["element"].findData(item["setting"]) + + if idx > -1: + item["element"].setCurrentIndex(idx) + else: + item["element"].setCurrentIndex(item["default_id"]) + + def thread_it(self, fn: Callable, *args, **kwargs) -> None: + """Run a function in a separate thread. + + Args: + fn (Callable): The function to run. + *args: Positional arguments for the function. + **kwargs: Keyword arguments for the function. + """ + worker = Worker(fn, *args, **kwargs) + self.threadpool.start(worker) |
