# Standard library imports from __future__ import annotations from dataclasses import dataclass import datetime as dt from enum import auto, Enum import functools import threading from typing import NamedTuple # Third party imports # PyQt imports from PyQt6.QtCore import ( pyqtSignal, QObject, ) from PyQt6.QtWidgets import ( QProxyStyle, QStyle, QStyleOption, ) # App imports # from music_manager import FadeCurve # Define singleton first as it's needed below def singleton(cls): """ Make a class a Singleton class (see https://realpython.com/primer-on-python-decorators/#creating-singletons) Added locking. """ lock = threading.Lock() @functools.wraps(cls) def wrapper_singleton(*args, **kwargs): if wrapper_singleton.instance is None: with lock: if wrapper_singleton.instance is None: # Check still None wrapper_singleton.instance = cls(*args, **kwargs) return wrapper_singleton.instance wrapper_singleton.instance = None return wrapper_singleton class ApplicationError(Exception): """ Custom exception """ pass class AudioMetadata(NamedTuple): start_gap: int = 0 silence_at: int = 0 fade_at: int = 0 class Col(Enum): START_GAP = 0 TITLE = auto() ARTIST = auto() INTRO = auto() DURATION = auto() START_TIME = auto() END_TIME = auto() LAST_PLAYED = auto() BITRATE = auto() NOTE = auto() class FileErrors(NamedTuple): path: str error: str @dataclass class Filter: version: int = 1 path_type: str = "contains" path: str = "" last_played_number: int = 0 last_played_comparator: str = "before" last_played_unit: str = "years" duration_type: str = "longer than" duration_number: int = 0 duration_unit: str = "minutes" class PlaylistStyle(QProxyStyle): def drawPrimitive(self, element, option, painter, widget=None): """ Draw a line across the entire row rather than just the column we're hovering over. """ if ( element == QStyle.PrimitiveElement.PE_IndicatorItemViewItemDrop and not option.rect.isNull() ): option_new = QStyleOption(option) option_new.rect.setLeft(0) if widget: option_new.rect.setRight(widget.width()) option = option_new super().drawPrimitive(element, option, painter, widget) class QueryCol(Enum): TITLE = 0 ARTIST = auto() DURATION = auto() LAST_PLAYED = auto() BITRATE = auto() class Tags(NamedTuple): artist: str = "" title: str = "" bitrate: int = 0 duration: int = 0 @dataclass class PlaylistDTO: name: str playlist_id: int favourite: bool = False is_template: bool = False open: bool = False @dataclass class TrackDTO: track_id: int artist: str bitrate: int duration: int fade_at: int intro: int | None path: str silence_at: int start_gap: int title: str lastplayed: dt.datetime | None @dataclass class PlaylistRowDTO(TrackDTO): note: str played: bool playlist_id: int playlistrow_id: int row_number: int class TrackInfo(NamedTuple): track_id: int row_number: int @singleton @dataclass class MusicMusterSignals(QObject): """ Class for all MusicMuster signals. See: - https://zetcode.com/gui/pyqt5/eventssignals/ - https://stackoverflow.com/questions/62654525/emit-a-signal-from-another-class-to-main-class """ begin_reset_model_signal = pyqtSignal(int) enable_escape_signal = pyqtSignal(bool) end_reset_model_signal = pyqtSignal(int) next_track_changed_signal = pyqtSignal() resize_rows_signal = pyqtSignal(int) search_songfacts_signal = pyqtSignal(str) search_wikipedia_signal = pyqtSignal(str) show_warning_signal = pyqtSignal(str, str) signal_add_track_to_header = pyqtSignal(int, int) signal_set_next_row = pyqtSignal(int) # TODO: undestirable (and unresolvable) reference # signal_set_next_track = pyqtSignal(PlaylistRow) span_cells_signal = pyqtSignal(int, int, int, int, int) status_message_signal = pyqtSignal(str, int) track_ended_signal = pyqtSignal() def __post_init__(self): super().__init__()