WIP: template management: new, rename, delete working

This commit is contained in:
Keith Edmunds 2025-02-22 19:16:42 +00:00
parent e10c2adafe
commit 5f396a0993
4 changed files with 145 additions and 89 deletions

View File

@ -203,12 +203,12 @@ class Playlists(dbtables.PlaylistsTable):
@classmethod @classmethod
def create_playlist_from_template( def create_playlist_from_template(
cls, session: Session, template: "Playlists", playlist_name: str cls, session: Session, template_id: int, playlist_name: str
) -> Optional["Playlists"]: ) -> Optional["Playlists"]:
"""Create a new playlist from template""" """Create a new playlist from template"""
# Sanity check # Sanity check
if not template.id: if not template_id:
return None return None
playlist = cls(session, playlist_name) playlist = cls(session, playlist_name)
@ -217,7 +217,7 @@ class Playlists(dbtables.PlaylistsTable):
if not playlist or not playlist.id: if not playlist or not playlist.id:
return None return None
PlaylistRows.copy_playlist(session, template.id, playlist.id) PlaylistRows.copy_playlist(session, template_id, playlist.id)
return playlist return playlist

View File

@ -83,18 +83,6 @@ from utilities import check_db, update_bitrates
import helpers 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: class Current:
base_model: PlaylistModel base_model: PlaylistModel
proxy_model: PlaylistProxyModel proxy_model: PlaylistProxyModel
@ -267,7 +255,16 @@ class ItemlistManager(QDialog):
self.callbacks.edit(item_id) self.callbacks.edit(item_id)
def rename_item(self, item_id: int) -> None: 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: def toggle_favourite(self, item_id: int, checked: bool) -> None:
print(f"Toggle favourite for item {item_id}: {checked}") print(f"Toggle favourite for item {item_id}: {checked}")
@ -294,7 +291,7 @@ class ItemlistManagerCallbacks:
edit: Callable[[int], None] edit: Callable[[int], None]
favourite: Callable[[int, bool], None] favourite: Callable[[int, bool], None]
new_item: Callable[[], None] new_item: Callable[[], None]
rename: Callable[[int], None] rename: Callable[[int], Optional[str]]
class PreviewManager: class PreviewManager:
@ -459,16 +456,21 @@ class TemplateSelectorDialog(QDialog):
Class to manage user selection of template 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__() super().__init__()
self.templates = templates self.templates = templates
self.template_prompt = template_prompt
self.selected_id = None self.selected_id = None
self.init_ui() self.init_ui()
def init_ui(self): def init_ui(self):
# Create label # Create label
label = QLabel("Select template:") if not self.template_prompt:
self.template_prompt = "Select template:"
label = QLabel(self.template_prompt)
# Create combo box # Create combo box
self.combo_box = QComboBox() self.combo_box = QComboBox()
@ -868,14 +870,22 @@ class Window(QMainWindow):
self.signals.search_songfacts_signal.connect(self.open_songfacts_browser) self.signals.search_songfacts_signal.connect(self.open_songfacts_browser)
self.signals.search_wikipedia_signal.connect(self.open_wikipedia_browser) self.signals.search_wikipedia_signal.connect(self.open_wikipedia_browser)
def create_playlist( def create_playlist(self, session: Session, template_id: int) -> Optional[Playlists]:
self, session: Session, playlist_name: str
) -> Optional[Playlists]:
"""Create new playlist""" """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: if playlist:
return playlist return playlist
@ -1072,6 +1082,18 @@ class Window(QMainWindow):
if track_sequence.current: if track_sequence.current:
track_sequence.current.fade() 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): def hide_played(self):
"""Toggle hide played tracks""" """Toggle hide played tracks"""
@ -1178,6 +1200,7 @@ class Window(QMainWindow):
Delete / edit templates Delete / edit templates
""" """
# Define callbacks to handle management options
def delete(template_id: int) -> None: def delete(template_id: int) -> None:
"""delete template""" """delete template"""
@ -1191,19 +1214,15 @@ class Window(QMainWindow):
f"Delete template '{template.name}': " "Are you sure?", f"Delete template '{template.name}': " "Are you sure?",
): ):
# If template is currently open, re-check # If template is currently open, re-check
for idx in range(self.playlist_section.tabPlaylist.count()): open_idx = self.get_tab_index_for_playlist(template_id)
if ( if open_idx:
self.playlist_section.tabPlaylist.widget(idx).playlist_id if not helpers.ask_yes_no(
== template_id "Delete open template",
f"Template '{template.name}' is currently open. Really delete?",
): ):
if not helpers.ask_yes_no( return
"Delete open template", else:
f"Template '{template.name}' is currently open. Really delete?" self.playlist_section.tabPlaylist.removeTab(open_idx)
):
return
else:
self.playlist_section.tabPlaylist.removeTab(idx)
break
log.info(f"manage_templates: delete {template=}") log.info(f"manage_templates: delete {template=}")
template.delete(session) template.delete(session)
@ -1223,25 +1242,50 @@ class Window(QMainWindow):
self.playlist_section.tabPlaylist.setCurrentIndex(idx) self.playlist_section.tabPlaylist.setCurrentIndex(idx)
def favourite(template_id: int, favourite: bool) -> None: def favourite(template_id: int, favourite: bool) -> None:
"""favourite template""" """Mark template as (not) favourite"""
print(f"manage_templates.favourite({template_id=}") print(f"manage_templates.favourite({template_id=}")
print(f"{session=}") print(f"{session=}")
def new_item() -> None: def new_item() -> None:
"""new item""" """Create new template"""
print("manage_templates.new()") # Get base template
print(f"{session=}") 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""" """rename template"""
print(f"manage_templates.rename({template_id=}") template = session.get(Playlists, template_id)
print(f"{session=}") 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( 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 # Build a list of templates
@ -1251,8 +1295,10 @@ class Window(QMainWindow):
for template in Playlists.get_all_templates(session): for template in Playlists.get_all_templates(session):
# TODO: need to add in favourites # TODO: need to add in favourites
template_list.append(ItemlistItem(name=template.name, id=template.id)) template_list.append(ItemlistItem(name=template.name, id=template.id))
x = ItemlistManager(template_list, callbacks) # We need to retain a reference to the dialog box to stop it
x.show() # going out of scope and being garbage-collected.
self.dlg = ItemlistManager(template_list, callbacks)
self.dlg.show()
def mark_rows_for_moving(self) -> None: def mark_rows_for_moving(self) -> None:
""" """
@ -1337,37 +1383,17 @@ class Window(QMainWindow):
self.move_playlist_rows(unplayed_rows) self.move_playlist_rows(unplayed_rows)
self.disable_selection_timing = False self.disable_selection_timing = False
def new_playlist(self) -> None: def new_playlist(self) -> Optional[Playlists]:
""" """
Create new playlist, optionally from template 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: with db.Session() as session:
for template in Playlists.get_all_templates(session): template_id = self.solicit_template_to_use(session)
template_list.append((template.name, template.id)) if not template_id:
return None # User cancelled
dlg = TemplateSelectorDialog(template_list) playlist = self.create_playlist(session, template_id)
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
)
if playlist: if playlist:
playlist.mark_open() playlist.mark_open()
@ -1376,10 +1402,13 @@ class Window(QMainWindow):
session.commit() session.commit()
idx = self.create_playlist_tab(playlist) idx = self.create_playlist_tab(playlist)
self.playlist_section.tabPlaylist.setCurrentIndex(idx) self.playlist_section.tabPlaylist.setCurrentIndex(idx)
return playlist
else: 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""" """Open existing playlist"""
with db.Session() as session: with db.Session() as session:
@ -1388,11 +1417,16 @@ class Window(QMainWindow):
dlg.exec() dlg.exec()
playlist = dlg.playlist playlist = dlg.playlist
if playlist: if playlist:
idx = self.create_playlist_tab(playlist) self.open_playlist(session, playlist)
playlist.mark_open()
session.commit()
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: def open_songfacts_browser(self, title: str) -> None:
"""Search Songfacts for title""" """Search Songfacts for title"""
@ -1859,24 +1893,45 @@ class Window(QMainWindow):
# Switch to correct tab # Switch to correct tab
if playlist_id != self.current.playlist_id: if playlist_id != self.current.playlist_id:
for idx in range(self.playlist_section.tabPlaylist.count()): open_idx = self.get_tab_index_for_playlist(playlist_id)
if ( if open_idx:
self.playlist_section.tabPlaylist.widget(idx).playlist_id self.playlist_section.tabPlaylist.setCurrentIndex(open_idx)
== playlist_id else:
): raise ApplicationError(
self.playlist_section.tabPlaylist.setCurrentIndex(idx) f"show_track() can't find current playlist tab {playlist_id=}"
break )
self.active_tab().scroll_to_top(playlist_track.row_number) 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( def solicit_playlist_name(
self, session: Session, default: str = "" self, session: Session, default: str = "", prompt: str = "Playlist name:"
) -> Optional[str]: ) -> Optional[str]:
"""Get name of new playlist from user""" """Get name of new playlist from user"""
dlg = QInputDialog(self) dlg = QInputDialog(self)
dlg.setInputMode(QInputDialog.InputMode.TextInput) dlg.setInputMode(QInputDialog.InputMode.TextInput)
dlg.setLabelText("Playlist name:") dlg.setLabelText(prompt)
while True: while True:
if default: if default:
dlg.setTextValue(default) dlg.setTextValue(default)

View File

@ -26,6 +26,7 @@ from PyQt6.QtGui import (
) )
# Third party imports # Third party imports
from sqlalchemy.orm.session import Session
import obswebsocket # type: ignore import obswebsocket # type: ignore
# import snoop # type: ignore # import snoop # type: ignore
@ -772,7 +773,7 @@ class PlaylistModel(QAbstractTableModel):
return None 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. Same as refresh data, but only used when creating playslit.
Distinguishes profile time between initial load and other Distinguishes profile time between initial load and other
@ -1061,7 +1062,7 @@ class PlaylistModel(QAbstractTableModel):
# Update display # Update display
self.invalidate_row(track_sequence.previous.row_number) 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 Populate self.playlist_rows with playlist data

View File

@ -13,7 +13,7 @@ menus:
- title: "&Playlist" - title: "&Playlist"
actions: actions:
- text: "Open Playlist" - text: "Open Playlist"
handler: "open_playlist" handler: "open_existing_playlist"
shortcut: "Ctrl+O" shortcut: "Ctrl+O"
- text: "New Playlist" - text: "New Playlist"
handler: "new_playlist_dynamic_submenu" handler: "new_playlist_dynamic_submenu"