Compare commits
5 Commits
0ea12eb9d9
...
4eaab98745
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4eaab98745 | ||
|
|
2fce0b34be | ||
|
|
ed7ac0758c | ||
|
|
098ce7198e | ||
|
|
38b166737b |
@ -194,7 +194,7 @@ class MusicMusterSignals(QObject):
|
|||||||
search_songfacts_signal = pyqtSignal(str)
|
search_songfacts_signal = pyqtSignal(str)
|
||||||
search_wikipedia_signal = pyqtSignal(str)
|
search_wikipedia_signal = pyqtSignal(str)
|
||||||
show_warning_signal = pyqtSignal(str, str)
|
show_warning_signal = pyqtSignal(str, str)
|
||||||
signal_add_track_to_header = pyqtSignal(int, int)
|
signal_add_track_to_header = pyqtSignal(InsertTrack)
|
||||||
signal_begin_insert_rows = pyqtSignal(InsertRows)
|
signal_begin_insert_rows = pyqtSignal(InsertRows)
|
||||||
signal_end_insert_rows = pyqtSignal(int)
|
signal_end_insert_rows = pyqtSignal(int)
|
||||||
signal_insert_track = pyqtSignal(InsertTrack)
|
signal_insert_track = pyqtSignal(InsertTrack)
|
||||||
|
|||||||
@ -21,20 +21,14 @@ from PyQt6.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from sqlalchemy.orm.session import Session
|
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
from classes import ApplicationError, InsertTrack, MusicMusterSignals
|
from classes import ApplicationError, InsertTrack, MusicMusterSignals
|
||||||
from helpers import (
|
from helpers import (
|
||||||
ask_yes_no,
|
|
||||||
get_relative_date,
|
get_relative_date,
|
||||||
ms_to_mmss,
|
ms_to_mmss,
|
||||||
)
|
)
|
||||||
from log import log
|
|
||||||
from models import Settings, Tracks
|
|
||||||
from playlistmodel import PlaylistModel
|
|
||||||
import repository
|
import repository
|
||||||
from ui import dlg_TrackSelect_ui
|
|
||||||
|
|
||||||
|
|
||||||
class TrackInsertDialog(QDialog):
|
class TrackInsertDialog(QDialog):
|
||||||
@ -111,6 +105,8 @@ class TrackInsertDialog(QDialog):
|
|||||||
# height = record.f_int or 600
|
# height = record.f_int or 600
|
||||||
# self.resize(width, height)
|
# self.resize(width, height)
|
||||||
|
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
def update_list(self, text: str) -> None:
|
def update_list(self, text: str) -> None:
|
||||||
self.track_list.clear()
|
self.track_list.clear()
|
||||||
if text.strip() == "":
|
if text.strip() == "":
|
||||||
@ -145,7 +141,6 @@ class TrackInsertDialog(QDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
insert_track_data = InsertTrack(self.playlist_id, track_id, note_text)
|
insert_track_data = InsertTrack(self.playlist_id, track_id, note_text)
|
||||||
self.signals.signal_insert_track.emit(insert_track_data)
|
|
||||||
|
|
||||||
self.title_edit.clear()
|
self.title_edit.clear()
|
||||||
self.note_edit.clear()
|
self.note_edit.clear()
|
||||||
@ -153,17 +148,13 @@ class TrackInsertDialog(QDialog):
|
|||||||
self.title_edit.setFocus()
|
self.title_edit.setFocus()
|
||||||
|
|
||||||
if self.add_to_header:
|
if self.add_to_header:
|
||||||
|
self.signals.signal_add_track_to_header.emit(insert_track_data)
|
||||||
self.accept()
|
self.accept()
|
||||||
|
else:
|
||||||
|
self.signals.signal_insert_track.emit(insert_track_data)
|
||||||
|
|
||||||
def add_and_close_clicked(self):
|
def add_and_close_clicked(self):
|
||||||
track_id = self.get_selected_track_id()
|
self.add_clicked()
|
||||||
if track_id is not None:
|
|
||||||
note_text = self.note_edit.text()
|
|
||||||
insert_track_data = InsertTrack(
|
|
||||||
playlist_id=self.playlist_id, track_id=self.track_id, note=self.note
|
|
||||||
)
|
|
||||||
insert_track_data = InsertTrack(self.playlist_id, track_id, note_text)
|
|
||||||
self.signals.signal_insert_track.emit(insert_track_data)
|
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
def selection_changed(self) -> None:
|
def selection_changed(self) -> None:
|
||||||
|
|||||||
@ -4,7 +4,6 @@ from dataclasses import dataclass, field
|
|||||||
from fuzzywuzzy import fuzz # type: ignore
|
from fuzzywuzzy import fuzz # type: ignore
|
||||||
import os.path
|
import os.path
|
||||||
import threading
|
import threading
|
||||||
from typing import Optional, Sequence
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
@ -37,13 +36,15 @@ from classes import (
|
|||||||
from config import Config
|
from config import Config
|
||||||
from helpers import (
|
from helpers import (
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
|
get_audio_metadata,
|
||||||
get_tags,
|
get_tags,
|
||||||
|
normalise_track,
|
||||||
show_OK,
|
show_OK,
|
||||||
)
|
)
|
||||||
from log import log
|
from log import log
|
||||||
from playlistrow import TrackSequence
|
from playlistrow import TrackSequence
|
||||||
from playlistmodel import PlaylistModel
|
from playlistmodel import PlaylistModel
|
||||||
import helpers
|
import repository
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -67,7 +68,7 @@ class TrackFileData:
|
|||||||
destination_path: str = ""
|
destination_path: str = ""
|
||||||
import_this_file: bool = False
|
import_this_file: bool = False
|
||||||
error: str = ""
|
error: str = ""
|
||||||
file_path_to_remove: Optional[str] = None
|
file_path_to_remove: str | None = None
|
||||||
track_id: int = 0
|
track_id: int = 0
|
||||||
track_match_data: list[TrackMatchData] = field(default_factory=list)
|
track_match_data: list[TrackMatchData] = field(default_factory=list)
|
||||||
|
|
||||||
@ -250,7 +251,7 @@ class FileImporter:
|
|||||||
artist_match=artist_score,
|
artist_match=artist_score,
|
||||||
title=existing_track.title,
|
title=existing_track.title,
|
||||||
title_match=title_score,
|
title_match=title_score,
|
||||||
track_id=existing_track.id,
|
track_id=existing_track.track_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -383,12 +384,12 @@ class FileImporter:
|
|||||||
else:
|
else:
|
||||||
tfd.destination_path = existing_track_path
|
tfd.destination_path = existing_track_path
|
||||||
|
|
||||||
def _get_existing_track(self, track_id: int) -> Tracks:
|
def _get_existing_track(self, track_id: int) -> TrackDTO:
|
||||||
"""
|
"""
|
||||||
Lookup in existing track in the local cache and return it
|
Lookup in existing track in the local cache and return it
|
||||||
"""
|
"""
|
||||||
|
|
||||||
existing_track_records = [a for a in self.existing_tracks if a.id == track_id]
|
existing_track_records = [a for a in self.existing_tracks if a.track_id == track_id]
|
||||||
if len(existing_track_records) != 1:
|
if len(existing_track_records) != 1:
|
||||||
raise ApplicationError(
|
raise ApplicationError(
|
||||||
f"Internal error in _get_existing_track: {existing_track_records=}"
|
f"Internal error in _get_existing_track: {existing_track_records=}"
|
||||||
@ -461,13 +462,12 @@ class FileImporter:
|
|||||||
# file). Check that because the path field in the database is
|
# file). Check that because the path field in the database is
|
||||||
# unique and so adding a duplicate will give a db integrity
|
# unique and so adding a duplicate will give a db integrity
|
||||||
# error.
|
# error.
|
||||||
with db.Session() as session:
|
if repository.track_with_path(tfd.destination_path):
|
||||||
if Tracks.get_by_path(session, tfd.destination_path):
|
tfd.error = (
|
||||||
tfd.error = (
|
"Importing a new track but destination path already exists "
|
||||||
"Importing a new track but destination path already exists "
|
f"in database ({tfd.destination_path})"
|
||||||
f"in database ({tfd.destination_path})"
|
)
|
||||||
)
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
# Check track_id
|
# Check track_id
|
||||||
if tfd.track_id < 0:
|
if tfd.track_id < 0:
|
||||||
@ -589,7 +589,7 @@ class DoTrackImport(QThread):
|
|||||||
tags: Tags,
|
tags: Tags,
|
||||||
destination_path: str,
|
destination_path: str,
|
||||||
track_id: int,
|
track_id: int,
|
||||||
file_path_to_remove: Optional[str] = None,
|
file_path_to_remove: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Save parameters
|
Save parameters
|
||||||
@ -620,7 +620,7 @@ class DoTrackImport(QThread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Get audio metadata in this thread rather than calling function to save interactive time
|
# Get audio metadata in this thread rather than calling function to save interactive time
|
||||||
self.audio_metadata = helpers.get_audio_metadata(self.import_file_path)
|
self.audio_metadata = get_audio_metadata(self.import_file_path)
|
||||||
|
|
||||||
# Remove old file if so requested
|
# Remove old file if so requested
|
||||||
if self.file_path_to_remove and os.path.exists(self.file_path_to_remove):
|
if self.file_path_to_remove and os.path.exists(self.file_path_to_remove):
|
||||||
@ -659,7 +659,7 @@ class DoTrackImport(QThread):
|
|||||||
return
|
return
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
helpers.normalise_track(self.destination_track_path)
|
normalise_track(self.destination_track_path)
|
||||||
|
|
||||||
self.signals.status_message_signal.emit(
|
self.signals.status_message_signal.emit(
|
||||||
f"{os.path.basename(self.import_file_path)} imported", 10000
|
f"{os.path.basename(self.import_file_path)} imported", 10000
|
||||||
|
|||||||
@ -102,8 +102,8 @@ def handle_exception(exc_type, exc_value, exc_traceback):
|
|||||||
|
|
||||||
print(stackprinter.format(exc_value, suppressed_paths=['/.venv'], style='darkbg'))
|
print(stackprinter.format(exc_value, suppressed_paths=['/.venv'], style='darkbg'))
|
||||||
|
|
||||||
msg = stackprinter.format(exc_value)
|
stack = stackprinter.format(exc_value)
|
||||||
log.error(msg)
|
log.error(stack)
|
||||||
log.error(error_msg)
|
log.error(error_msg)
|
||||||
print("Critical error:", error_msg) # Consider logging instead of print
|
print("Critical error:", error_msg) # Consider logging instead of print
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ def handle_exception(exc_type, exc_value, exc_traceback):
|
|||||||
Config.ERRORS_TO,
|
Config.ERRORS_TO,
|
||||||
Config.ERRORS_FROM,
|
Config.ERRORS_FROM,
|
||||||
"Exception (log_uncaught_exceptions) from musicmuster",
|
"Exception (log_uncaught_exceptions) from musicmuster",
|
||||||
msg,
|
stack,
|
||||||
)
|
)
|
||||||
if QApplication.instance() is not None:
|
if QApplication.instance() is not None:
|
||||||
fname = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
|
fname = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
|
||||||
|
|||||||
@ -2321,7 +2321,7 @@ class Window(QMainWindow):
|
|||||||
track.intro = intro
|
track.intro = intro
|
||||||
session.commit()
|
session.commit()
|
||||||
self.preview_manager.set_intro(intro)
|
self.preview_manager.set_intro(intro)
|
||||||
self.current.base_model.refresh_row(session, row_number)
|
self.current.base_model.refresh_row(row_number)
|
||||||
roles = [
|
roles = [
|
||||||
Qt.ItemDataRole.DisplayRole,
|
Qt.ItemDataRole.DisplayRole,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -34,6 +34,7 @@ from classes import (
|
|||||||
ApplicationError,
|
ApplicationError,
|
||||||
Col,
|
Col,
|
||||||
InsertRows,
|
InsertRows,
|
||||||
|
InsertTrack,
|
||||||
MusicMusterSignals,
|
MusicMusterSignals,
|
||||||
)
|
)
|
||||||
from config import Config
|
from config import Config
|
||||||
@ -144,16 +145,12 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
return header_row
|
return header_row
|
||||||
|
|
||||||
@log_call
|
@log_call
|
||||||
def add_track_to_header(
|
def add_track_to_header(self, track_details: InsertTrack) -> None:
|
||||||
self, playlist_id: int, track_id: int, note: str = ""
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Handle signal_add_track_to_header
|
Handle signal_add_track_to_header
|
||||||
"""
|
"""
|
||||||
|
|
||||||
log.debug(f"{self}: add_track_to_header({playlist_id=}, {track_id=}, {note=}")
|
if track_details.playlist_id != self.playlist_id:
|
||||||
|
|
||||||
if playlist_id != self.playlist_id:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.selected_rows:
|
if not self.selected_rows:
|
||||||
@ -173,8 +170,8 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if selected_row.note:
|
if selected_row.note:
|
||||||
selected_row.note += " " + note
|
selected_row.note += " " + track_details.note
|
||||||
selected_row.track_id = track_id
|
selected_row.track_id = track_details.track_id
|
||||||
|
|
||||||
# Update local copy
|
# Update local copy
|
||||||
self.refresh_row(selected_row.row_number)
|
self.refresh_row(selected_row.row_number)
|
||||||
@ -288,12 +285,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
f"current_track_started() called with no track_id ({playlist_dto=})"
|
f"current_track_started() called with no track_id ({playlist_dto=})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: ensure Playdates is updated
|
repository.update_playdates(track_id)
|
||||||
# with db.Session() as session:
|
|
||||||
# # Update Playdates in database
|
|
||||||
# log.debug(f"{self}: update playdates {track_id=}")
|
|
||||||
# Playdates(session, track_id)
|
|
||||||
# session.commit()
|
|
||||||
|
|
||||||
# Mark track as played in playlist
|
# Mark track as played in playlist
|
||||||
playlist_dto.played = True
|
playlist_dto.played = True
|
||||||
@ -359,23 +351,23 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
row = index.row()
|
row = index.row()
|
||||||
column = index.column()
|
column = index.column()
|
||||||
# rat for playlist row data as it's used a lot
|
# plr for playlist row data as it's used a lot
|
||||||
rat = self.playlist_rows[row]
|
plr = self.playlist_rows[row]
|
||||||
|
|
||||||
# These are ordered in approximately the frequency with which
|
# These are ordered in approximately the frequency with which
|
||||||
# they are called
|
# they are called
|
||||||
if role == Qt.ItemDataRole.BackgroundRole:
|
if role == Qt.ItemDataRole.BackgroundRole:
|
||||||
return self._background_role(row, column, rat)
|
return self._background_role(row, column, plr)
|
||||||
elif role == Qt.ItemDataRole.DisplayRole:
|
elif role == Qt.ItemDataRole.DisplayRole:
|
||||||
return self._display_role(row, column, rat)
|
return self._display_role(row, column, plr)
|
||||||
elif role == Qt.ItemDataRole.EditRole:
|
elif role == Qt.ItemDataRole.EditRole:
|
||||||
return self._edit_role(row, column, rat)
|
return self._edit_role(row, column, plr)
|
||||||
elif role == Qt.ItemDataRole.FontRole:
|
elif role == Qt.ItemDataRole.FontRole:
|
||||||
return self._font_role(row, column, rat)
|
return self._font_role(row, column, plr)
|
||||||
elif role == Qt.ItemDataRole.ForegroundRole:
|
elif role == Qt.ItemDataRole.ForegroundRole:
|
||||||
return self._foreground_role(row, column, rat)
|
return self._foreground_role(row, column, plr)
|
||||||
elif role == Qt.ItemDataRole.ToolTipRole:
|
elif role == Qt.ItemDataRole.ToolTipRole:
|
||||||
return self._tooltip_role(row, column, rat)
|
return self._tooltip_role(row, column, plr)
|
||||||
|
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
@ -402,7 +394,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
self.track_sequence.update()
|
self.track_sequence.update()
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
|
|
||||||
def _display_role(self, row: int, column: int, rat: PlaylistRow) -> str:
|
def _display_role(self, row: int, column: int, plr: PlaylistRow) -> str:
|
||||||
"""
|
"""
|
||||||
Return text for display
|
Return text for display
|
||||||
"""
|
"""
|
||||||
@ -420,42 +412,42 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
if header_row:
|
if header_row:
|
||||||
if column == HEADER_NOTES_COLUMN:
|
if column == HEADER_NOTES_COLUMN:
|
||||||
header_text = self.header_text(rat)
|
header_text = self.header_text(plr)
|
||||||
if not header_text:
|
if not header_text:
|
||||||
return Config.SECTION_HEADER
|
return Config.SECTION_HEADER
|
||||||
else:
|
else:
|
||||||
formatted_header = self.header_text(rat)
|
formatted_header = self.header_text(plr)
|
||||||
trimmed_header = self.remove_section_timer_markers(formatted_header)
|
trimmed_header = self.remove_section_timer_markers(formatted_header)
|
||||||
return trimmed_header
|
return trimmed_header
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if column == Col.START_TIME.value:
|
if column == Col.START_TIME.value:
|
||||||
start_time = rat.forecast_start_time
|
start_time = plr.forecast_start_time
|
||||||
if start_time:
|
if start_time:
|
||||||
return start_time.strftime(Config.TRACK_TIME_FORMAT)
|
return start_time.strftime(Config.TRACK_TIME_FORMAT)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if column == Col.END_TIME.value:
|
if column == Col.END_TIME.value:
|
||||||
end_time = rat.forecast_end_time
|
end_time = plr.forecast_end_time
|
||||||
if end_time:
|
if end_time:
|
||||||
return end_time.strftime(Config.TRACK_TIME_FORMAT)
|
return end_time.strftime(Config.TRACK_TIME_FORMAT)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if column == Col.INTRO.value:
|
if column == Col.INTRO.value:
|
||||||
if rat.intro:
|
if plr.intro:
|
||||||
return f"{rat.intro / 1000:{Config.INTRO_SECONDS_FORMAT}}"
|
return f"{plr.intro / 1000:{Config.INTRO_SECONDS_FORMAT}}"
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
dispatch_table: dict[int, str] = {
|
dispatch_table: dict[int, str] = {
|
||||||
Col.ARTIST.value: rat.artist,
|
Col.ARTIST.value: plr.artist,
|
||||||
Col.BITRATE.value: str(rat.bitrate),
|
Col.BITRATE.value: str(plr.bitrate),
|
||||||
Col.DURATION.value: ms_to_mmss(rat.duration),
|
Col.DURATION.value: ms_to_mmss(plr.duration),
|
||||||
Col.LAST_PLAYED.value: get_relative_date(rat.lastplayed),
|
Col.LAST_PLAYED.value: get_relative_date(plr.lastplayed),
|
||||||
Col.NOTE.value: rat.note,
|
Col.NOTE.value: plr.note,
|
||||||
Col.START_GAP.value: str(rat.start_gap),
|
Col.START_GAP.value: str(plr.start_gap),
|
||||||
Col.TITLE.value: rat.title,
|
Col.TITLE.value: plr.title,
|
||||||
}
|
}
|
||||||
if column in dispatch_table:
|
if column in dispatch_table:
|
||||||
return dispatch_table[column]
|
return dispatch_table[column]
|
||||||
@ -470,13 +462,12 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
if playlist_id != self.playlist_id:
|
if playlist_id != self.playlist_id:
|
||||||
return
|
return
|
||||||
with db.Session() as session:
|
self.refresh_data()
|
||||||
self.refresh_data(session)
|
|
||||||
super().endResetModel()
|
super().endResetModel()
|
||||||
self.track_sequence.update()
|
self.track_sequence.update()
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
|
|
||||||
def _edit_role(self, row: int, column: int, rat: PlaylistRow) -> str | int:
|
def _edit_role(self, row: int, column: int, plr: PlaylistRow) -> str | int:
|
||||||
"""
|
"""
|
||||||
Return value for editing
|
Return value for editing
|
||||||
"""
|
"""
|
||||||
@ -484,31 +475,25 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# If this is a header row and we're being asked for the
|
# If this is a header row and we're being asked for the
|
||||||
# HEADER_NOTES_COLUMN, return the note value
|
# HEADER_NOTES_COLUMN, return the note value
|
||||||
if self.is_header_row(row) and column == HEADER_NOTES_COLUMN:
|
if self.is_header_row(row) and column == HEADER_NOTES_COLUMN:
|
||||||
return rat.note
|
return plr.note
|
||||||
|
|
||||||
if column == Col.INTRO.value:
|
if column == Col.INTRO.value:
|
||||||
return rat.intro or 0
|
return plr.intro or 0
|
||||||
if column == Col.TITLE.value:
|
if column == Col.TITLE.value:
|
||||||
return rat.title
|
return plr.title
|
||||||
if column == Col.ARTIST.value:
|
if column == Col.ARTIST.value:
|
||||||
return rat.artist
|
return plr.artist
|
||||||
if column == Col.NOTE.value:
|
if column == Col.NOTE.value:
|
||||||
return rat.note
|
return plr.note
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _foreground_role(self, row: int, column: int, rat: PlaylistRow) -> QBrush:
|
def _foreground_role(self, row: int, column: int, plr: PlaylistRow) -> QBrush:
|
||||||
"""Return header foreground colour or QBrush() if none"""
|
"""Return header foreground colour or QBrush() if none"""
|
||||||
|
|
||||||
if self.is_header_row(row):
|
plr.row_fg = repository.get_colour(plr.note, foreground=True)
|
||||||
if rat.row_fg is None:
|
if plr.row_fg:
|
||||||
with db.Session() as session:
|
return QBrush(QColor(plr.row_fg))
|
||||||
rat.row_fg = NoteColours.get_colour(
|
|
||||||
session, rat.note, foreground=True
|
|
||||||
)
|
|
||||||
if rat.row_fg:
|
|
||||||
return QBrush(QColor(rat.row_fg))
|
|
||||||
|
|
||||||
return QBrush()
|
return QBrush()
|
||||||
|
|
||||||
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
|
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
|
||||||
@ -534,7 +519,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def _font_role(self, row: int, column: int, rat: PlaylistRow) -> QFont:
|
def _font_role(self, row: int, column: int, plr: PlaylistRow) -> QFont:
|
||||||
"""
|
"""
|
||||||
Return font
|
Return font
|
||||||
"""
|
"""
|
||||||
@ -673,21 +658,21 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
def header_text(self, rat: PlaylistRow) -> str:
|
def header_text(self, plr: PlaylistRow) -> str:
|
||||||
"""
|
"""
|
||||||
Process possible section timing directives embeded in header
|
Process possible section timing directives embeded in header
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if rat.note.endswith(Config.SECTION_STARTS):
|
if plr.note.endswith(Config.SECTION_STARTS):
|
||||||
return self.start_of_timed_section_header(rat)
|
return self.start_of_timed_section_header(plr)
|
||||||
|
|
||||||
elif rat.note.endswith("="):
|
elif plr.note.endswith("="):
|
||||||
return self.section_subtotal_header(rat)
|
return self.section_subtotal_header(plr)
|
||||||
|
|
||||||
elif rat.note == "-":
|
elif plr.note == "-":
|
||||||
# If the hyphen is the only thing on the line, echo the note
|
# If the hyphen is the only thing on the line, echo the note
|
||||||
# that started the section without the trailing "+".
|
# that started the section without the trailing "+".
|
||||||
for row_number in range(rat.row_number - 1, -1, -1):
|
for row_number in range(plr.row_number - 1, -1, -1):
|
||||||
row_rat = self.playlist_rows[row_number]
|
row_rat = self.playlist_rows[row_number]
|
||||||
if self.is_header_row(row_number):
|
if self.is_header_row(row_number):
|
||||||
if row_rat.note.endswith("-"):
|
if row_rat.note.endswith("-"):
|
||||||
@ -697,7 +682,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
return f"[End: {row_rat.note[:-1]}]"
|
return f"[End: {row_rat.note[:-1]}]"
|
||||||
return "-"
|
return "-"
|
||||||
|
|
||||||
return rat.note
|
return plr.note
|
||||||
|
|
||||||
def hide_played_tracks(self, hide: bool) -> None:
|
def hide_played_tracks(self, hide: bool) -> None:
|
||||||
"""
|
"""
|
||||||
@ -855,31 +840,6 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
if to_row_number in from_rows:
|
if to_row_number in from_rows:
|
||||||
return False # Destination within rows to be moved
|
return False # Destination within rows to be moved
|
||||||
|
|
||||||
# # Remove rows from bottom to top to avoid index shifting
|
|
||||||
# for row in sorted(from_rows, reverse=True):
|
|
||||||
# self.beginRemoveRows(QModelIndex(), row, row)
|
|
||||||
# del self.playlist_rows[row]
|
|
||||||
# # At this point self.playlist_rows has been updated but the
|
|
||||||
# # underlying database has not (that's done below after
|
|
||||||
# # inserting the rows)
|
|
||||||
# self.endRemoveRows()
|
|
||||||
|
|
||||||
# # Adjust insertion point after removal
|
|
||||||
# if to_row_number > max(from_rows):
|
|
||||||
# rows_below_dest = len([r for r in from_rows if r < to_row_number])
|
|
||||||
# insertion_point = to_row_number - rows_below_dest
|
|
||||||
# else:
|
|
||||||
# insertion_point = to_row_number
|
|
||||||
|
|
||||||
# # Insert rows at the destination
|
|
||||||
# plrid_to_new_row_number: list[dict[int, int]] = []
|
|
||||||
# for offset, row_data in enumerate(rows_to_move):
|
|
||||||
# row_number = insertion_point + offset
|
|
||||||
# self.beginInsertRows(QModelIndex(), row_number, row_number)
|
|
||||||
# self.playlist_rows[row_number] = row_data
|
|
||||||
# plrid_to_new_row_number.append({row_data.playlistrow_id: row_number})
|
|
||||||
# self.endInsertRows()
|
|
||||||
|
|
||||||
# Notify model going to change
|
# Notify model going to change
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
# Update database
|
# Update database
|
||||||
@ -887,25 +847,6 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# Notify model changed
|
# Notify model changed
|
||||||
self.endResetModel()
|
self.endResetModel()
|
||||||
|
|
||||||
# Handle the moves in row_group chunks
|
|
||||||
for row_group in row_groups:
|
|
||||||
# Tell model we will be moving rows
|
|
||||||
# See https://doc.qt.io/qt-6/qabstractitemmodel.html#beginMoveRows
|
|
||||||
# for how destination is calculated
|
|
||||||
destination = to_row_number
|
|
||||||
if to_row_number > max(row_group):
|
|
||||||
destination = to_row_number - max(row_group) + 1
|
|
||||||
super().beginMoveRows(QModelIndex(),
|
|
||||||
min(row_group),
|
|
||||||
max(row_group),
|
|
||||||
QModelIndex(),
|
|
||||||
destination
|
|
||||||
)
|
|
||||||
# Update database
|
|
||||||
repository.move_rows_within_playlist(self.playlist_id, row_group, to_row_number)
|
|
||||||
# Tell model we have finished moving rows
|
|
||||||
super().endMoveRows()
|
|
||||||
|
|
||||||
# Update display
|
# Update display
|
||||||
self.refresh_data()
|
self.refresh_data()
|
||||||
self.track_sequence.update()
|
self.track_sequence.update()
|
||||||
@ -916,26 +857,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# Qt.ItemDataRole.DisplayRole,
|
# Qt.ItemDataRole.DisplayRole,
|
||||||
# ]
|
# ]
|
||||||
# self.invalidate_rows(list(row_map.keys()), roles)
|
# self.invalidate_rows(list(row_map.keys()), roles)
|
||||||
|
return True
|
||||||
def begin_insert_rows(self, insert_rows: InsertRows) -> None:
|
|
||||||
"""
|
|
||||||
Prepare model to insert rows
|
|
||||||
"""
|
|
||||||
|
|
||||||
if insert_rows.playlist_id != self.playlist_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
super().beginInsertRows(QModelIndex(), insert_rows.from_row, insert_rows.to_row)
|
|
||||||
|
|
||||||
def end_insert_rows(self, playlist_id: int) -> None:
|
|
||||||
"""
|
|
||||||
End insert rows
|
|
||||||
"""
|
|
||||||
|
|
||||||
if playlist_id != self.playlist_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
super().endInsertRows()
|
|
||||||
|
|
||||||
@log_call
|
@log_call
|
||||||
def move_rows_between_playlists(
|
def move_rows_between_playlists(
|
||||||
@ -974,11 +896,10 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
to_row_number + len(row_group)
|
to_row_number + len(row_group)
|
||||||
)
|
)
|
||||||
self.signals.signal_begin_insert_rows.emit(insert_rows)
|
self.signals.signal_begin_insert_rows.emit(insert_rows)
|
||||||
repository.move_rows_to_playlist(from_rows=row_group,
|
repository.move_rows(from_rows=row_group,
|
||||||
from_playlist_id=self.playlist_id,
|
from_playlist_id=self.playlist_id,
|
||||||
to_row=to_row_number,
|
to_row=to_row_number,
|
||||||
to_playlist_id=to_playlist_id
|
to_playlist_id=to_playlist_id)
|
||||||
)
|
|
||||||
self.signals.signal_end_insert_rows.emit(to_playlist_id)
|
self.signals.signal_end_insert_rows.emit(to_playlist_id)
|
||||||
super().endRemoveRows()
|
super().endRemoveRows()
|
||||||
|
|
||||||
@ -986,6 +907,26 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
self.track_sequence.update()
|
self.track_sequence.update()
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
|
|
||||||
|
def begin_insert_rows(self, insert_rows: InsertRows) -> None:
|
||||||
|
"""
|
||||||
|
Prepare model to insert rows
|
||||||
|
"""
|
||||||
|
|
||||||
|
if insert_rows.playlist_id != self.playlist_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
super().beginInsertRows(QModelIndex(), insert_rows.from_row, insert_rows.to_row)
|
||||||
|
|
||||||
|
def end_insert_rows(self, playlist_id: int) -> None:
|
||||||
|
"""
|
||||||
|
End insert rows
|
||||||
|
"""
|
||||||
|
|
||||||
|
if playlist_id != self.playlist_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
super().endInsertRows()
|
||||||
|
|
||||||
@log_call
|
@log_call
|
||||||
def move_track_add_note(
|
def move_track_add_note(
|
||||||
self, new_row_number: int, existing_plr: PlaylistRow, note: str
|
self, new_row_number: int, existing_plr: PlaylistRow, note: str
|
||||||
@ -1007,23 +948,6 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
self.move_rows([existing_plr.row_number], new_row_number)
|
self.move_rows([existing_plr.row_number], new_row_number)
|
||||||
self.signals.resize_rows_signal.emit(self.playlist_id)
|
self.signals.resize_rows_signal.emit(self.playlist_id)
|
||||||
|
|
||||||
@log_call
|
|
||||||
def move_track_to_header(
|
|
||||||
self,
|
|
||||||
header_row_number: int,
|
|
||||||
existing_rat: RowAndTrack,
|
|
||||||
note: Optional[str],
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Add the existing_rat track details to the existing header at header_row_number
|
|
||||||
"""
|
|
||||||
|
|
||||||
if existing_rat.track_id:
|
|
||||||
if note and existing_rat.note:
|
|
||||||
note += "\n" + existing_rat.note
|
|
||||||
self.add_track_to_header(header_row_number, existing_rat.track_id, note)
|
|
||||||
self.delete_rows([existing_rat.row_number])
|
|
||||||
|
|
||||||
@log_call
|
@log_call
|
||||||
def obs_scene_change(self, row_number: int) -> None:
|
def obs_scene_change(self, row_number: int) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1170,17 +1094,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
looking up the playlistrow_id and retrieving the row number from the database.
|
looking up the playlistrow_id and retrieving the row number from the database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check the track_sequence.next, current and previous plrs and
|
self.track_sequence.update()
|
||||||
# update the row number
|
|
||||||
with db.Session() as session:
|
|
||||||
for ts in [
|
|
||||||
track_sequence.next,
|
|
||||||
track_sequence.current,
|
|
||||||
track_sequence.previous,
|
|
||||||
]:
|
|
||||||
if ts:
|
|
||||||
ts.update_playlist_and_row(session)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
|
|
||||||
@ -1302,7 +1216,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return len(self.playlist_rows)
|
return len(self.playlist_rows)
|
||||||
|
|
||||||
def section_subtotal_header(self, rat: PlaylistRow) -> str:
|
def section_subtotal_header(self, plr: PlaylistRow) -> str:
|
||||||
"""
|
"""
|
||||||
Process this row as subtotal within a timed section and
|
Process this row as subtotal within a timed section and
|
||||||
return display text for this row
|
return display text for this row
|
||||||
@ -1312,12 +1226,12 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
unplayed_count: int = 0
|
unplayed_count: int = 0
|
||||||
duration: int = 0
|
duration: int = 0
|
||||||
|
|
||||||
if rat.row_number == 0:
|
if plr.row_number == 0:
|
||||||
# Meaningless to have a subtotal on row 0
|
# Meaningless to have a subtotal on row 0
|
||||||
return Config.SUBTOTAL_ON_ROW_ZERO
|
return Config.SUBTOTAL_ON_ROW_ZERO
|
||||||
|
|
||||||
# Show subtotal
|
# Show subtotal
|
||||||
for row_number in range(rat.row_number - 1, -1, -1):
|
for row_number in range(plr.row_number - 1, -1, -1):
|
||||||
row_rat = self.playlist_rows[row_number]
|
row_rat = self.playlist_rows[row_number]
|
||||||
if self.is_header_row(row_number) or row_number == 0:
|
if self.is_header_row(row_number) or row_number == 0:
|
||||||
if row_rat.note.endswith(Config.SECTION_STARTS) or row_number == 0:
|
if row_rat.note.endswith(Config.SECTION_STARTS) or row_number == 0:
|
||||||
@ -1330,7 +1244,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
and (
|
and (
|
||||||
row_number
|
row_number
|
||||||
< self.track_sequence.current.row_number
|
< self.track_sequence.current.row_number
|
||||||
< rat.row_number
|
< plr.row_number
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
section_end_time = (
|
section_end_time = (
|
||||||
@ -1341,7 +1255,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
", section end time "
|
", section end time "
|
||||||
+ section_end_time.strftime(Config.TRACK_TIME_FORMAT)
|
+ section_end_time.strftime(Config.TRACK_TIME_FORMAT)
|
||||||
)
|
)
|
||||||
clean_header = self.remove_section_timer_markers(rat.note)
|
clean_header = self.remove_section_timer_markers(plr.note)
|
||||||
if clean_header:
|
if clean_header:
|
||||||
return (
|
return (
|
||||||
f"{clean_header} ["
|
f"{clean_header} ["
|
||||||
@ -1421,15 +1335,15 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
rat = self.selected_rows[0]
|
plr = self.selected_rows[0]
|
||||||
if rat.track_id is None:
|
if plr.track_id is None:
|
||||||
raise ApplicationError(f"set_next_row: no track_id ({rat=})")
|
raise ApplicationError(f"set_next_row: no track_id ({plr=})")
|
||||||
|
|
||||||
old_next_row: Optional[int] = None
|
old_next_row: Optional[int] = None
|
||||||
if self.track_sequence.next:
|
if self.track_sequence.next:
|
||||||
old_next_row = self.track_sequence.next.row_number
|
old_next_row = self.track_sequence.next.row_number
|
||||||
|
|
||||||
self.track_sequence.set_next(rat)
|
self.track_sequence.set_next(plr)
|
||||||
|
|
||||||
roles = [
|
roles = [
|
||||||
Qt.ItemDataRole.BackgroundRole,
|
Qt.ItemDataRole.BackgroundRole,
|
||||||
@ -1438,7 +1352,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# only invalidate required roles
|
# only invalidate required roles
|
||||||
self.invalidate_row(old_next_row, roles)
|
self.invalidate_row(old_next_row, roles)
|
||||||
# only invalidate required roles
|
# only invalidate required roles
|
||||||
self.invalidate_row(rat.row_number, roles)
|
self.invalidate_row(plr.row_number, roles)
|
||||||
|
|
||||||
self.signals.next_track_changed_signal.emit()
|
self.signals.next_track_changed_signal.emit()
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
@ -1552,7 +1466,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
self.sort_by_attribute(row_numbers, "title")
|
self.sort_by_attribute(row_numbers, "title")
|
||||||
|
|
||||||
def start_of_timed_section_header(self, rat: PlaylistRow) -> str:
|
def start_of_timed_section_header(self, plr: PlaylistRow) -> str:
|
||||||
"""
|
"""
|
||||||
Process this row as the start of a timed section and
|
Process this row as the start of a timed section and
|
||||||
return display text for this row
|
return display text for this row
|
||||||
@ -1562,9 +1476,9 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
unplayed_count: int = 0
|
unplayed_count: int = 0
|
||||||
duration: int = 0
|
duration: int = 0
|
||||||
|
|
||||||
clean_header = self.remove_section_timer_markers(rat.note)
|
clean_header = self.remove_section_timer_markers(plr.note)
|
||||||
|
|
||||||
for row_number in range(rat.row_number + 1, len(self.playlist_rows)):
|
for row_number in range(plr.row_number + 1, len(self.playlist_rows)):
|
||||||
row_rat = self.playlist_rows[row_number]
|
row_rat = self.playlist_rows[row_number]
|
||||||
if self.is_header_row(row_number):
|
if self.is_header_row(row_number):
|
||||||
if row_rat.note.endswith(Config.SECTION_ENDINGS):
|
if row_rat.note.endswith(Config.SECTION_ENDINGS):
|
||||||
@ -1588,7 +1502,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
def supportedDropActions(self) -> Qt.DropAction:
|
def supportedDropActions(self) -> Qt.DropAction:
|
||||||
return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction
|
return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction
|
||||||
|
|
||||||
def _tooltip_role(self, row: int, column: int, rat: PlaylistRow) -> str:
|
def _tooltip_role(self, row: int, column: int, plr: PlaylistRow) -> str:
|
||||||
"""
|
"""
|
||||||
Return tooltip. Currently only used for last_played column.
|
Return tooltip. Currently only used for last_played column.
|
||||||
"""
|
"""
|
||||||
@ -1659,20 +1573,20 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
next_track_row = self.track_sequence.next.row_number
|
next_track_row = self.track_sequence.next.row_number
|
||||||
|
|
||||||
for row_number in range(row_count):
|
for row_number in range(row_count):
|
||||||
rat = self.playlist_rows[row_number]
|
plr = self.playlist_rows[row_number]
|
||||||
|
|
||||||
# Don't update times for tracks that have been played, for
|
# Don't update times for tracks that have been played, for
|
||||||
# unreadable tracks or for the current track, handled above.
|
# unreadable tracks or for the current track, handled above.
|
||||||
if (
|
if (
|
||||||
rat.played
|
plr.played
|
||||||
or row_number == current_track_row
|
or row_number == current_track_row
|
||||||
or (rat.path and file_is_unreadable(rat.path))
|
or (plr.path and file_is_unreadable(plr.path))
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Reset start time if timing in header
|
# Reset start time if timing in header
|
||||||
if self.is_header_row(row_number):
|
if self.is_header_row(row_number):
|
||||||
header_time = get_embedded_time(rat.note)
|
header_time = get_embedded_time(plr.note)
|
||||||
if header_time:
|
if header_time:
|
||||||
next_start_time = header_time
|
next_start_time = header_time
|
||||||
continue
|
continue
|
||||||
@ -1683,7 +1597,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
and self.track_sequence.current
|
and self.track_sequence.current
|
||||||
and self.track_sequence.current.end_time
|
and self.track_sequence.current.end_time
|
||||||
):
|
):
|
||||||
next_start_time = rat.set_forecast_start_time(
|
next_start_time = plr.set_forecast_start_time(
|
||||||
update_rows, self.track_sequence.current.end_time
|
update_rows, self.track_sequence.current.end_time
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
@ -1691,11 +1605,11 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# If we're between the current and next row, zero out
|
# If we're between the current and next row, zero out
|
||||||
# times
|
# times
|
||||||
if (current_track_row or row_count) < row_number < (next_track_row or 0):
|
if (current_track_row or row_count) < row_number < (next_track_row or 0):
|
||||||
rat.set_forecast_start_time(update_rows, None)
|
plr.set_forecast_start_time(update_rows, None)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Set start/end
|
# Set start/end
|
||||||
rat.forecast_start_time = next_start_time
|
plr.forecast_start_time = next_start_time
|
||||||
|
|
||||||
# Update start/stop times of rows that have changed
|
# Update start/stop times of rows that have changed
|
||||||
for updated_row in update_rows:
|
for updated_row in update_rows:
|
||||||
|
|||||||
@ -110,13 +110,13 @@ class PlaylistRow:
|
|||||||
Adding a track_id should only happen to a header row.
|
Adding a track_id should only happen to a header row.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.track_id:
|
if self.track_id > 0:
|
||||||
raise ApplicationError("Attempting to add track to row with existing track ({self=}")
|
raise ApplicationError("Attempting to add track to row with existing track ({self=}")
|
||||||
|
|
||||||
# TODO: set up write access to track_id. Should only update if
|
# TODO: set up write access to track_id. Should only update if
|
||||||
# track_id == 0. Need to update all other track fields at the
|
# track_id == 0. Need to update all other track fields at the
|
||||||
# same time.
|
# same time.
|
||||||
print("set track_id attribute for {self=}, {value=}")
|
print(f"set track_id attribute for {self=}, {value=}")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Expose PlaylistRowDTO fields as properties
|
# Expose PlaylistRowDTO fields as properties
|
||||||
@ -127,7 +127,7 @@ class PlaylistRow:
|
|||||||
@note.setter
|
@note.setter
|
||||||
def note(self, value: str) -> None:
|
def note(self, value: str) -> None:
|
||||||
# TODO set up write access to db
|
# TODO set up write access to db
|
||||||
print("set note attribute for {self=}, {value=}")
|
print(f"set note attribute for {self=}, {value=}")
|
||||||
# self.dto.note = value
|
# self.dto.note = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -137,7 +137,7 @@ class PlaylistRow:
|
|||||||
@played.setter
|
@played.setter
|
||||||
def played(self, value: bool = True) -> None:
|
def played(self, value: bool = True) -> None:
|
||||||
# TODO set up write access to db
|
# TODO set up write access to db
|
||||||
print("set played attribute for {self=}")
|
print(f"set played attribute for {self=}")
|
||||||
# self.dto.played = value
|
# self.dto.played = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -35,7 +35,13 @@ from PyQt6.QtWidgets import (
|
|||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
from audacity_controller import AudacityController
|
from audacity_controller import AudacityController
|
||||||
from classes import ApplicationError, Col, MusicMusterSignals, PlaylistStyle, TrackInfo
|
from classes import (
|
||||||
|
ApplicationError,
|
||||||
|
Col,
|
||||||
|
MusicMusterSignals,
|
||||||
|
PlaylistStyle,
|
||||||
|
TrackInfo
|
||||||
|
)
|
||||||
from config import Config
|
from config import Config
|
||||||
from dialogs import TrackInsertDialog
|
from dialogs import TrackInsertDialog
|
||||||
from helpers import (
|
from helpers import (
|
||||||
@ -48,6 +54,7 @@ from log import log, log_call
|
|||||||
from models import db, Settings
|
from models import db, Settings
|
||||||
from playlistrow import TrackSequence
|
from playlistrow import TrackSequence
|
||||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||||
|
import repository
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from musicmuster import Window
|
from musicmuster import Window
|
||||||
@ -513,18 +520,12 @@ class PlaylistTab(QTableView):
|
|||||||
def _add_track(self) -> None:
|
def _add_track(self) -> None:
|
||||||
"""Add a track to a section header making it a normal track row"""
|
"""Add a track to a section header making it a normal track row"""
|
||||||
|
|
||||||
model_row_number = self.source_model_selected_row_number()
|
dlg = TrackInsertDialog(
|
||||||
if model_row_number is None:
|
parent=self.musicmuster,
|
||||||
return
|
playlist_id=self.playlist_id,
|
||||||
|
add_to_header=True,
|
||||||
with db.Session() as session:
|
)
|
||||||
dlg = TrackInsertDialog(
|
dlg.exec()
|
||||||
parent=self.musicmuster,
|
|
||||||
playlist_id=self.playlist_id,
|
|
||||||
add_to_header=True,
|
|
||||||
)
|
|
||||||
dlg.exec()
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
def _build_context_menu(self, item: QTableWidgetItem) -> None:
|
def _build_context_menu(self, item: QTableWidgetItem) -> None:
|
||||||
"""Used to process context (right-click) menu, which is defined here"""
|
"""Used to process context (right-click) menu, which is defined here"""
|
||||||
@ -685,11 +686,10 @@ class PlaylistTab(QTableView):
|
|||||||
# Resize rows if necessary
|
# Resize rows if necessary
|
||||||
self.resizeRowsToContents()
|
self.resizeRowsToContents()
|
||||||
|
|
||||||
with db.Session() as session:
|
# Save settings
|
||||||
attr_name = f"playlist_col_{column_number}_width"
|
repository.set_setting(
|
||||||
record = Settings.get_setting(session, attr_name)
|
f"playlist_col_{column_number}_width", self.columnWidth(column_number)
|
||||||
record.f_int = self.columnWidth(column_number)
|
)
|
||||||
session.commit()
|
|
||||||
|
|
||||||
def _context_menu(self, pos):
|
def _context_menu(self, pos):
|
||||||
"""Display right-click menu"""
|
"""Display right-click menu"""
|
||||||
@ -1063,14 +1063,13 @@ class PlaylistTab(QTableView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Last column is set to stretch so ignore it here
|
# Last column is set to stretch so ignore it here
|
||||||
with db.Session() as session:
|
for column_number in range(header.count() - 1):
|
||||||
for column_number in range(header.count() - 1):
|
attr_name = f"playlist_col_{column_number}_width"
|
||||||
attr_name = f"playlist_col_{column_number}_width"
|
value = repository.get_setting(attr_name)
|
||||||
record = Settings.get_setting(session, attr_name)
|
if value is not None:
|
||||||
if record.f_int is not None:
|
self.setColumnWidth(column_number, value)
|
||||||
self.setColumnWidth(column_number, record.f_int)
|
else:
|
||||||
else:
|
self.setColumnWidth(column_number, Config.DEFAULT_COLUMN_WIDTH)
|
||||||
self.setColumnWidth(column_number, Config.DEFAULT_COLUMN_WIDTH)
|
|
||||||
|
|
||||||
def set_row_as_next_track(self) -> None:
|
def set_row_as_next_track(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -136,7 +136,7 @@ class QuerylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
row = index.row()
|
row = index.row()
|
||||||
column = index.column()
|
column = index.column()
|
||||||
# rat for playlist row data as it's used a lot
|
# plr for playlist row data as it's used a lot
|
||||||
qrow = self.querylist_rows[row]
|
qrow = self.querylist_rows[row]
|
||||||
|
|
||||||
# Dispatch to role-specific functions
|
# Dispatch to role-specific functions
|
||||||
@ -268,7 +268,7 @@ class QuerylistModel(QAbstractTableModel):
|
|||||||
bottom_right = self.index(row, self.columnCount() - 1)
|
bottom_right = self.index(row, self.columnCount() - 1)
|
||||||
self.dataChanged.emit(top_left, bottom_right, [Qt.ItemDataRole.BackgroundRole])
|
self.dataChanged.emit(top_left, bottom_right, [Qt.ItemDataRole.BackgroundRole])
|
||||||
|
|
||||||
def _tooltip_role(self, row: int, column: int, rat: PlaylistRow) -> str | QVariant:
|
def _tooltip_role(self, row: int, column: int, plr: PlaylistRow) -> str | QVariant:
|
||||||
"""
|
"""
|
||||||
Return tooltip. Currently only used for last_played column.
|
Return tooltip. Currently only used for last_played column.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -238,6 +238,24 @@ def _tracks_where(where: BinaryExpression | ColumnElement[bool]) -> list[TrackDT
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def track_with_path(path: str) -> bool:
|
||||||
|
"""
|
||||||
|
Return True if a track with passed path exists, else False
|
||||||
|
"""
|
||||||
|
|
||||||
|
with db.Session() as session:
|
||||||
|
track = (
|
||||||
|
session.execute(
|
||||||
|
select(Tracks)
|
||||||
|
.where(Tracks.path == path)
|
||||||
|
)
|
||||||
|
.scalars()
|
||||||
|
.one_or_none()
|
||||||
|
)
|
||||||
|
|
||||||
|
return track is not None
|
||||||
|
|
||||||
|
|
||||||
def tracks_like_artist(filter_str: str) -> list[TrackDTO]:
|
def tracks_like_artist(filter_str: str) -> list[TrackDTO]:
|
||||||
"""
|
"""
|
||||||
Return tracks where artist is like filter
|
Return tracks where artist is like filter
|
||||||
@ -399,6 +417,15 @@ def move_rows(
|
|||||||
_check_playlist_integrity(session, to_playlist_id, fix=False)
|
_check_playlist_integrity(session, to_playlist_id, fix=False)
|
||||||
|
|
||||||
|
|
||||||
|
def update_playdates(track_id: int) -> None:
|
||||||
|
"""
|
||||||
|
Update playdates for passed track
|
||||||
|
"""
|
||||||
|
|
||||||
|
with db.Session() as session:
|
||||||
|
_ = Playdates(session, track_id)
|
||||||
|
|
||||||
|
|
||||||
def update_row_numbers(
|
def update_row_numbers(
|
||||||
playlist_id: int, id_to_row_number: list[dict[int, int]]
|
playlist_id: int, id_to_row_number: list[dict[int, int]]
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -527,7 +554,7 @@ def get_playlist_row(playlistrow_id: int) -> PlaylistRowDTO | None:
|
|||||||
|
|
||||||
|
|
||||||
def get_playlist_rows(
|
def get_playlist_rows(
|
||||||
playlist_id: int, check_playlist_itegrity=True
|
playlist_id: int, check_playlist_itegrity: bool = True
|
||||||
) -> list[PlaylistRowDTO]:
|
) -> list[PlaylistRowDTO]:
|
||||||
# Alias PlaydatesTable for subquery
|
# Alias PlaydatesTable for subquery
|
||||||
LatestPlaydate = aliased(Playdates)
|
LatestPlaydate = aliased(Playdates)
|
||||||
@ -724,7 +751,7 @@ def get_setting(name: str) -> int | None:
|
|||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
record = session.execute(
|
record = session.execute(
|
||||||
select(Settings).where(Settings.name == name)
|
select(Settings).where(Settings.name == name)
|
||||||
).one_or_none()
|
).scalars().one_or_none()
|
||||||
if not record:
|
if not record:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -739,7 +766,7 @@ def set_setting(name: str, value: int) -> None:
|
|||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
record = session.execute(
|
record = session.execute(
|
||||||
select(Settings).where(Settings.name == name)
|
select(Settings).where(Settings.name == name)
|
||||||
).one_or_none()
|
).scalars().one_or_none()
|
||||||
if not record:
|
if not record:
|
||||||
record = Settings(session=session, name=name)
|
record = Settings(session=session, name=name)
|
||||||
if not record:
|
if not record:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user