Compare commits

..

18 Commits

Author SHA1 Message Date
Keith Edmunds
0ea12eb9d9 Clean up merge from dev 2025-03-30 12:05:41 +01:00
Keith Edmunds
75cc7a3f19 Merge changes from dev 2025-03-30 11:56:24 +01:00
Keith Edmunds
f64671d126 Improve function logging
Use @log_call decorator

Add 'checked' parameter to menu slots because PyQt6 will pass a
boolean 'checked' parameter even when the menu item can't be checked.

Remove superfluous logging calls.
2025-03-29 21:04:59 +00:00
Keith Edmunds
2bf1bc64e7 WIP: Unify function to move rows within/between playlists 2025-03-29 18:20:38 +00:00
Keith Edmunds
3c7fc20e5a Remove kae.py from git 2025-03-29 18:20:38 +00:00
Keith Edmunds
52d2269ece WIP: Move rows within and between playlists working 2025-03-29 18:20:38 +00:00
Keith Edmunds
3cd764c893 WIP: moving rows within playlist works 2025-03-29 18:20:38 +00:00
Keith Edmunds
65878b0b75 WIP: all tests for move rows within playlist working 2025-03-29 18:20:38 +00:00
Keith Edmunds
4e89d72a8f WIP: move within playlist tests working 2025-03-29 18:20:38 +00:00
Keith Edmunds
92ecb632b5 Report correct line for ApplicationError 2025-03-29 18:20:38 +00:00
Keith Edmunds
6296566c2c WIP: Can play tracks without errors 2025-03-29 18:20:38 +00:00
Keith Edmunds
7e5b170f5e Use @singleton decorator 2025-03-29 18:20:38 +00:00
Keith Edmunds
3db71a08ae WIP remove sessions, use reporistory 2025-03-29 18:20:38 +00:00
Keith Edmunds
7b0e2b2c6c WIP: playlists load, can't play track 2025-03-29 18:20:38 +00:00
Keith Edmunds
4265472d73 Keep track of selected rows in model 2025-03-29 18:20:38 +00:00
Keith Edmunds
c94cadf24f WIP: Use PlaylistRowDTO to isolate SQLAlchemy objects 2025-03-29 18:20:38 +00:00
Keith Edmunds
9720c11ecc Don't track kae.py in git 2025-03-29 18:20:13 +00:00
Keith Edmunds
ca4c490091 Add log_call decorator and issue 287 logging 2025-03-29 18:19:14 +00:00
5 changed files with 192 additions and 101 deletions

View File

@ -104,16 +104,14 @@ class FileImporter:
# variable or an instance variable are effectively the same thing.
workers: dict[str, DoTrackImport] = {}
def __init__(
self, base_model: PlaylistModel, row_number: Optional[int] = None
) -> None:
def __init__(self, base_model: PlaylistModel, row_number: int) -> None:
"""
Initialise the FileImporter singleton instance.
"""
log.debug(f"FileImporter.__init__({base_model=}, {row_number=})")
# Create ModelData
if not row_number:
row_number = base_model.rowCount()
self.model_data = ThreadData(base_model=base_model, row_number=row_number)
# Data structure to track files to import

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
# Standard library imports
from collections import defaultdict
from functools import wraps
import logging
import logging.config
import logging.handlers
@ -120,4 +121,30 @@ def handle_exception(exc_type, exc_value, exc_traceback):
QMessageBox.critical(None, "Application Error", msg)
def truncate_large(obj, limit=5):
"""Helper to truncate large lists or other iterables."""
if isinstance(obj, (list, tuple, set)):
if len(obj) > limit:
return f"{type(obj).__name__}(len={len(obj)}, items={list(obj)[:limit]}...)"
return repr(obj)
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
args_repr = [truncate_large(a) for a in args]
kwargs_repr = [f"{k}={truncate_large(v)}" for k, v in kwargs.items()]
params_repr = ", ".join(args_repr + kwargs_repr)
log.debug(f"call {func.__name__}({params_repr})", stacklevel=2)
try:
result = func(*args, **kwargs)
log.debug(f"return {func.__name__}: {truncate_large(result)}", stacklevel=2)
return result
except Exception as e:
log.debug(f"exception in {func.__name__}: {e}", stacklevel=2)
raise
return wrapper
sys.excepthook = handle_exception

View File

@ -73,7 +73,7 @@ from config import Config
from dialogs import TrackInsertDialog
from file_importer import FileImporter
from helpers import ask_yes_no, file_is_unreadable, get_name
from log import log
from log import log, log_call
from models import db, Playdates, PlaylistRows, Playlists, Queries, Settings, Tracks
from playlistrow import PlaylistRow, TrackSequence
from playlistmodel import PlaylistModel, PlaylistProxyModel
@ -478,6 +478,7 @@ class ManageQueries(ItemlistManager):
self.populate_table(query_list)
@log_call
def delete_item(self, query_id: int) -> None:
"""delete query"""
@ -490,7 +491,6 @@ class ManageQueries(ItemlistManager):
"Delete query",
f"Delete query '{query.name}': " "Are you sure?",
):
log.debug(f"manage_queries: delete {query=}")
self.session.delete(query)
self.session.commit()
@ -583,6 +583,7 @@ class ManageTemplates(ItemlistManager):
self.populate_table(template_list)
@log_call
def delete_item(self, template_id: int) -> None:
"""delete template"""
@ -606,7 +607,6 @@ class ManageTemplates(ItemlistManager):
else:
self.musicmuster.playlist_section.tabPlaylist.removeTab(open_idx)
log.debug(f"manage_templates: delete {template=}")
self.session.delete(template)
self.session.commit()
@ -1235,7 +1235,6 @@ class Window(QMainWindow):
for playlist_id, idx in open_playlist_ids.items():
playlist = session.get(Playlists, playlist_id)
if playlist:
log.debug(f"Set {playlist=} tab to {idx=}")
playlist.tab = idx
# Save window attributes
@ -1453,6 +1452,7 @@ class Window(QMainWindow):
# # # # # # # # # # Playlist management functions # # # # # # # # # #
@log_call
def _create_playlist(
self, session: Session, name: str, template_id: int
) -> Playlists:
@ -1461,10 +1461,9 @@ class Window(QMainWindow):
if template_id > 0, and return the Playlists object.
"""
log.debug(f" _create_playlist({name=}, {template_id=})")
return Playlists(session, name, template_id)
@log_call
def _open_playlist(self, playlist: Playlists, is_template: bool = False) -> int:
"""
With passed playlist:
@ -1475,8 +1474,6 @@ class Window(QMainWindow):
return: tab index
"""
log.debug(f" _open_playlist({playlist=}, {is_template=})")
# Create base model and proxy model
base_model = PlaylistModel(playlist.id, is_template)
proxy_model = PlaylistProxyModel()
@ -1495,6 +1492,7 @@ class Window(QMainWindow):
return idx
@log_call
def create_playlist_from_template(self, session: Session, template_id: int) -> None:
"""
Prompt for new playlist name and create from passed template_id
@ -1516,7 +1514,8 @@ class Window(QMainWindow):
self._open_playlist(playlist)
session.commit()
def delete_playlist(self) -> None:
@log_call
def delete_playlist(self, checked: bool = False) -> None:
"""
Delete current playlist
"""
@ -1535,7 +1534,7 @@ class Window(QMainWindow):
else:
log.error("Failed to retrieve playlist")
def open_existing_playlist(self) -> None:
def open_existing_playlist(self, checked: bool = False) -> None:
"""Open existing playlist"""
with db.Session() as session:
@ -1547,7 +1546,7 @@ class Window(QMainWindow):
self._open_playlist(playlist)
session.commit()
def save_as_template(self) -> None:
def save_as_template(self, checked: bool = False) -> None:
"""Save current playlist as template"""
with db.Session() as session:
@ -1623,7 +1622,7 @@ class Window(QMainWindow):
# # # # # # # # # # Manage templates and queries # # # # # # # # # #
def manage_queries_wrapper(self):
def manage_queries_wrapper(self, checked: bool = False) -> None:
"""
Simply instantiate the manage_queries class
"""
@ -1631,7 +1630,7 @@ class Window(QMainWindow):
with db.Session() as session:
_ = ManageQueries(session, self)
def manage_templates_wrapper(self):
def manage_templates_wrapper(self, checked: bool = False) -> None:
"""
Simply instantiate the manage_queries class
"""
@ -1641,12 +1640,12 @@ class Window(QMainWindow):
# # # # # # # # # # Miscellaneous functions # # # # # # # # # #
def select_duplicate_rows(self) -> None:
def select_duplicate_rows(self, checked: bool = False) -> None:
"""Call playlist to select duplicate rows"""
self.active_tab().select_duplicate_rows()
def about(self) -> None:
def about(self, checked: bool = False) -> None:
"""Get git tag and database name"""
try:
@ -1675,7 +1674,7 @@ class Window(QMainWindow):
self.track_sequence.set_next(None)
self.update_headers()
def clear_selection(self) -> None:
def clear_selection(self, checked: bool = False) -> None:
"""Clear row selection"""
# Unselect any selected rows
@ -1685,7 +1684,7 @@ class Window(QMainWindow):
# Clear the search bar
self.search_playlist_clear()
def close_playlist_tab(self) -> bool:
def close_playlist_tab(self, checked: bool = False) -> bool:
"""
Close active playlist tab, called by menu item
"""
@ -1771,6 +1770,7 @@ class Window(QMainWindow):
self.signals.search_songfacts_signal.connect(self.open_songfacts_browser)
self.signals.search_wikipedia_signal.connect(self.open_wikipedia_browser)
@log_call
def current_row_or_end(self) -> int:
"""
If a row or rows are selected, return the row number of the first
@ -1782,14 +1782,14 @@ class Window(QMainWindow):
return self.current.selected_row_numbers[0]
return self.current.base_model.rowCount()
def debug(self):
def debug(self, checked: bool = False) -> None:
"""Invoke debugger"""
import ipdb # type: ignore
ipdb.set_trace()
def download_played_tracks(self) -> None:
def download_played_tracks(self, checked: bool = False) -> None:
"""Download a CSV of played tracks"""
dlg = DownloadCSV(self)
@ -1820,6 +1820,7 @@ class Window(QMainWindow):
if self.track_sequence.current:
self.track_sequence.current.drop3db(self.footer_section.btnDrop3db.isChecked())
@log_call
def enable_escape(self, enabled: bool) -> None:
"""
Manage signal to enable/disable handling ESC character.
@ -1828,11 +1829,10 @@ class Window(QMainWindow):
so we need to disable it here while editing.
"""
log.debug(f"enable_escape({enabled=})")
if "clear_selection" in self.menu_actions:
self.menu_actions["clear_selection"].setEnabled(enabled)
@log_call
def end_of_track_actions(self) -> None:
"""
@ -1867,7 +1867,7 @@ class Window(QMainWindow):
# if not self.stop_autoplay:
# self.play_next()
def export_playlist_tab(self) -> None:
def export_playlist_tab(self, checked: bool = False) -> None:
"""Export the current playlist to an m3u file"""
playlist_id = self.current.playlist_id
@ -1908,7 +1908,7 @@ class Window(QMainWindow):
"\n"
)
def fade(self) -> None:
def fade(self, checked: bool = False) -> None:
"""Fade currently playing track"""
if self.track_sequence.current:
@ -1944,7 +1944,7 @@ class Window(QMainWindow):
# Reset row heights
self.active_tab().resize_rows()
def import_files_wrapper(self) -> None:
def import_files_wrapper(self, checked: bool = False) -> None:
"""
Pass import files call to file_importer module
"""
@ -1954,7 +1954,7 @@ class Window(QMainWindow):
self.importer = FileImporter(self.current.base_model, self.current_row_or_end())
self.importer.start()
def insert_header(self) -> None:
def insert_header(self, checked: bool = False) -> None:
"""Show dialog box to enter header text and add to playlist"""
# Get header text
@ -1969,7 +1969,7 @@ class Window(QMainWindow):
note=dlg.textValue(),
)
def insert_track(self) -> None:
def insert_track(self, checked: bool = False) -> None:
"""Show dialog box to select and add track from database"""
dlg = TrackInsertDialog(
@ -1978,6 +1978,7 @@ class Window(QMainWindow):
)
dlg.exec()
@log_call
def load_last_playlists(self) -> None:
"""Load the playlists that were open when the last session closed"""
@ -1985,7 +1986,6 @@ class Window(QMainWindow):
with db.Session() as session:
for playlist in Playlists.get_open(session):
if playlist:
log.debug(f"load_last_playlists() loaded {playlist=}")
# Create tab
playlist_ids.append(self._open_playlist(playlist))
@ -2001,7 +2001,7 @@ class Window(QMainWindow):
Playlists.clear_tabs(session, playlist_ids)
session.commit()
def lookup_row_in_songfacts(self) -> None:
def lookup_row_in_songfacts(self, checked: bool = False) -> None:
"""
Display songfacts page for title in highlighted row
"""
@ -2012,7 +2012,7 @@ class Window(QMainWindow):
self.signals.search_songfacts_signal.emit(track_info.title)
def lookup_row_in_wikipedia(self) -> None:
def lookup_row_in_wikipedia(self, checked: bool = False) -> None:
"""
Display Wikipedia page for title in highlighted row or next track
"""
@ -2023,7 +2023,7 @@ class Window(QMainWindow):
self.signals.search_wikipedia_signal.emit(track_info.title)
def mark_rows_for_moving(self) -> None:
def mark_rows_for_moving(self, checked: bool = False) -> None:
"""
Cut rows ready for pasting.
"""
@ -2037,6 +2037,7 @@ class Window(QMainWindow):
f"mark_rows_for_moving(): {self.move_source_rows=} {self.move_source_model=}"
)
@log_call
def move_playlist_rows(self, row_numbers: list[int]) -> None:
"""
Move passed playlist rows to another playlist
@ -2074,7 +2075,7 @@ class Window(QMainWindow):
# Reset track_sequences
self.track_sequence.update()
def move_selected(self) -> None:
def move_selected(self, checked: bool = False) -> None:
"""
Move selected rows to another playlist
"""
@ -2085,7 +2086,7 @@ class Window(QMainWindow):
self.move_playlist_rows(selected_rows)
def move_unplayed(self) -> None:
def move_unplayed(self, checked: bool = False) -> None:
"""
Move unplayed rows to another playlist
"""
@ -2117,7 +2118,8 @@ class Window(QMainWindow):
webbrowser.get("browser").open_new_tab(url)
def paste_rows(self, dummy_for_profiling: int | None = None) -> None:
@log_call
def paste_rows(self, checked: bool = False) -> None:
"""
Paste earlier cut rows.
"""
@ -2150,7 +2152,8 @@ class Window(QMainWindow):
if set_next_row:
to_playlist_model.set_next_row(set_next_row)
def play_next(self, position: Optional[float] = None) -> None:
@log_call
def play_next(self, position: Optional[float] = None, checked: bool = False) -> None:
"""
Play next track, optionally from passed position.
@ -2345,7 +2348,7 @@ class Window(QMainWindow):
if ok:
log.debug("quicklog: " + dlg.textValue())
def rename_playlist(self) -> None:
def rename_playlist(self, checked: bool = False) -> None:
"""
Rename current playlist
"""
@ -2401,7 +2404,7 @@ class Window(QMainWindow):
return False
def resume(self) -> None:
def resume(self, checked: bool = False) -> None:
"""
Resume playing last track. We may be playing the next track
or none; take care of both eventualities.
@ -2442,7 +2445,7 @@ class Window(QMainWindow):
)
self.track_sequence.current.start_time -= dt.timedelta(milliseconds=elapsed_ms)
def search_playlist(self) -> None:
def search_playlist(self, checked: bool = False) -> None:
"""Show text box to search playlist"""
# Disable play controls so that 'return' in search box doesn't
@ -2500,7 +2503,8 @@ class Window(QMainWindow):
height = Settings.get_setting(session, "mainwindow_height").f_int or 100
self.setGeometry(x, y, width, height)
def set_selected_track_next(self) -> None:
@log_call
def set_selected_track_next(self, checked: bool = False) -> None:
"""
Set currently-selected row on visible playlist tab as next track
"""
@ -2513,6 +2517,7 @@ class Window(QMainWindow):
# else:
# log.error("No active tab")
@log_call
def set_tab_colour(self, widget: PlaylistTab, colour: QColor) -> None:
"""
Find the tab containing the widget and set the text colour
@ -2574,7 +2579,8 @@ class Window(QMainWindow):
self.active_tab().scroll_to_top(playlist_track.row_number)
def stop(self) -> None:
@log_call
def stop(self, checked: bool = False) -> None:
"""Stop playing immediately"""
self.stop_autoplay = True
@ -2708,6 +2714,7 @@ class Window(QMainWindow):
self.catch_return_key = False
self.show_status_message("Play controls: Enabled", 0)
# Re-enable 10ms timer (see above)
log.debug(f"issue287: {self.timer10.isActive()=}")
if not self.timer10.isActive():
self.timer10.start(10)
log.debug("issue223: update_clocks: 10ms timer enabled")

View File

@ -46,7 +46,7 @@ from helpers import (
remove_substring_case_insensitive,
set_track_metadata,
)
from log import log
from log import log, log_call
from models import db, NoteColours, Playdates, PlaylistRows, Tracks
from playlistrow import PlaylistRow, TrackSequence
import repository
@ -79,8 +79,6 @@ class PlaylistModel(QAbstractTableModel):
) -> None:
super().__init__()
log.debug("PlaylistModel.__init__()")
self.playlist_id = playlist_id
self.is_template = is_template
self.track_sequence = TrackSequence()
@ -145,6 +143,7 @@ class PlaylistModel(QAbstractTableModel):
return header_row
@log_call
def add_track_to_header(
self, playlist_id: int, track_id: int, note: str = ""
) -> None:
@ -242,6 +241,7 @@ class PlaylistModel(QAbstractTableModel):
return QBrush()
@log_call
def begin_reset_model(self, playlist_id: int) -> None:
"""
Reset model if playlist_id is ours
@ -256,6 +256,7 @@ class PlaylistModel(QAbstractTableModel):
return len(Col)
@log_call
def current_track_started(self) -> None:
"""
Notification from musicmuster that the current track has just
@ -271,8 +272,6 @@ class PlaylistModel(QAbstractTableModel):
- update track times
"""
log.debug(f"{self}: current_track_started()")
if not self.track_sequence.current:
return
@ -380,6 +379,7 @@ class PlaylistModel(QAbstractTableModel):
return QVariant()
@log_call
def delete_rows(self, row_numbers: list[int]) -> None:
"""
Delete passed rows from model
@ -462,15 +462,13 @@ class PlaylistModel(QAbstractTableModel):
return ""
@log_call
def end_reset_model(self, playlist_id: int) -> None:
"""
End model reset if this is our playlist
"""
log.debug(f"{self}: end_reset_model({playlist_id=})")
if playlist_id != self.playlist_id:
log.debug(f"{self}: end_reset_model: not us ({self.playlist_id=})")
return
with db.Session() as session:
self.refresh_data(session)
@ -550,14 +548,13 @@ class PlaylistModel(QAbstractTableModel):
return boldfont
@log_call
def get_duplicate_rows(self) -> list[int]:
"""
Return a list of duplicate rows. If track appears in rows 2, 3 and 4, return [3, 4]
(ie, ignore the first, not-yet-duplicate, track).
"""
log.debug(f"{self}: get_duplicate_rows() called")
found = []
result = []
@ -570,9 +567,9 @@ class PlaylistModel(QAbstractTableModel):
else:
found.append(track_id)
log.debug(f"{self}: get_duplicate_rows() returned: {result=}")
return result
@log_call
def _get_new_row_number(self, proposed_row_number: Optional[int]) -> int:
"""
Sanitises proposed new row number.
@ -581,8 +578,6 @@ class PlaylistModel(QAbstractTableModel):
If not given, return row number to add to end of model.
"""
log.debug(f"{self}: _get_new_row_number({proposed_row_number=})")
if proposed_row_number is None or proposed_row_number > len(self.playlist_rows):
# We are adding to the end of the list
new_row_number = len(self.playlist_rows)
@ -592,7 +587,6 @@ class PlaylistModel(QAbstractTableModel):
else:
new_row_number = proposed_row_number
log.debug(f"{self}: get_new_row_number() return: {new_row_number=}")
return new_row_number
def get_row_info(self, row_number: int) -> PlaylistRow:
@ -602,6 +596,7 @@ class PlaylistModel(QAbstractTableModel):
return self.playlist_rows[row_number]
@log_call
def get_row_track_id(self, row_number: int) -> Optional[int]:
"""
Return id of track associated with row or None if no track associated
@ -718,6 +713,7 @@ class PlaylistModel(QAbstractTableModel):
]
self.invalidate_row(row_number, roles)
@log_call
def insert_row(
self,
proposed_row_number: Optional[int],
@ -728,8 +724,6 @@ class PlaylistModel(QAbstractTableModel):
Insert a row.
"""
log.debug(f"{self}: insert_row({proposed_row_number=}, {track_id=}, {note=})")
new_row_number = self._get_new_row_number(proposed_row_number)
super().beginInsertRows(QModelIndex(), new_row_number, new_row_number)
@ -762,13 +756,12 @@ class PlaylistModel(QAbstractTableModel):
list(range(new_row_number, len(self.playlist_rows))), roles_to_invalidate
)
@log_call
def invalidate_row(self, modified_row: int, roles: list[Qt.ItemDataRole]) -> None:
"""
Signal to view to refresh invalidated row
"""
log.debug(f"issue285: {self}: invalidate_row({modified_row=})")
self.dataChanged.emit(
self.index(modified_row, 0),
self.index(modified_row, self.columnCount() - 1),
@ -782,8 +775,6 @@ class PlaylistModel(QAbstractTableModel):
Signal to view to refresh invlidated rows
"""
log.debug(f"issue285: {self}: invalidate_rows({modified_rows=})")
for modified_row in modified_rows:
# only invalidate required roles
self.invalidate_row(modified_row, roles)
@ -797,6 +788,7 @@ class PlaylistModel(QAbstractTableModel):
return self.playlist_rows[row_number].path == ""
return False
@log_call
def is_played_row(self, row_number: int) -> bool:
"""
Return True if row is an unplayed track row, else False
@ -832,6 +824,7 @@ class PlaylistModel(QAbstractTableModel):
]
self.invalidate_rows(row_numbers, roles)
@log_call
def move_rows(self, from_rows: list[int], to_row_number: int) -> bool:
"""
Move the playlist rows in from_rows to to_row. Return True if successful
@ -853,11 +846,46 @@ class PlaylistModel(QAbstractTableModel):
)
from_rows.remove(self.track_sequence.current.row_number)
# Row moves must be wrapped in beginMoveRows .. endMoveRows and
# the row range must be contiguous. Process the highest rows
# first so the lower row numbers are unchanged
from_rows = sorted(set(from_rows))
if (min(from_rows) < 0 or max(from_rows) >= self.rowCount()
or to_row_number < 0 or to_row_number > self.rowCount()):
log.debug("move_rows: invalid indexes")
return False
row_groups = self._reversed_contiguous_row_groups([a.row_number for a in from_rows])
if to_row_number in from_rows:
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
self.beginResetModel()
# Update database
repository.move_rows(from_rows, self.playlist_id, to_row_number)
# Notify model changed
self.endResetModel()
# Handle the moves in row_group chunks
for row_group in row_groups:
@ -909,6 +937,7 @@ class PlaylistModel(QAbstractTableModel):
super().endInsertRows()
@log_call
def move_rows_between_playlists(
self,
from_rows: list[PlaylistRow],
@ -919,11 +948,6 @@ class PlaylistModel(QAbstractTableModel):
Move the playlist rows given to to_row and below of to_playlist.
"""
log.debug(
f"{self}: move_rows_between_playlists({from_rows=}, "
f"{to_row_number=}, {to_playlist_id=}"
)
# Don't move current row
if self.track_sequence.current:
current_row = self.track_sequence.current.row_number
@ -962,6 +986,7 @@ class PlaylistModel(QAbstractTableModel):
self.track_sequence.update()
self.update_track_times()
@log_call
def move_track_add_note(
self, new_row_number: int, existing_plr: PlaylistRow, note: str
) -> None:
@ -969,10 +994,6 @@ class PlaylistModel(QAbstractTableModel):
Move existing_rat track to new_row_number and append note to any existing note
"""
log.debug(
f"{self}: move_track_add_note({new_row_number=}, {existing_plr=}, {note=}"
)
if note:
playlist_row = self.playlist_rows[existing_plr.row_number]
if playlist_row.note:
@ -986,14 +1007,30 @@ class PlaylistModel(QAbstractTableModel):
self.move_rows([existing_plr.row_number], new_row_number)
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
def obs_scene_change(self, row_number: int) -> None:
"""
Check this row and any preceding headers for OBS scene change command
and execute any found
"""
log.debug(f"{self}: obs_scene_change({row_number=})")
# Check any headers before this row
idx = row_number - 1
while self.is_header_row(idx):
@ -1022,6 +1059,7 @@ class PlaylistModel(QAbstractTableModel):
log.warning(f"{self}: OBS connection refused")
return
@log_call
def previous_track_ended(self) -> None:
"""
Notification from musicmuster that the previous track has ended.
@ -1031,8 +1069,6 @@ class PlaylistModel(QAbstractTableModel):
- update display
"""
log.debug(f"{self}: previous_track_ended()")
# Sanity check
if not self.track_sequence.previous:
log.error(
@ -1088,13 +1124,12 @@ class PlaylistModel(QAbstractTableModel):
self.playlist_rows[row_number] = PlaylistRow(refreshed_row)
@log_call
def remove_track(self, row_number: int) -> None:
"""
Remove track from row, retaining row as a header row
"""
log.debug(f"{self}: remove_track({row_number=})")
self.playlist_rows[row_number].track_id = None
# only invalidate required roles
@ -1124,6 +1159,31 @@ class PlaylistModel(QAbstractTableModel):
self.signals.resize_rows_signal.emit(self.playlist_id)
session.commit()
@log_call
def reset_track_sequence_row_numbers(self) -> None:
"""
Signal handler for when row ordering has changed.
Example: row 4 is marked as next. Row 2 is deleted. The PlaylistRows table will
be correctly updated with change of row number, but track_sequence.next will still
contain row_number==4. This function fixes up the track_sequence row numbers by
looking up the playlistrow_id and retrieving the row number from the database.
"""
# Check the track_sequence.next, current and previous plrs and
# 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()
def remove_comments(self, row_numbers: list[int]) -> None:
"""
Remove comments from passed rows
@ -1162,7 +1222,10 @@ class PlaylistModel(QAbstractTableModel):
]
self.invalidate_rows(row_numbers, roles)
def _reversed_contiguous_row_groups(self, row_numbers: list[int]) -> list[list[int]]:
@log_call
def _reversed_contiguous_row_groups(
self, row_numbers: list[int]
) -> list[list[int]]:
"""
Take the list of row numbers and split into groups of contiguous rows. Return as a list
of lists with the highest row numbers first.
@ -1172,8 +1235,6 @@ class PlaylistModel(QAbstractTableModel):
return: [[20, 21], [17], [13], [9, 10], [7], [2, 3, 4, 5]]
"""
log.debug(f"{self}: _reversed_contiguous_row_groups({row_numbers=} called")
result: list[list[int]] = []
temp: list[int] = []
last_value = row_numbers[0] - 1
@ -1189,12 +1250,11 @@ class PlaylistModel(QAbstractTableModel):
result.append(temp)
result.reverse()
log.debug(f"{self}: _reversed_contiguous_row_groups() returned: {result=}")
return result
def remove_section_timer_markers(self, header_text: str) -> str:
"""
Remove characters used to mark section timeings from
Remove characters used to mark section timings from
passed header text.
Remove text using to signal header colours if colour entry
@ -1327,6 +1387,7 @@ class PlaylistModel(QAbstractTableModel):
return True
@log_call
def set_selected_rows(self, playlist_id: int, selected_row_numbers: list[int]) -> None:
"""
Handle signal_playlist_selected_rows to keep track of which rows
@ -1546,6 +1607,7 @@ class PlaylistModel(QAbstractTableModel):
]
)
@log_call
def update_or_insert(self, track_id: int, row_number: int) -> None:
"""
If the passed track_id exists in this playlist, update the
@ -1569,13 +1631,12 @@ class PlaylistModel(QAbstractTableModel):
else:
self.insert_row(proposed_row_number=row_number, track_id=track_id)
@log_call
def update_track_times(self) -> None:
"""
Update track start/end times in self.playlist_rows
"""
log.debug(f"issue285: {self}: update_track_times()")
next_start_time: Optional[dt.datetime] = None
update_rows: list[int] = []
row_count = len(self.playlist_rows)

View File

@ -44,7 +44,7 @@ from helpers import (
show_OK,
show_warning,
)
from log import log
from log import log, log_call
from models import db, Settings
from playlistrow import TrackSequence
from playlistmodel import PlaylistModel, PlaylistProxyModel
@ -359,7 +359,8 @@ class PlaylistTab(QTableView):
# Deselect edited line
self.clear_selection()
def dropEvent(self, event: Optional[QDropEvent], dummy: int | None = None) -> None:
@log_call
def dropEvent(self, event: Optional[QDropEvent]) -> None:
"""
Move dropped rows
"""
@ -395,9 +396,6 @@ class PlaylistTab(QTableView):
destination_index = to_index
to_model_row = self.model().mapToSource(destination_index).row()
log.debug(
f"PlaylistTab.dropEvent(): {from_rows=}, {destination_index=}, {to_model_row=}"
)
# Sanity check
base_model_row_count = self.get_base_model().rowCount()
@ -680,8 +678,6 @@ class PlaylistTab(QTableView):
Called when column width changes. Save new width to database.
"""
log.debug(f"_column_resize({column_number=}, {_old=}, {_new=}")
header = self.horizontalHeader()
if not header:
return
@ -726,6 +722,7 @@ class PlaylistTab(QTableView):
cb.clear(mode=cb.Mode.Clipboard)
cb.setText(track_path, mode=cb.Mode.Clipboard)
@log_call
def current_track_started(self) -> None:
"""
Called when track starts playing
@ -813,6 +810,7 @@ class PlaylistTab(QTableView):
else:
return TrackInfo(track_id, selected_row)
@log_call
def get_selected_row(self) -> Optional[int]:
"""
Return selected row number. If no rows or multiple rows selected, return None
@ -824,6 +822,7 @@ class PlaylistTab(QTableView):
else:
return None
@log_call
def get_selected_rows(self) -> list[int]:
"""Return a list of model-selected row numbers sorted by row"""
@ -836,6 +835,7 @@ class PlaylistTab(QTableView):
return sorted(list(set([self.model().mapToSource(a).row() for a in selected_indexes])))
@log_call
def get_top_visible_row(self) -> int:
"""
Get the viewport of the table view
@ -958,8 +958,6 @@ class PlaylistTab(QTableView):
If playlist_id is us, resize rows
"""
log.debug(f"resize_rows({playlist_id=}) {self.playlist_id=}")
if playlist_id and playlist_id != self.playlist_id:
return
@ -1006,6 +1004,7 @@ class PlaylistTab(QTableView):
# Reset selection mode
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
@log_call
def source_model_selected_row_number(self) -> Optional[int]:
"""
Return the model row number corresponding to the selected row or None
@ -1016,6 +1015,7 @@ class PlaylistTab(QTableView):
return None
return self.model().mapToSource(selected_index).row()
@log_call
def selected_model_row_numbers(self) -> list[int]:
"""
Return a list of model row numbers corresponding to the selected rows or
@ -1058,8 +1058,6 @@ class PlaylistTab(QTableView):
def _set_column_widths(self) -> None:
"""Column widths from settings"""
log.debug("_set_column_widths()")
header = self.horizontalHeader()
if not header:
return