From 5f396a099391789c0472e92ddc1deff38ab45c60 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Sat, 22 Feb 2025 19:16:42 +0000 Subject: [PATCH] WIP: template management: new, rename, delete working --- app/models.py | 6 +- app/musicmuster.py | 221 +++++++++++++++++++++++++++---------------- app/playlistmodel.py | 5 +- menu.yaml | 2 +- 4 files changed, 145 insertions(+), 89 deletions(-) diff --git a/app/models.py b/app/models.py index ee3a1b4..e620b0b 100644 --- a/app/models.py +++ b/app/models.py @@ -203,12 +203,12 @@ class Playlists(dbtables.PlaylistsTable): @classmethod def create_playlist_from_template( - cls, session: Session, template: "Playlists", playlist_name: str + cls, session: Session, template_id: int, playlist_name: str ) -> Optional["Playlists"]: """Create a new playlist from template""" # Sanity check - if not template.id: + if not template_id: return None playlist = cls(session, playlist_name) @@ -217,7 +217,7 @@ class Playlists(dbtables.PlaylistsTable): if not playlist or not playlist.id: return None - PlaylistRows.copy_playlist(session, template.id, playlist.id) + PlaylistRows.copy_playlist(session, template_id, playlist.id) return playlist diff --git a/app/musicmuster.py b/app/musicmuster.py index e7ffd83..ba2d6d8 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -83,18 +83,6 @@ from utilities import check_db, update_bitrates import helpers -class DownloadCSV(QDialog): - def __init__(self, parent=None): - super().__init__() - - self.ui = Ui_DateSelect() - self.ui.setupUi(self) - self.ui.dateTimeEdit.setDate(QDate.currentDate()) - self.ui.dateTimeEdit.setTime(QTime(19, 59, 0)) - self.ui.buttonBox.accepted.connect(self.accept) - self.ui.buttonBox.rejected.connect(self.reject) - - class Current: base_model: PlaylistModel proxy_model: PlaylistProxyModel @@ -267,7 +255,16 @@ class ItemlistManager(QDialog): self.callbacks.edit(item_id) def rename_item(self, item_id: int) -> None: - print(f"Rename item {item_id}") + new_name = self.callbacks.rename(item_id) + if not new_name: + return + # Rename item in list + for row in range(self.table.rowCount()): + item = self.table.item(row, 0) + if item and self.items[row].id == item_id: + item.setText(new_name) + self.items[row].name = new_name + break def toggle_favourite(self, item_id: int, checked: bool) -> None: print(f"Toggle favourite for item {item_id}: {checked}") @@ -294,7 +291,7 @@ class ItemlistManagerCallbacks: edit: Callable[[int], None] favourite: Callable[[int, bool], None] new_item: Callable[[], None] - rename: Callable[[int], None] + rename: Callable[[int], Optional[str]] class PreviewManager: @@ -459,16 +456,21 @@ class TemplateSelectorDialog(QDialog): Class to manage user selection of template """ - def __init__(self, templates: list[tuple[str, int]]) -> None: + def __init__( + self, templates: list[tuple[str, int]], template_prompt: Optional[str] + ) -> None: super().__init__() self.templates = templates + self.template_prompt = template_prompt self.selected_id = None self.init_ui() def init_ui(self): # Create label - label = QLabel("Select template:") + if not self.template_prompt: + self.template_prompt = "Select template:" + label = QLabel(self.template_prompt) # Create combo box self.combo_box = QComboBox() @@ -868,14 +870,22 @@ class Window(QMainWindow): self.signals.search_songfacts_signal.connect(self.open_songfacts_browser) self.signals.search_wikipedia_signal.connect(self.open_wikipedia_browser) - def create_playlist( - self, session: Session, playlist_name: str - ) -> Optional[Playlists]: + def create_playlist(self, session: Session, template_id: int) -> Optional[Playlists]: """Create new playlist""" - log.debug(f"create_playlist({playlist_name=}") + # Get a name for this new playlist + playlist_name = self.solicit_playlist_name(session) + if not playlist_name: + return None - playlist = Playlists(session, playlist_name) + # If template.id == 0, user doesn't want a template + playlist: Optional[Playlists] + if template_id == 0: + playlist = Playlists(session, playlist_name) + else: + playlist = Playlists.create_playlist_from_template( + session, template_id, playlist_name + ) if playlist: return playlist @@ -1072,6 +1082,18 @@ class Window(QMainWindow): if track_sequence.current: track_sequence.current.fade() + def get_tab_index_for_playlist(self, playlist_id: int) -> Optional[int]: + """ + Return the tab index for the passed playlist_id if it is displayed, + else return None. + """ + + for idx in range(self.playlist_section.tabPlaylist.count()): + if self.playlist_section.tabPlaylist.widget(idx).playlist_id == playlist_id: + return idx + + return None + def hide_played(self): """Toggle hide played tracks""" @@ -1178,6 +1200,7 @@ class Window(QMainWindow): Delete / edit templates """ + # Define callbacks to handle management options def delete(template_id: int) -> None: """delete template""" @@ -1191,19 +1214,15 @@ class Window(QMainWindow): f"Delete template '{template.name}': " "Are you sure?", ): # If template is currently open, re-check - for idx in range(self.playlist_section.tabPlaylist.count()): - if ( - self.playlist_section.tabPlaylist.widget(idx).playlist_id - == template_id + open_idx = self.get_tab_index_for_playlist(template_id) + if open_idx: + if not helpers.ask_yes_no( + "Delete open template", + f"Template '{template.name}' is currently open. Really delete?", ): - if not helpers.ask_yes_no( - "Delete open template", - f"Template '{template.name}' is currently open. Really delete?" - ): - return - else: - self.playlist_section.tabPlaylist.removeTab(idx) - break + return + else: + self.playlist_section.tabPlaylist.removeTab(open_idx) log.info(f"manage_templates: delete {template=}") template.delete(session) @@ -1223,25 +1242,50 @@ class Window(QMainWindow): self.playlist_section.tabPlaylist.setCurrentIndex(idx) def favourite(template_id: int, favourite: bool) -> None: - """favourite template""" + """Mark template as (not) favourite""" print(f"manage_templates.favourite({template_id=}") print(f"{session=}") def new_item() -> None: - """new item""" + """Create new template""" - print("manage_templates.new()") - print(f"{session=}") + # Get base template + template_id = self.solicit_template_to_use( + session, template_prmompt="New template based upon:" + ) + if template_id is None: + return - def rename(template_id: int) -> None: + new_template = self.create_playlist(session, template_id) + if new_template: + self.open_playlist(session, new_template) + + def rename(template_id: int) -> Optional[str]: """rename template""" - print(f"manage_templates.rename({template_id=}") - print(f"{session=}") + template = session.get(Playlists, template_id) + if not template: + raise ApplicationError( + f"manage_templeate.delete({template_id=}) can't load template" + ) + new_name = self.solicit_playlist_name(session, template.name) + if new_name: + template.rename(session, new_name) + idx = self.tabBar.currentIndex() + self.tabBar.setTabText(idx, new_name) + session.commit() + return new_name + return None + + # Call listitem management dialog to manage templates callbacks = ItemlistManagerCallbacks( - delete=delete, edit=edit, favourite=favourite, new_item=new_item, rename=rename + delete=delete, + edit=edit, + favourite=favourite, + new_item=new_item, + rename=rename, ) # Build a list of templates @@ -1251,8 +1295,10 @@ class Window(QMainWindow): for template in Playlists.get_all_templates(session): # TODO: need to add in favourites template_list.append(ItemlistItem(name=template.name, id=template.id)) - x = ItemlistManager(template_list, callbacks) - x.show() + # We need to retain a reference to the dialog box to stop it + # going out of scope and being garbage-collected. + self.dlg = ItemlistManager(template_list, callbacks) + self.dlg.show() def mark_rows_for_moving(self) -> None: """ @@ -1337,37 +1383,17 @@ class Window(QMainWindow): self.move_playlist_rows(unplayed_rows) self.disable_selection_timing = False - def new_playlist(self) -> None: + def new_playlist(self) -> Optional[Playlists]: """ Create new playlist, optionally from template """ - # Build a list of (template-name, playlist-id) tuples starting - # with the "no template" entry - template_list: list[tuple[str, int]] = [] - template_list.append((Config.NO_TEMPLATE_NAME, 0)) - with db.Session() as session: - for template in Playlists.get_all_templates(session): - template_list.append((template.name, template.id)) + template_id = self.solicit_template_to_use(session) + if not template_id: + return None # User cancelled - dlg = TemplateSelectorDialog(template_list) - if not dlg.exec(): - return # User cancelled - template_id = dlg.selected_id - - # Get a name for this new playlist - playlist_name = self.solicit_playlist_name(session) - if not playlist_name: - return - - # If template_id == 0, user doesn't want a template - if template_id == 0: - playlist = self.create_playlist(session, playlist_name) - else: - playlist = Playlists.create_playlist_from_template( - session, template, playlist_name - ) + playlist = self.create_playlist(session, template_id) if playlist: playlist.mark_open() @@ -1376,10 +1402,13 @@ class Window(QMainWindow): session.commit() idx = self.create_playlist_tab(playlist) self.playlist_section.tabPlaylist.setCurrentIndex(idx) + return playlist else: - log.error("Playlist failed to create") + ApplicationError("new_playlist: Playlist failed to create") - def open_playlist(self) -> None: + return None + + def open_existing_playlist(self) -> None: """Open existing playlist""" with db.Session() as session: @@ -1388,11 +1417,16 @@ class Window(QMainWindow): dlg.exec() playlist = dlg.playlist if playlist: - idx = self.create_playlist_tab(playlist) - playlist.mark_open() - session.commit() + self.open_playlist(session, playlist) - self.playlist_section.tabPlaylist.setCurrentIndex(idx) + def open_playlist(self, session: Session, playlist: Playlists) -> None: + """Open passed playlist""" + + idx = self.create_playlist_tab(playlist) + playlist.mark_open() + session.commit() + + self.playlist_section.tabPlaylist.setCurrentIndex(idx) def open_songfacts_browser(self, title: str) -> None: """Search Songfacts for title""" @@ -1859,24 +1893,45 @@ class Window(QMainWindow): # Switch to correct tab if playlist_id != self.current.playlist_id: - for idx in range(self.playlist_section.tabPlaylist.count()): - if ( - self.playlist_section.tabPlaylist.widget(idx).playlist_id - == playlist_id - ): - self.playlist_section.tabPlaylist.setCurrentIndex(idx) - break + open_idx = self.get_tab_index_for_playlist(playlist_id) + if open_idx: + self.playlist_section.tabPlaylist.setCurrentIndex(open_idx) + else: + raise ApplicationError( + f"show_track() can't find current playlist tab {playlist_id=}" + ) self.active_tab().scroll_to_top(playlist_track.row_number) + def solicit_template_to_use( + self, session: Session, template_prmompt: Optional[str] = None + ) -> Optional[int]: + """ + Have user select a template. Return the template.id, or None if they cancel. + template_id of zero means don't use a template. + """ + + template_name_id_list: list[tuple[str, int]] = [] + template_name_id_list.append((Config.NO_TEMPLATE_NAME, 0)) + + with db.Session() as session: + for template in Playlists.get_all_templates(session): + template_name_id_list.append((template.name, template.id)) + + dlg = TemplateSelectorDialog(template_name_id_list, template_prmompt) + if not dlg.exec() or dlg.selected_id is None: + return None # User cancelled + + return dlg.selected_id + def solicit_playlist_name( - self, session: Session, default: str = "" + self, session: Session, default: str = "", prompt: str = "Playlist name:" ) -> Optional[str]: """Get name of new playlist from user""" dlg = QInputDialog(self) dlg.setInputMode(QInputDialog.InputMode.TextInput) - dlg.setLabelText("Playlist name:") + dlg.setLabelText(prompt) while True: if default: dlg.setTextValue(default) diff --git a/app/playlistmodel.py b/app/playlistmodel.py index 0ef38a2..dd7ab13 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -26,6 +26,7 @@ from PyQt6.QtGui import ( ) # Third party imports +from sqlalchemy.orm.session import Session import obswebsocket # type: ignore # import snoop # type: ignore @@ -772,7 +773,7 @@ class PlaylistModel(QAbstractTableModel): return None - def load_data(self, session: db.session) -> None: + def load_data(self, session: Session) -> None: """ Same as refresh data, but only used when creating playslit. Distinguishes profile time between initial load and other @@ -1061,7 +1062,7 @@ class PlaylistModel(QAbstractTableModel): # Update display self.invalidate_row(track_sequence.previous.row_number) - def refresh_data(self, session: db.session) -> None: + def refresh_data(self, session: Session) -> None: """ Populate self.playlist_rows with playlist data diff --git a/menu.yaml b/menu.yaml index 62fc331..239d5ce 100644 --- a/menu.yaml +++ b/menu.yaml @@ -13,7 +13,7 @@ menus: - title: "&Playlist" actions: - text: "Open Playlist" - handler: "open_playlist" + handler: "open_existing_playlist" shortcut: "Ctrl+O" - text: "New Playlist" handler: "new_playlist_dynamic_submenu"