WIP V3: check track already present in playlist when adding

This commit is contained in:
Keith Edmunds 2023-11-27 20:55:24 +00:00
parent 3cab9f737c
commit b1442b2c7d
7 changed files with 103 additions and 60 deletions

View File

@ -79,8 +79,6 @@ class MusicMusterSignals(QObject):
https://refactoring.guru/design-patterns/singleton/python/example#example-0 https://refactoring.guru/design-patterns/singleton/python/example#example-0
""" """
add_track_to_header_signal = pyqtSignal(int, int, int)
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
begin_reset_model_signal = pyqtSignal(int) begin_reset_model_signal = pyqtSignal(int)
enable_escape_signal = pyqtSignal(bool) enable_escape_signal = pyqtSignal(bool)
end_reset_model_signal = pyqtSignal(int) end_reset_model_signal = pyqtSignal(int)

View File

@ -6,10 +6,12 @@ from PyQt6.QtWidgets import QDialog, QListWidgetItem
from classes import MusicMusterSignals from classes import MusicMusterSignals
from dbconfig import scoped_session from dbconfig import scoped_session
from helpers import ( from helpers import (
ask_yes_no,
get_relative_date, get_relative_date,
ms_to_mmss, ms_to_mmss,
) )
from models import Settings, Tracks from models import Settings, Tracks
from playlistmodel import PlaylistModel
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
@ -20,7 +22,7 @@ class TrackSelectDialog(QDialog):
self, self,
session: scoped_session, session: scoped_session,
new_row_number: int, new_row_number: int,
playlist_id: int, model: PlaylistModel,
add_to_header: Optional[bool] = False, add_to_header: Optional[bool] = False,
*args, *args,
**kwargs, **kwargs,
@ -32,7 +34,7 @@ class TrackSelectDialog(QDialog):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.session = session self.session = session
self.new_row_number = new_row_number self.new_row_number = new_row_number
self.playlist_id = playlist_id self.model = model
self.add_to_header = add_to_header self.add_to_header = add_to_header
self.ui = Ui_Dialog() self.ui = Ui_Dialog()
self.ui.setupUi(self) self.ui.setupUi(self)
@ -73,14 +75,30 @@ class TrackSelectDialog(QDialog):
track_id = None track_id = None
if track: if track:
track_id = track.id track_id = track.id
if self.add_to_header:
self.signals.add_track_to_header_signal.emit(
self.playlist_id, self.new_row_number, track_id
)
else: else:
self.signals.add_track_to_playlist_signal.emit( return
self.playlist_id, self.new_row_number, track_id, note # Check whether track is already in playlist
) move_existing = False
existing_prd = self.model.is_track_in_playlist(track_id)
if existing_prd is not None:
if ask_yes_no(
"Duplicate row",
"Track already in playlist. " "Move to new location?",
default_yes=True,
):
move_existing = True
if self.add_to_header and existing_prd: # and existing_prd for mypy's benefit
if move_existing:
self.model.move_track_to_header(self.new_row_number, existing_prd, note)
else:
self.model.add_track_to_header(self.new_row_number, track_id)
# Close dialog - we can only add one track to a header
self.accept()
else:
if move_existing and existing_prd: # and existing_prd for mypy's benefit
self.model.move_track_add_note(self.new_row_number, existing_prd, note)
else:
self.model.insert_row(self.new_row_number, track_id, note)
def add_selected_and_close(self) -> None: def add_selected_and_close(self) -> None:
"""Handle Add and Close button""" """Handle Add and Close button"""

View File

@ -148,11 +148,11 @@ class ImportTrack(QObject):
import_finished = pyqtSignal() import_finished = pyqtSignal()
def __init__( def __init__(
self, filenames: List[str], playlist_id: int, row_number: Optional[int] self, filenames: List[str], model: PlaylistModel, row_number: Optional[int]
) -> None: ) -> None:
super().__init__() super().__init__()
self.filenames = filenames self.filenames = filenames
self.playlist_id = playlist_id self.model = model
self.next_row_number = row_number self.next_row_number = row_number
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
@ -179,9 +179,7 @@ class ImportTrack(QObject):
# previous additions in this loop. So, commit now to # previous additions in this loop. So, commit now to
# lock in what we've just done. # lock in what we've just done.
session.commit() session.commit()
self.signals.add_track_to_playlist_signal.emit( self.model.insert_row(self.next_row_number, track.id, "")
self.playlist_id, self.next_row_number, track.id, ""
)
self.next_row_number += 1 self.next_row_number += 1
self.signals.status_message_signal.emit( self.signals.status_message_signal.emit(
f"{len(self.filenames)} tracks imported", 10000 f"{len(self.filenames)} tracks imported", 10000
@ -532,8 +530,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionSelect_duplicate_rows.triggered.connect( self.actionSelect_duplicate_rows.triggered.connect(
lambda: self.active_tab().select_duplicate_rows() lambda: self.active_tab().select_duplicate_rows()
) )
self.actionSelect_next_track.triggered.connect(self.select_next_row)
self.actionSelect_previous_track.triggered.connect(self.select_previous_row)
self.actionMoveUnplayed.triggered.connect(self.move_unplayed) self.actionMoveUnplayed.triggered.connect(self.move_unplayed)
self.actionSetNext.triggered.connect(self.set_selected_track_next) self.actionSetNext.triggered.connect(self.set_selected_track_next)
self.actionSkipToNext.triggered.connect(self.play_next) self.actionSkipToNext.triggered.connect(self.play_next)
@ -832,8 +828,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.import_thread = QThread() self.import_thread = QThread()
self.worker = ImportTrack( self.worker = ImportTrack(
new_tracks, new_tracks,
self.active_tab().playlist_id, self.active_model(),
self.active_tab().get_selected_row_number(), self.active_tab().selected_model_row_number(),
) )
self.worker.moveToThread(self.import_thread) self.worker.moveToThread(self.import_thread)
self.import_thread.started.connect(self.worker.run) self.import_thread.started.connect(self.worker.run)
@ -871,8 +867,8 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session: with Session() as session:
dlg = TrackSelectDialog( dlg = TrackSelectDialog(
session=session, session=session,
new_row_number=self.active_tab().get_selected_row_number(), new_row_number=self.active_tab().selected_model_row_number(),
playlist_id=self.active_tab().playlist_id, model=self.active_model()
) )
dlg.exec() dlg.exec()
@ -893,7 +889,7 @@ class Window(QMainWindow, Ui_MainWindow):
Display songfacts page for title in highlighted row Display songfacts page for title in highlighted row
""" """
row_number = self.active_tab().get_selected_row_number() row_number = self.active_tab().selected_model_row_number()
if row_number is None: if row_number is None:
return return
@ -908,7 +904,7 @@ class Window(QMainWindow, Ui_MainWindow):
Display Wikipedia page for title in highlighted row Display Wikipedia page for title in highlighted row
""" """
row_number = self.active_tab().get_selected_row_number() row_number = self.active_tab().selected_model_row_number()
if row_number is None: if row_number is None:
return return

View File

@ -124,8 +124,6 @@ class PlaylistModel(QAbstractTableModel):
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
self.played_tracks_hidden = False self.played_tracks_hidden = False
self.signals.add_track_to_header_signal.connect(self.add_track_to_header)
self.signals.add_track_to_playlist_signal.connect(self.add_track)
self.signals.begin_reset_model_signal.connect(self.begin_reset_model) self.signals.begin_reset_model_signal.connect(self.begin_reset_model)
self.signals.end_reset_model_signal.connect(self.end_reset_model) self.signals.end_reset_model_signal.connect(self.end_reset_model)
self.signals.row_order_changed_signal.connect(self.row_order_changed) self.signals.row_order_changed_signal.connect(self.row_order_changed)
@ -142,45 +140,22 @@ class PlaylistModel(QAbstractTableModel):
f"<PlaylistModel: playlist_id={self.playlist_id}, {self.rowCount()} rows>" f"<PlaylistModel: playlist_id={self.playlist_id}, {self.rowCount()} rows>"
) )
def add_track(
self,
playlist_id: int,
new_row_number: int,
track_id: Optional[int],
note: Optional[str],
) -> None:
"""
Add track if it's for our playlist
"""
# Ignore if it's not for us
if playlist_id != self.playlist_id:
return
self.insert_row(
proposed_row_number=new_row_number, track_id=track_id, note=note
)
def add_track_to_header( def add_track_to_header(
self, self,
playlist_id: int,
row_number: int, row_number: int,
track_id: int, track_id: int,
note: Optional[str] = None
) -> None: ) -> None:
""" """
Add track to existing header row if it's for our playlist Add track to existing header row
""" """
# Ignore if it's not for us
if playlist_id != self.playlist_id:
return
# Get existing row # Get existing row
try: try:
prd = self.playlist_rows[row_number] prd = self.playlist_rows[row_number]
except KeyError: except KeyError:
log.error( log.error(
f"KeyError in PlaylistModel:add_track_to_header ({playlist_id=}, " f"KeyError in PlaylistModel:add_track_to_header "
f"{row_number=}, {track_id=}, {len(self.playlist_rows)=}" f"{row_number=}, {track_id=}, {len(self.playlist_rows)=}"
) )
return return
@ -195,6 +170,9 @@ class PlaylistModel(QAbstractTableModel):
if plr: if plr:
# Add track to PlaylistRows # Add track to PlaylistRows
plr.track_id = track_id plr.track_id = track_id
# Add any further note
if note:
plr.note += "\n" + note
# Reset header row spanning # Reset header row spanning
self.signals.span_cells_signal.emit( self.signals.span_cells_signal.emit(
row_number, HEADER_NOTES_COLUMN, 1, 1 row_number, HEADER_NOTES_COLUMN, 1, 1
@ -723,7 +701,8 @@ class PlaylistModel(QAbstractTableModel):
""" """
self.dataChanged.emit( self.dataChanged.emit(
self.index(modified_row, 0), self.index(modified_row, self.columnCount() - 1) self.index(modified_row, 0),
self.index(modified_row, self.columnCount() - 1),
) )
def invalidate_rows(self, modified_rows: List[int]) -> None: def invalidate_rows(self, modified_rows: List[int]) -> None:
@ -734,6 +713,18 @@ class PlaylistModel(QAbstractTableModel):
for modified_row in modified_rows: for modified_row in modified_rows:
self.invalidate_row(modified_row) self.invalidate_row(modified_row)
def is_track_in_playlist(self, track_id: int) -> Optional[PlaylistRowData]:
"""
If this track_id is in the playlist, return the PlaylistRowData object
else return None
"""
for row_number in range(len(self.playlist_rows)):
if self.playlist_rows[row_number].track_id == track_id:
return self.playlist_rows[row_number]
return None
def mark_unplayed(self, row_numbers: List[int]) -> None: def mark_unplayed(self, row_numbers: List[int]) -> None:
""" """
Mark row as unplayed Mark row as unplayed
@ -869,6 +860,39 @@ class PlaylistModel(QAbstractTableModel):
self.signals.end_reset_model_signal.emit(to_playlist_id) self.signals.end_reset_model_signal.emit(to_playlist_id)
self.update_track_times() self.update_track_times()
def move_track_add_note(
self, new_row_number: int, existing_prd: PlaylistRowData, note: str
) -> None:
"""
Move existing_prd track to new_row_number and append note to any existing note
"""
if note:
with Session() as session:
plr = session.get(PlaylistRows, existing_prd.plrid)
if plr:
if plr.note:
plr.note += "\n" + note
else:
plr.note = note
# Carry out the move outside of the session context to ensure
# database updated with any note change
self.move_rows([existing_prd.plr_rownum], new_row_number)
def move_track_to_header(
self, header_row_number: int, existing_prd: PlaylistRowData, note: Optional[str]
) -> None:
"""
Add the existing_prd track details to the existing header at header_row_number
"""
if existing_prd.track_id:
if note and existing_prd.note:
note += "\n" + existing_prd.note
self.add_track_to_header(header_row_number, existing_prd.track_id, note)
self.delete_rows([existing_prd.plr_rownum])
def open_in_audacity(self, row_number: int) -> None: def open_in_audacity(self, row_number: int) -> None:
""" """
Open track at passed row number in Audacity Open track at passed row number in Audacity
@ -1338,6 +1362,9 @@ class PlaylistProxyModel(QSortFilterProxyModel):
def is_played_row(self, row_number: int) -> bool: def is_played_row(self, row_number: int) -> bool:
return self.playlist_model.is_played_row(row_number) return self.playlist_model.is_played_row(row_number)
def is_track_in_playlist(self, track_id: int) -> Optional[PlaylistRowData]:
return self.playlist_model.is_track_in_playlist(track_id)
def mark_unplayed(self, row_numbers: List[int]) -> None: def mark_unplayed(self, row_numbers: List[int]) -> None:
return self.playlist_model.mark_unplayed(row_numbers) return self.playlist_model.mark_unplayed(row_numbers)
@ -1351,6 +1378,16 @@ class PlaylistProxyModel(QSortFilterProxyModel):
from_rows, to_row_number, to_playlist_id from_rows, to_row_number, to_playlist_id
) )
def move_track_add_note(
self, new_row_number: int, existing_prd: PlaylistRowData, note: str
) -> None:
return self.playlist_model.move_track_add_note(new_row_number, existing_prd, note)
def move_track_to_header(
self, header_row_number: int, existing_prd: PlaylistRowData, note: Optional[str]
) -> None:
return self.playlist_model.move_track_to_header(header_row_number, existing_prd)
def open_in_audacity(self, row_number: int) -> None: def open_in_audacity(self, row_number: int) -> None:
return self.playlist_model.open_in_audacity(row_number) return self.playlist_model.open_in_audacity(row_number)

View File

@ -562,7 +562,7 @@ class PlaylistTab(QTableView):
dlg = TrackSelectDialog( dlg = TrackSelectDialog(
session=session, session=session,
new_row_number=model_row_number, new_row_number=model_row_number,
playlist_id=self.playlist_id, model=self.playlist_model,
add_to_header=True, add_to_header=True,
) )
dlg.exec() dlg.exec()

View File

@ -786,9 +786,6 @@ padding-left: 8px;</string>
</property> </property>
<addaction name="actionSearch"/> <addaction name="actionSearch"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionSelect_next_track"/>
<addaction name="actionSelect_previous_track"/>
<addaction name="separator"/>
<addaction name="actionSearch_title_in_Wikipedia"/> <addaction name="actionSearch_title_in_Wikipedia"/>
<addaction name="actionSearch_title_in_Songfacts"/> <addaction name="actionSearch_title_in_Songfacts"/>
</widget> </widget>

View File

@ -493,9 +493,6 @@ class Ui_MainWindow(object):
self.menuPlaylist.addAction(self.actionPaste) self.menuPlaylist.addAction(self.actionPaste)
self.menuSearc_h.addAction(self.actionSearch) self.menuSearc_h.addAction(self.actionSearch)
self.menuSearc_h.addSeparator() self.menuSearc_h.addSeparator()
self.menuSearc_h.addAction(self.actionSelect_next_track)
self.menuSearc_h.addAction(self.actionSelect_previous_track)
self.menuSearc_h.addSeparator()
self.menuSearc_h.addAction(self.actionSearch_title_in_Wikipedia) self.menuSearc_h.addAction(self.actionSearch_title_in_Wikipedia)
self.menuSearc_h.addAction(self.actionSearch_title_in_Songfacts) self.menuSearc_h.addAction(self.actionSearch_title_in_Songfacts)
self.menuHelp.addAction(self.action_About) self.menuHelp.addAction(self.action_About)