From d5950ab29a8bd5f7c87fed3dd669c0052bfff19d Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Mon, 15 Aug 2022 12:29:36 +0100 Subject: [PATCH] Move selected / move unplayed working --- app/models.py | 40 +++++++------- app/musicmuster.py | 111 ++++++++++++++++++++++++--------------- app/playlists.py | 67 ++++++++++------------- app/ui/main_window.ui | 4 +- app/ui/main_window_ui.py | 8 +-- 5 files changed, 125 insertions(+), 105 deletions(-) diff --git a/app/models.py b/app/models.py index ba69322..a5208ea 100644 --- a/app/models.py +++ b/app/models.py @@ -556,29 +556,33 @@ class PlaylistRows(Base): return plrs @staticmethod - def move_to_playlist(session: Session, - playlistrow_ids: List[int], - destination_playlist_id: int) -> None: - """ - Move the list of playlistrow_ids to the end of destination_playlist - """ + def get_last_used_row(session: Session, playlist_id: int) -> Optional[int]: + """Return the last used row for playlist, or None if no rows""" - # Find last row of destination playlist - last_row = session.execute( + return session.execute( select(func.max(PlaylistRows.row_number)) - .where(PlaylistRows.playlist_id == destination_playlist_id) + .where(PlaylistRows.playlist_id == playlist_id) ).scalar_one() - if last_row is None: - last_row = 0 - # Update the PlaylistRows entries - for plr_id in playlistrow_ids: - last_row += 1 - plr = session.get(PlaylistRows, plr_id) - plr.row_number = last_row - plr.playlist_id = destination_playlist_id + @classmethod + def get_unplayed_rows(cls, session: Session, + playlist_id: int) -> List[int]: + """ + For passed playlist, return a list of track rows that + have not been played. + """ - session.commit() + plrs = session.execute( + select(cls) + .where( + cls.playlist_id == playlist_id, + cls.track_id.is_not(None), + cls.played.is_(False) + ) + .order_by(cls.row_number) + ).scalars().all() + + return plrs # @classmethod # def get_playlist_rows(cls, playlist_id: int) -> \ diff --git a/app/musicmuster.py b/app/musicmuster.py index 6b9907e..add3973 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -7,6 +7,7 @@ import sys from datetime import datetime, timedelta # from typing import Callable, Dict, List, Optional, Tuple +from typing import List from PyQt5.QtCore import QDate, QEvent, Qt, QTime, QTimer from PyQt5.QtGui import QColor @@ -175,8 +176,7 @@ class Window(QMainWindow, Ui_MainWindow): # self.actionSelect_played_tracks.triggered.connect(self.select_played) self.actionSelect_previous_track.triggered.connect( self.select_previous_row) -# self.actionSelect_unplayed_tracks.triggered.connect( -# self.select_unplayed) + self.actionMoveUnplayed.triggered.connect(self.move_unplayed) self.actionSetNext.triggered.connect( lambda: self.tabPlaylist.currentWidget().set_selected_as_next()) self.actionSkipToNext.triggered.connect(self.play_next) @@ -485,9 +485,10 @@ class Window(QMainWindow, Ui_MainWindow): self.create_playlist_tab(session, playlist) playlist.mark_open(session) - def move_selected(self) -> None: + def move_playlist_rows(self, session: Session, + playlistrows: List[PlaylistRows]) -> None: """ - Move selected rows to another playlist + Move passed playlist rows to another playlist Actions required: - identify destination playlist @@ -496,43 +497,74 @@ class Window(QMainWindow, Ui_MainWindow): - update destination playlist display if loaded """ + if not playlistrows: + log.debug(f"musicmuster.move_playlist_rows({playlistrows=}") + # Identify destination playlist + visible_tab = self.visible_playlist_tab() + source_playlist = visible_tab.playlist_id + playlists = [] + for playlist in Playlists.get_all(session): + if playlist.id == source_playlist: + continue + else: + playlists.append(playlist) + + # Get destination playlist id + dlg = SelectPlaylistDialog(self, playlists=playlists, session=session) + dlg.exec() + if not dlg.playlist: + return + destination_playlist_id = dlg.playlist.id + + # Remove moved rows from display + visible_tab.remove_rows([plr.row_number for plr in playlistrows]) + + # Update playlist for the rows in the database + last_row = PlaylistRows.get_last_used_row(session, + destination_playlist_id) + if last_row is not None: + next_row = last_row + 1 + else: + next_row = 0 + + for plr in playlistrows: + plr.row_number = next_row + plr.playlist_id = destination_playlist_id + # Reset played as it's not been played on this playlist + plr.played = False + + # Update destination playlist_tab if visible (if not visible, it + # will be re-populated when it is opened) + destionation_playlist_tab = None + for tab in range(self.tabPlaylist.count()): + if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id: + destionation_playlist_tab = self.tabPlaylist.widget(tab) + break + if destionation_playlist_tab: + destionation_playlist_tab.populate(session, dlg.playlist.id) + + def move_selected(self) -> None: + """ + Move selected rows to another playlist + """ + with Session() as session: - visible_tab = self.visible_playlist_tab() - source_playlist = visible_tab.playlist_id - playlists = [] - for playlist in Playlists.get_all(session): - if playlist.id == source_playlist: - continue - else: - playlists.append(playlist) - - # Get destination playlist id - dlg = SelectPlaylistDialog(self, playlists=playlists, - session=session) - dlg.exec() - if not dlg.playlist: - return - destination_playlist = dlg.playlist - - # Update playlist for the rows in the database - plr_ids = visible_tab.get_selected_playlistrow_ids() - PlaylistRows.move_to_playlist( - session, plr_ids, destination_playlist.id + self.move_playlist_rows( + session, + self.visible_playlist_tab().get_selected_playlistrows(session) ) - # Remove moved rows from display - visible_tab.remove_selected_rows() + def move_unplayed(self) -> None: + """ + Move unplayed rows to another playlist + """ - # Update destination playlist_tab if visible (if not visible, it - # will be re-populated when it is opened) - destionation_playlist_tab = None - for tab in range(self.tabPlaylist.count()): - if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id: - destionation_playlist_tab = self.tabPlaylist.widget(tab) - break - if destionation_playlist_tab: - destionation_playlist_tab.populate(session, dlg.playlist.id) + playlist_id = self.visible_playlist_tab().playlist_id + with Session() as session: + unplayed_playlist_rows = PlaylistRows.get_unplayed_rows( + session, playlist_id) + self.move_playlist_rows(session, unplayed_playlist_rows) def play_next(self) -> None: """ @@ -640,7 +672,7 @@ class Window(QMainWindow, Ui_MainWindow): """Tidy up and reset search bar""" # Clear the search text - self.visible_playlist_tab().search("") + self.visible_playlist_tab().set_search("") # Clean up search bar self.txtSearch.setText("") self.txtSearch.setHidden(True) @@ -681,11 +713,6 @@ class Window(QMainWindow, Ui_MainWindow): """Select previous or first row in playlist""" self.visible_playlist_tab().select_previous_row() -# -# def select_unplayed(self) -> None: -# """Select all unplayed tracks in playlist""" -# -# self.visible_playlist_tab().select_unplayed_tracks() def set_main_window_size(self) -> None: """Set size of window from database""" diff --git a/app/playlists.py b/app/playlists.py index addff38..782d0b8 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -156,7 +156,7 @@ class PlaylistTab(QTableWidget): self.populate(session, self.playlist_id) def __repr__(self) -> str: - return f"" # ########## Events other than cell editing ########## @@ -472,7 +472,15 @@ class PlaylistTab(QTableWidget): Return a list of PlaylistRow ids of the selected rows """ - return [self._get_playlistrow_id(a) for a in self._selected_rows()] + return [self._get_playlistrow_id(a) for a in self._get_selected_rows()] + + def get_selected_playlistrows(self, session: Session) -> Optional[List]: + """ + Return a list of PlaylistRows of the selected rows + """ + + plr_ids = self.get_selected_playlistrow_ids() + return [session.get(PlaylistRows, a) for a in plr_ids] def insert_header(self, session: Session, note: str, repaint: bool = True) -> None: @@ -497,12 +505,6 @@ class PlaylistTab(QTableWidget): if repaint: self.update_display(session) # -# def _get_selected_rows(self) -> List[int]: -# """Return a sorted list of selected row numbers""" -# -# rows = self.selectionModel().selectedRows() -# return sorted([row.row() for row in rows]) -# # def get_selected_title(self) -> Optional[str]: # """Return title of selected row or None""" # @@ -747,13 +749,18 @@ class PlaylistTab(QTableWidget): # self.save_playlist(session) self.update_display(session) - def remove_selected_rows(self) -> None: - """Remove selected rows from display""" + def remove_rows(self, row_numbers: List[int]) -> None: + """Remove passed rows from display""" # Remove rows from display. Do so in reverse order so that # row numbers remain valid. - for row in sorted(self._selected_rows(), reverse=True): + for row in sorted(row_numbers, reverse=True): self.removeRow(row) + + def remove_selected_rows(self) -> None: + """Remove selected rows from display""" + + self.remove_rows(self._get_selected_rows()) # Reset drag mode self.setDragEnabled(False) @@ -868,7 +875,7 @@ class PlaylistTab(QTableWidget): row: int selected_rows: List[int] - selected_rows = self._selected_rows() + selected_rows = self._get_selected_rows() # we will only handle zero or one selected rows if len(selected_rows) > 1: return @@ -915,7 +922,7 @@ class PlaylistTab(QTableWidget): row: int selected_rows: List[int] - selected_rows = self._selected_rows() + selected_rows = self._get_selected_rows() # we will only handle zero or one selected rows if len(selected_rows) > 1: return @@ -943,16 +950,6 @@ class PlaylistTab(QTableWidget): track_id = self._get_row_track_id(row) self.selectRow(row) -# -# def select_unplayed_tracks(self) -> None: -# """Select all unplayed tracks in playlist""" -# -# try: -# self.selecting_in_progress = True -# self._select_tracks(played=False) -# finally: -# self.selecting_in_progress = False -# self._select_event() def set_searchtext(self, text: Optional[str]) -> None: """Set the search text and find first match""" @@ -1393,14 +1390,6 @@ class PlaylistTab(QTableWidget): return track_id -# def _get_unplayed_track_rows(self) -> Optional[List[int]]: -# """Return rows marked as unplayed, or None""" -# -# unplayed_rows: Set[int] = set(self._meta_notset(RowMeta.PLAYED)) -# notes_rows: Set[int] = set(self._get_notes_rows()) -# -# return list(unplayed_rows - notes_rows) - def _get_selected_row(self) -> Optional[int]: """Return row number of first selected row, or None if none selected""" @@ -1409,6 +1398,13 @@ class PlaylistTab(QTableWidget): else: return self.selectionModel().selectedRows()[0].row() + def _get_selected_rows(self) -> List[int]: + """Return a list of selected row numbers""" + + # Use a set to deduplicate result (a selected row will have all + # items in that row selected) + return [row for row in set([a.row() for a in self.selectedItems()])] + def _get_unreadable_track_rows(self) -> List[int]: """Return rows marked as unreadable, or None""" @@ -1680,7 +1676,7 @@ class PlaylistTab(QTableWidget): if self.selecting_in_progress: return - selected_rows = self._selected_rows() + selected_rows = self._get_selected_rows() # If no rows are selected, we have nothing to do if len(selected_rows) == 0: self.musicmuster.lblSumPlaytime.setText("") @@ -1697,13 +1693,6 @@ class PlaylistTab(QTableWidget): else: self.musicmuster.lblSumPlaytime.setText("") - def _selected_rows(self) -> List[int]: - """Return a list of selected row numbers""" - - # Use a set to deduplicate result (a selected row will have all - # items in that row selected) - return [row for row in set([a.row() for a in self.selectedItems()])] - def _set_column_widths(self, session: Session) -> None: """Column widths from settings""" diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui index e9a86e3..1fcf54d 100644 --- a/app/ui/main_window.ui +++ b/app/ui/main_window.ui @@ -763,7 +763,7 @@ border: 1px solid rgb(85, 87, 83); - + @@ -1000,7 +1000,7 @@ border: 1px solid rgb(85, 87, 83); Select played tracks - + Move &unplayed tracks to... diff --git a/app/ui/main_window_ui.py b/app/ui/main_window_ui.py index b827b64..909e6f1 100644 --- a/app/ui/main_window_ui.py +++ b/app/ui/main_window_ui.py @@ -430,8 +430,8 @@ class Ui_MainWindow(object): self.actionSelect_previous_track.setObjectName("actionSelect_previous_track") self.actionSelect_played_tracks = QtWidgets.QAction(MainWindow) self.actionSelect_played_tracks.setObjectName("actionSelect_played_tracks") - self.actionMove_unplayed = QtWidgets.QAction(MainWindow) - self.actionMove_unplayed.setObjectName("actionMove_unplayed") + self.actionMoveUnplayed = QtWidgets.QAction(MainWindow) + self.actionMoveUnplayed.setObjectName("actionMoveUnplayed") self.actionAdd_note = QtWidgets.QAction(MainWindow) self.actionAdd_note.setObjectName("actionAdd_note") self.actionEnable_controls = QtWidgets.QAction(MainWindow) @@ -458,7 +458,7 @@ class Ui_MainWindow(object): self.menuFile.addAction(self.actionDeletePlaylist) self.menuFile.addSeparator() self.menuFile.addAction(self.actionMoveSelected) - self.menuFile.addAction(self.actionMove_unplayed) + self.menuFile.addAction(self.actionMoveUnplayed) self.menuFile.addAction(self.actionDownload_CSV_of_played_tracks) self.menuFile.addSeparator() self.menuFile.addAction(self.actionE_xit) @@ -559,7 +559,7 @@ class Ui_MainWindow(object): self.actionSelect_previous_track.setText(_translate("MainWindow", "Select previous track")) self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K")) self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks")) - self.actionMove_unplayed.setText(_translate("MainWindow", "Move &unplayed tracks to...")) + self.actionMoveUnplayed.setText(_translate("MainWindow", "Move &unplayed tracks to...")) self.actionAdd_note.setText(_translate("MainWindow", "Add note...")) self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T")) self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls"))