refactor: Restructure GUI components into separate modules for improved maintainability
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user