diff --git a/app/models.py b/app/models.py index 58b9339..e37c682 100644 --- a/app/models.py +++ b/app/models.py @@ -18,7 +18,7 @@ from sqlalchemy import ( delete, Float, ForeignKey, - # func, + func, Integer, select, String, @@ -317,15 +317,15 @@ class Playlists(Base): # self.loaded = False # session.add(self) # session.flush() -# -# @classmethod -# def get_all(cls, session: Session) -> List["Playlists"]: -# """Returns a list of all playlists ordered by last use""" -# -# return ( -# session.query(cls).order_by( -# cls.loaded.desc(), cls.last_used.desc()) -# ).all() + + @classmethod + def get_all(cls, session: Session) -> List["Playlists"]: + """Returns a list of all playlists ordered by last use""" + + return ( + session.query(cls).order_by( + cls.loaded.desc(), cls.last_used.desc()) + ).all() # # @classmethod # def get_closed(cls, session: Session) -> List["Playlists"]: @@ -508,6 +508,31 @@ class PlaylistRows(Base): # Ensure new row numbers are available to the caller session.commit() + @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 + """ + + # Find last row of destination playlist + last_row = session.execute( + select(func.max(PlaylistRows.row_number)) + .where(PlaylistRows.playlist_id == destination_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 + + session.commit() + # @classmethod # def get_playlist_rows(cls, playlist_id: int) -> \ # Optional[List["PlaylistRows"]]: diff --git a/app/musicmuster.py b/app/musicmuster.py index e511731..f72c582 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -13,16 +13,17 @@ import sys # from typing import Callable, Dict, List, Optional, Tuple # # from PyQt5.QtCore import QDate, QEvent, QProcess, Qt, QTime, QTimer, QUrl +from PyQt5.QtCore import Qt # from PyQt5.QtGui import QColor # from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView from PyQt5.QtWidgets import ( QApplication, - # QDialog, + QDialog, # QFileDialog, # QInputDialog, QLabel, # QLineEdit, - # QListWidgetItem, + QListWidgetItem, QMainWindow, # QMessageBox, ) @@ -35,14 +36,15 @@ from dbconfig import engine, Session from models import ( Base, # Playdates, + PlaylistRows, Playlists, - # Settings, + Settings, Tracks ) from playlists import PlaylistTab # from sqlalchemy.orm.exc import DetachedInstanceError # from ui.dlg_search_database_ui import Ui_Dialog -# from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist +from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # from ui.downloadcsv_ui import Ui_DateSelect from config import Config from ui.main_window_ui import Ui_MainWindow @@ -503,51 +505,58 @@ class Window(QMainWindow, Ui_MainWindow): playlist.mark_open(session) def move_selected(self) -> None: - """Move selected rows to another playlist""" + """ + Move selected rows to another playlist - # ***KAE - pass + Actions required: + - identify destination playlist + - update playlist for the rows in the database + - remove them from the display + - update destination playlist display if loaded + """ -# with Session() as session: -# visible_tab = self.visible_playlist_tab() -# visible_tab_id = visible_tab.playlist_id -# -# source_playlist = None -# playlists = [] -# for playlist in Playlists.get_all(session): -# if playlist.id == visible_tab_id: -# source_playlist = playlist -# 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 -# -# self.visible_playlist_tab().move_selected_to_playlist( -# session, destination_playlist.id) -# -# # Update destination playlist_tab if visible (if not visible, it -# # will be re-populated when it is opened) -# destination_visible_playlist_tab = None -# for tab in range(self.tabPlaylist.count()): -# # Non-playlist tabs won't have a 'playlist_id' attribute -# if not hasattr(self.tabPlaylist.widget(tab), 'playlist_id'): -# continue -# if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id: -# destination_visible_playlist_tab = ( -# self.tabPlaylist.widget(tab)) -# break -# -# if destination_visible_playlist_tab: -# # We need to commit to database for populate to work -# session.commit() -# destination_visible_playlist_tab.populate( -# session, dlg.playlist.id) + # Identify destination 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 + ) + + # Remove moved rows from display + visible_tab.remove_selected_rows() + + # Update destination playlist_tab if visible (if not visible, it + # will be re-populated when it is opened) + destination_visible_playlist_tab = None + for tab in range(self.tabPlaylist.count()): + # Non-playlist tabs won't have a 'playlist_id' attribute + if not hasattr(self.tabPlaylist.widget(tab), 'playlist_id'): + continue + if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id: + destination_visible_playlist_tab = ( + self.tabPlaylist.widget(tab)) + break + if destination_visible_playlist_tab: + destination_visible_playlist_tab.populate( + session, dlg.playlist.id) # # def open_info_tabs(self) -> None: # """ @@ -1095,57 +1104,57 @@ class Window(QMainWindow, Ui_MainWindow): # self.ui.dateTimeEdit.setTime(QTime(19, 59, 0)) # self.ui.buttonBox.accepted.connect(self.accept) # self.ui.buttonBox.rejected.connect(self.reject) -# -# -# class SelectPlaylistDialog(QDialog): -# def __init__(self, parent=None, playlists=None, session=None): -# super().__init__(parent) -# -# if playlists is None: -# return -# self.ui = Ui_dlgSelectPlaylist() -# self.ui.setupUi(self) -# self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick) -# self.ui.buttonBox.accepted.connect(self.open) -# self.ui.buttonBox.rejected.connect(self.close) -# self.session = session -# self.playlist = None -# self.plid = None -# -# record = Settings.get_int_settings( -# self.session, "select_playlist_dialog_width") -# width = record.f_int or 800 -# record = Settings.get_int_settings( -# self.session, "select_playlist_dialog_height") -# height = record.f_int or 600 -# self.resize(width, height) -# -# for playlist in playlists: -# p = QListWidgetItem() -# p.setText(playlist.name) -# p.setData(Qt.UserRole, playlist) -# self.ui.lstPlaylists.addItem(p) -# -# def __del__(self): # review -# record = Settings.get_int_settings( -# self.session, "select_playlist_dialog_height") -# if record.f_int != self.height(): -# record.update(self.session, {'f_int': self.height()}) -# -# record = Settings.get_int_settings( -# self.session, "select_playlist_dialog_width") -# if record.f_int != self.width(): -# record.update(self.session, {'f_int': self.width()}) -# -# def list_doubleclick(self, entry): # review -# self.playlist = entry.data(Qt.UserRole) -# self.accept() -# -# def open(self): # review -# if self.ui.lstPlaylists.selectedItems(): -# item = self.ui.lstPlaylists.currentItem() -# self.playlist = item.data(Qt.UserRole) -# self.accept() + + +class SelectPlaylistDialog(QDialog): + def __init__(self, parent=None, playlists=None, session=None): + super().__init__(parent) + + if playlists is None: + return + self.ui = Ui_dlgSelectPlaylist() + self.ui.setupUi(self) + self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick) + self.ui.buttonBox.accepted.connect(self.open) + self.ui.buttonBox.rejected.connect(self.close) + self.session = session + self.playlist = None + self.plid = None + + record = Settings.get_int_settings( + self.session, "select_playlist_dialog_width") + width = record.f_int or 800 + record = Settings.get_int_settings( + self.session, "select_playlist_dialog_height") + height = record.f_int or 600 + self.resize(width, height) + + for playlist in playlists: + p = QListWidgetItem() + p.setText(playlist.name) + p.setData(Qt.UserRole, playlist) + self.ui.lstPlaylists.addItem(p) + + def __del__(self): # review + record = Settings.get_int_settings( + self.session, "select_playlist_dialog_height") + if record.f_int != self.height(): + record.update(self.session, {'f_int': self.height()}) + + record = Settings.get_int_settings( + self.session, "select_playlist_dialog_width") + if record.f_int != self.width(): + record.update(self.session, {'f_int': self.width()}) + + def list_doubleclick(self, entry): # review + self.playlist = entry.data(Qt.UserRole) + self.accept() + + def open(self): # review + if self.ui.lstPlaylists.selectedItems(): + item = self.ui.lstPlaylists.currentItem() + self.playlist = item.data(Qt.UserRole) + self.accept() if __name__ == "__main__": diff --git a/app/playlists.py b/app/playlists.py index cb0da9e..688371a 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -340,6 +340,13 @@ class PlaylistTab(QTableWidget): self.clearSelection() self.setDragEnabled(False) + def get_selected_playlistrow_ids(self) -> Optional[List]: + """ + Return a list of PlaylistRow ids of the selected rows + """ + + return [self._get_playlistrow_id(a) for a in self._selected_rows()] + # def closeEvent(self, event) -> None: # """Save column widths""" # @@ -697,6 +704,16 @@ class PlaylistTab(QTableWidget): # KAE self.save_playlist(session) self.update_display(session) + def remove_selected_rows(self) -> None: + """Remove selected 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): + self.removeRow(row) + # Reset drag mode + self.setDragEnabled(False) + def save_playlist(self, session: Session) -> None: """ Save playlist to database @@ -1254,22 +1271,14 @@ class PlaylistTab(QTableWidget): """ # Delete rows from database - # Each item in a row will be returned from selectedItems(), so - # make a set to remove duplicate row numbers - selected_rows = sorted( - set(item.row() for item in self.selectedItems()) - ) - plr_ids = [self._get_playlistrow_id(a) for a in selected_rows] + plr_ids = self.get_selected_playlistrow_ids() with Session() as session: PlaylistRows.delete_rows(session, plr_ids) # Fix up row numbers left in this playlist PlaylistRows.fixup_rownumbers(session, self.playlist_id) - - # Remove rows from display. Do so in reverse order so that - # row numbers remain valid. - for row in sorted(selected_rows, reverse=True): - self.removeRow(row) + #Remove selected rows from display + self.remove_selected_rows() def _drop_on(self, event): """ @@ -1736,9 +1745,7 @@ class PlaylistTab(QTableWidget): if self.selecting_in_progress: return - # Get the row number of all selected items and put into a set - # to deduplicate - selected_rows = set([item.row() for item in self.selectedItems()]) + selected_rows = self._selected_rows() # If no rows are selected, we have nothing to do if len(selected_rows) == 0: self.musicmuster.lblSumPlaytime.setText("") @@ -1754,6 +1761,14 @@ class PlaylistTab(QTableWidget): f"Selected duration: {helpers.ms_to_mmss(ms)}") else: self.musicmuster.lblSumPlaytime.setText("") + + def _selected_rows(self) -> Optional[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 _select_tracks(self, played: bool) -> None: # """