diff --git a/app/classes.py b/app/classes.py index 51249f2..25c78e6 100644 --- a/app/classes.py +++ b/app/classes.py @@ -79,8 +79,6 @@ class MusicMusterSignals(QObject): 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) enable_escape_signal = pyqtSignal(bool) end_reset_model_signal = pyqtSignal(int) diff --git a/app/dialogs.py b/app/dialogs.py index a5a80e9..861490f 100644 --- a/app/dialogs.py +++ b/app/dialogs.py @@ -6,10 +6,12 @@ from PyQt6.QtWidgets import QDialog, QListWidgetItem from classes import MusicMusterSignals from dbconfig import scoped_session from helpers import ( + ask_yes_no, get_relative_date, ms_to_mmss, ) from models import Settings, Tracks +from playlistmodel import PlaylistModel from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore @@ -20,7 +22,7 @@ class TrackSelectDialog(QDialog): self, session: scoped_session, new_row_number: int, - playlist_id: int, + model: PlaylistModel, add_to_header: Optional[bool] = False, *args, **kwargs, @@ -32,7 +34,7 @@ class TrackSelectDialog(QDialog): super().__init__(*args, **kwargs) self.session = session self.new_row_number = new_row_number - self.playlist_id = playlist_id + self.model = model self.add_to_header = add_to_header self.ui = Ui_Dialog() self.ui.setupUi(self) @@ -73,14 +75,30 @@ class TrackSelectDialog(QDialog): track_id = None if track: 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: - self.signals.add_track_to_playlist_signal.emit( - self.playlist_id, self.new_row_number, track_id, note - ) + return + # 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: """Handle Add and Close button""" diff --git a/app/musicmuster.py b/app/musicmuster.py index cc88d2a..eac669e 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -148,11 +148,11 @@ class ImportTrack(QObject): import_finished = pyqtSignal() def __init__( - self, filenames: List[str], playlist_id: int, row_number: Optional[int] + self, filenames: List[str], model: PlaylistModel, row_number: Optional[int] ) -> None: super().__init__() self.filenames = filenames - self.playlist_id = playlist_id + self.model = model self.next_row_number = row_number self.signals = MusicMusterSignals() @@ -179,9 +179,7 @@ class ImportTrack(QObject): # previous additions in this loop. So, commit now to # lock in what we've just done. session.commit() - self.signals.add_track_to_playlist_signal.emit( - self.playlist_id, self.next_row_number, track.id, "" - ) + self.model.insert_row(self.next_row_number, track.id, "") self.next_row_number += 1 self.signals.status_message_signal.emit( f"{len(self.filenames)} tracks imported", 10000 @@ -532,8 +530,6 @@ class Window(QMainWindow, Ui_MainWindow): self.actionSelect_duplicate_rows.triggered.connect( 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.actionSetNext.triggered.connect(self.set_selected_track_next) self.actionSkipToNext.triggered.connect(self.play_next) @@ -832,8 +828,8 @@ class Window(QMainWindow, Ui_MainWindow): self.import_thread = QThread() self.worker = ImportTrack( new_tracks, - self.active_tab().playlist_id, - self.active_tab().get_selected_row_number(), + self.active_model(), + self.active_tab().selected_model_row_number(), ) self.worker.moveToThread(self.import_thread) self.import_thread.started.connect(self.worker.run) @@ -871,8 +867,8 @@ class Window(QMainWindow, Ui_MainWindow): with Session() as session: dlg = TrackSelectDialog( session=session, - new_row_number=self.active_tab().get_selected_row_number(), - playlist_id=self.active_tab().playlist_id, + new_row_number=self.active_tab().selected_model_row_number(), + model=self.active_model() ) dlg.exec() @@ -893,7 +889,7 @@ class Window(QMainWindow, Ui_MainWindow): 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: return @@ -908,7 +904,7 @@ class Window(QMainWindow, Ui_MainWindow): 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: return diff --git a/app/playlistmodel.py b/app/playlistmodel.py index 8cc8622..afa3c35 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -124,8 +124,6 @@ class PlaylistModel(QAbstractTableModel): self.signals = MusicMusterSignals() 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.end_reset_model_signal.connect(self.end_reset_model) self.signals.row_order_changed_signal.connect(self.row_order_changed) @@ -142,45 +140,22 @@ class PlaylistModel(QAbstractTableModel): f"" ) - 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( self, - playlist_id: int, row_number: int, track_id: int, + note: Optional[str] = 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 try: prd = self.playlist_rows[row_number] except KeyError: 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)=}" ) return @@ -195,6 +170,9 @@ class PlaylistModel(QAbstractTableModel): if plr: # Add track to PlaylistRows plr.track_id = track_id + # Add any further note + if note: + plr.note += "\n" + note # Reset header row spanning self.signals.span_cells_signal.emit( row_number, HEADER_NOTES_COLUMN, 1, 1 @@ -723,7 +701,8 @@ class PlaylistModel(QAbstractTableModel): """ 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: @@ -734,6 +713,18 @@ class PlaylistModel(QAbstractTableModel): for modified_row in modified_rows: 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: """ Mark row as unplayed @@ -869,6 +860,39 @@ class PlaylistModel(QAbstractTableModel): self.signals.end_reset_model_signal.emit(to_playlist_id) 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: """ Open track at passed row number in Audacity @@ -1338,6 +1362,9 @@ class PlaylistProxyModel(QSortFilterProxyModel): def is_played_row(self, row_number: int) -> bool: 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: return self.playlist_model.mark_unplayed(row_numbers) @@ -1351,6 +1378,16 @@ class PlaylistProxyModel(QSortFilterProxyModel): 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: return self.playlist_model.open_in_audacity(row_number) diff --git a/app/playlists.py b/app/playlists.py index ca2f5ff..f188a5c 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -562,7 +562,7 @@ class PlaylistTab(QTableView): dlg = TrackSelectDialog( session=session, new_row_number=model_row_number, - playlist_id=self.playlist_id, + model=self.playlist_model, add_to_header=True, ) dlg.exec() diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui index a19d04a..5193324 100644 --- a/app/ui/main_window.ui +++ b/app/ui/main_window.ui @@ -786,9 +786,6 @@ padding-left: 8px; - - - diff --git a/app/ui/main_window_ui.py b/app/ui/main_window_ui.py index 6198d70..f640405 100644 --- a/app/ui/main_window_ui.py +++ b/app/ui/main_window_ui.py @@ -493,9 +493,6 @@ class Ui_MainWindow(object): self.menuPlaylist.addAction(self.actionPaste) self.menuSearc_h.addAction(self.actionSearch) 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_Songfacts) self.menuHelp.addAction(self.action_About)