WIP V3: insert track works

This commit is contained in:
Keith Edmunds 2023-10-30 21:55:02 +00:00
parent e4b986fd2e
commit 3557d22c54
6 changed files with 106 additions and 80 deletions

View File

@ -31,11 +31,10 @@ def Session() -> Generator[scoped_session, None, None]:
function = frame.function function = frame.function
lineno = frame.lineno lineno = frame.lineno
Session = scoped_session(sessionmaker(bind=engine)) Session = scoped_session(sessionmaker(bind=engine))
log.debug(f"SqlA: session acquired [{hex(id(Session))}]")
log.debug( log.debug(
f"Session acquisition: {file}:{function}:{lineno} " f"[{hex(id(Session))}]" f"Session acquired: {file}:{function}:{lineno} " f"[{hex(id(Session))}]"
) )
yield Session yield Session
log.debug(f" SqlA: session released [{hex(id(Session))}]") log.debug(f" Session released [{hex(id(Session))}]")
Session.commit() Session.commit()
Session.close() Session.close()

View File

@ -71,7 +71,7 @@ from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tr
from config import Config from config import Config
from playlists import PlaylistTab from playlists import PlaylistTab
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
from ui.dlg_search_database_ui import Ui_Dialog # type: ignore from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
from ui.main_window_ui import Ui_MainWindow # type: ignore from ui.main_window_ui import Ui_MainWindow # type: ignore
@ -246,9 +246,10 @@ class MusicMusterSignals(QObject):
emit-a-signal-from-another-class-to-main-class emit-a-signal-from-another-class-to-main-class
""" """
enable_escape_signal = pyqtSignal(bool)
set_next_track_signal = pyqtSignal(int, int) set_next_track_signal = pyqtSignal(int, int)
span_cells_signal = pyqtSignal(int, int, int, int) span_cells_signal = pyqtSignal(int, int, int, int)
enable_escape_signal = pyqtSignal(bool) add_track_to_playlist_signal = pyqtSignal(int, int, str)
class PlaylistTrack: class PlaylistTrack:
@ -745,9 +746,7 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session: with Session() as session:
# Save the selected PlaylistRows items ready for a later # Save the selected PlaylistRows items ready for a later
# paste # paste
self.selected_plrs = self.active_tab().get_selected_playlistrows( self.selected_plrs = self.active_tab().get_selected_playlistrows(session)
session
)
def debug(self): def debug(self):
"""Invoke debugger""" """Invoke debugger"""
@ -928,7 +927,10 @@ class Window(QMainWindow, Ui_MainWindow):
def get_one_track(self, session: scoped_session) -> Optional[Tracks]: def get_one_track(self, session: scoped_session) -> Optional[Tracks]:
"""Show dialog box to select one track and return it to caller""" """Show dialog box to select one track and return it to caller"""
dlg = DbDialog(self, session, get_one_track=True) dlg = TrackSelectDialog(self, session)
dlg.ui.txtNote.hide()
dlg.ui.lblNote.hide()
if dlg.exec(): if dlg.exec():
return dlg.track return dlg.track
else: else:
@ -1054,15 +1056,18 @@ class Window(QMainWindow, Ui_MainWindow):
ok = dlg.exec() ok = dlg.exec()
if ok: if ok:
model.insert_header_row( model.insert_header_row(
self.active_tab().get_selected_row_number(), self.active_tab().get_selected_row_number(), dlg.textValue()
dlg.textValue()
) )
def insert_track(self) -> None: def insert_track(self) -> None:
"""Show dialog box to select and add track from database""" """Show dialog box to select and add track from database"""
with Session() as session: with Session() as session:
dlg = DbDialog(self, session, get_one_track=False) dlg = TrackSelectDialog(
session=session,
signals=self.signals,
playlist_id=self.active_tab().playlist_id,
)
dlg.exec() dlg.exec()
def load_last_playlists(self) -> None: def load_last_playlists(self) -> None:
@ -1159,9 +1164,7 @@ class Window(QMainWindow, Ui_MainWindow):
""" """
with Session() as session: with Session() as session:
selected_plrs = self.active_tab().get_selected_playlistrows( selected_plrs = self.active_tab().get_selected_playlistrows(session)
session
)
if not selected_plrs: if not selected_plrs:
return return
@ -1914,66 +1917,42 @@ class CartDialog(QDialog):
self.ui.lblPath.setText(self.path) self.ui.lblPath.setText(self.path)
class DbDialog(QDialog): class TrackSelectDialog(QDialog):
"""Select track from database""" """Select track from database"""
def __init__( def __init__(
self, self,
musicmuster: Window,
session: scoped_session, session: scoped_session,
get_one_track: bool = False, signals: MusicMusterSignals,
playlist_id: int,
*args, *args,
**kwargs, **kwargs,
) -> None: ) -> None:
""" """
Subclassed QDialog to manage track selection Subclassed QDialog to manage track selection
If get_one_track is True, return after first track selection
with that track in ui.track. Otherwise, allow multiple tracks
to be added to the playlist.
""" """
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.musicmuster = musicmuster
self.session = session self.session = session
self.get_one_track = get_one_track self.signals = signals
self.playlist_id = playlist_id
self.ui = Ui_Dialog() self.ui = Ui_Dialog()
self.ui.setupUi(self) self.ui.setupUi(self)
self.ui.btnAdd.clicked.connect(self.add_selected) self.ui.btnAdd.clicked.connect(self.add_selected)
self.ui.btnAddClose.clicked.connect(self.add_selected_and_close) self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)
self.ui.btnClose.clicked.connect(self.close) self.ui.btnClose.clicked.connect(self.close)
self.ui.matchList.itemDoubleClicked.connect(self.double_click) self.ui.matchList.itemDoubleClicked.connect(self.add_selected)
self.ui.matchList.itemSelectionChanged.connect(self.selection_changed) self.ui.matchList.itemSelectionChanged.connect(self.selection_changed)
self.ui.radioTitle.toggled.connect(self.title_artist_toggle) self.ui.radioTitle.toggled.connect(self.title_artist_toggle)
self.ui.searchString.textEdited.connect(self.chars_typed) self.ui.searchString.textEdited.connect(self.chars_typed)
self.track: Optional[Tracks] = None self.track: Optional[Tracks] = None
if get_one_track:
self.ui.txtNote.hide()
self.ui.lblNote.hide()
record = Settings.get_int_settings(self.session, "dbdialog_width") record = Settings.get_int_settings(self.session, "dbdialog_width")
width = record.f_int or 800 width = record.f_int or 800
record = Settings.get_int_settings(self.session, "dbdialog_height") record = Settings.get_int_settings(self.session, "dbdialog_height")
height = record.f_int or 600 height = record.f_int or 600
self.resize(width, height) self.resize(width, height)
def __del__(self) -> None:
"""Save dialog size and position"""
# FIXME:
# if record.f_int != self.height():
# ^^^^^^^^^^^^^
# RuntimeError: wrapped C/C++ object of type DbDialog has been deleted
record = Settings.get_int_settings(self.session, "dbdialog_height")
if record.f_int != self.height():
record.update(self.session, {"f_int": self.height()})
record = Settings.get_int_settings(self.session, "dbdialog_width")
if record.f_int != self.width():
record.update(self.session, {"f_int": self.width()})
def add_selected(self) -> None: def add_selected(self) -> None:
"""Handle Add button""" """Handle Add button"""
@ -1989,7 +1968,13 @@ class DbDialog(QDialog):
if not note and not track: if not note and not track:
return return
self.add_track(track, self.ui.txtNote.text()) self.ui.txtNote.clear()
self.select_searchtext()
track_id = None
if track:
track_id = track.id
self.signals.add_track_to_playlist_signal.emit(self.playlist_id, track_id, note)
def add_selected_and_close(self) -> None: def add_selected_and_close(self) -> None:
"""Handle Add and Close button""" """Handle Add and Close button"""
@ -1997,24 +1982,6 @@ class DbDialog(QDialog):
self.add_selected() self.add_selected()
self.accept() self.accept()
def add_track(self, track: Optional[Tracks], note: str) -> None:
"""Add passed track to playlist on screen"""
if self.get_one_track:
self.track = track
self.accept()
return
if track:
self.musicmuster.active_tab().insert_track(
self.session, track, note
)
else:
self.musicmuster.active_tab().insert_header(self.session, note)
self.ui.txtNote.clear()
self.select_searchtext()
def chars_typed(self, s: str) -> None: def chars_typed(self, s: str) -> None:
"""Handle text typed in search box""" """Handle text typed in search box"""
@ -2042,12 +2009,23 @@ class DbDialog(QDialog):
t.setData(Qt.ItemDataRole.UserRole, track) t.setData(Qt.ItemDataRole.UserRole, track)
self.ui.matchList.addItem(t) self.ui.matchList.addItem(t)
def double_click(self, entry: QListWidgetItem) -> None: def closeEvent(self, event: Optional[QEvent]) -> None:
"""Add items that are double-clicked""" """
Override close and save dialog coordinates
"""
track = entry.data(Qt.ItemDataRole.UserRole) if not event:
note = self.ui.txtNote.text() return
self.add_track(track, note)
record = Settings.get_int_settings(self.session, "dbdialog_height")
if record.f_int != self.height():
record.update(self.session, {"f_int": self.height()})
record = Settings.get_int_settings(self.session, "dbdialog_width")
if record.f_int != self.width():
record.update(self.session, {"f_int": self.width()})
event.accept()
def keyPressEvent(self, event): def keyPressEvent(self, event):
""" """
@ -2059,7 +2037,7 @@ class DbDialog(QDialog):
self.ui.matchList.clearSelection() self.ui.matchList.clearSelection()
return return
super(DbDialog, self).keyPressEvent(event) super(TrackSelectDialog, self).keyPressEvent(event)
def select_searchtext(self) -> None: def select_searchtext(self) -> None:
"""Select the searchbox""" """Select the searchbox"""

View File

@ -19,11 +19,10 @@ from PyQt6.QtGui import (
) )
from config import Config from config import Config
from playlists import PlaylistTab
from helpers import ( from helpers import (
file_is_unreadable, file_is_unreadable,
) )
from models import PlaylistRows, Tracks from models import PlaylistRows, Tracks
if TYPE_CHECKING: if TYPE_CHECKING:
@ -98,14 +97,22 @@ class PlaylistModel(QAbstractTableModel):
""" """
def __init__( def __init__(
self, playlist_id: int, signals: "MusicMusterSignals", *args, **kwargs self,
playlist: PlaylistTab,
playlist_id: int,
signals: "MusicMusterSignals",
*args,
**kwargs,
): ):
self.playlist = playlist
self.playlist_id = playlist_id self.playlist_id = playlist_id
self.signals = signals self.signals = signals
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.playlist_rows: dict[int, PlaylistRowData] = {} self.playlist_rows: dict[int, PlaylistRowData] = {}
self.signals.add_track_to_playlist_signal.connect(self.add_track)
with Session() as session: with Session() as session:
self.refresh_data(session) self.refresh_data(session)
@ -114,6 +121,29 @@ class PlaylistModel(QAbstractTableModel):
f"<PlaylistModel: playlist_id={self.playlist_id}, {self.rowCount()} rows>" f"<PlaylistModel: playlist_id={self.playlist_id}, {self.rowCount()} rows>"
) )
def add_track(
self, playlist_id: int, track: Optional[Tracks], 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
row_number = self.playlist.get_selected_row_number()
# Insert track if we have one
if track:
self.insert_track_row(row_number, track, note)
# If we only have a note, add as a header row
elif note:
self.insert_header_row(row_number, note)
else:
# No track, no note, no point
return
def background_role(self, row: int, column: int, prd: PlaylistRowData) -> QBrush: def background_role(self, row: int, column: int, prd: PlaylistRowData) -> QBrush:
"""Return background setting""" """Return background setting"""
@ -299,7 +329,7 @@ class PlaylistModel(QAbstractTableModel):
def insert_header_row(self, row_number: Optional[int], text: str) -> None: def insert_header_row(self, row_number: Optional[int], text: str) -> None:
""" """
Insert a header row. Return row number or None if insertion failed. Insert a header row.
""" """
with Session() as session: with Session() as session:
@ -347,6 +377,24 @@ class PlaylistModel(QAbstractTableModel):
# Insert the new row and return it # Insert the new row and return it
return PlaylistRows(session, self.playlist_id, new_row_number) return PlaylistRows(session, self.playlist_id, new_row_number)
def insert_track_row(
self, row_number: Optional[int], track_id: int, text: Optional[str]
) -> None:
"""
Insert a track row.
"""
with Session() as session:
plr = self._insert_row(session, row_number)
# Update the PlaylistRows object
plr.track_id = track_id
if text:
plr.note = text
# Repopulate self.playlist_rows
self.refresh_data(session)
# Update the display from the new row onwards
self.invalidate_rows(list(range(plr.plr_rownum, len(self.playlist_rows))))
def invalidate_row(self, modified_row: int) -> None: def invalidate_row(self, modified_row: int) -> None:
""" """
Signal to view to refresh invlidated row Signal to view to refresh invlidated row
@ -394,6 +442,7 @@ class PlaylistModel(QAbstractTableModel):
[y for y in range(len(self.playlist_rows)) if y not in row_map.values()], [y for y in range(len(self.playlist_rows)) if y not in row_map.values()],
): ):
# Optimise: only add to map if there is a change # Optimise: only add to map if there is a change
if old_row != new_row:
row_map[old_row] = new_row row_map[old_row] = new_row
# For SQLAlchemy, build a list of dictionaries that map plrid to # For SQLAlchemy, build a list of dictionaries that map plrid to
@ -404,7 +453,7 @@ class PlaylistModel(QAbstractTableModel):
sqla_map.append({"plrid": plrid, "plr_rownum": newrow}) sqla_map.append({"plrid": plrid, "plr_rownum": newrow})
# Update database. Ref: # Update database. Ref:
# https://docs.sqlalchemy.org/en/20/core/sqlelement.html#sqlalchemy.sql.expression.case # https://docs.sqlalchemy.org/en/20/tutorial/data_update.html#the-update-sql-expression-construct
stmt = ( stmt = (
update(PlaylistRows) update(PlaylistRows)
.where( .where(

View File

@ -51,7 +51,7 @@ from log import log
from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours
from playlistmodel import PlaylistModel import playlistmodel
if TYPE_CHECKING: if TYPE_CHECKING:
from musicmuster import Window, MusicMusterSignals from musicmuster import Window, MusicMusterSignals
@ -205,7 +205,7 @@ class PlaylistTab(QTableView):
# self.edit_cell_type: Optional[int] # self.edit_cell_type: Optional[int]
# Load playlist rows # Load playlist rows
self.setModel(PlaylistModel(playlist_id, signals)) self.setModel(playlistmodel.PlaylistModel(self, playlist_id, signals))
self._set_column_widths() self._set_column_widths()
# kae def __repr__(self) -> str: # kae def __repr__(self) -> str:

View File

@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'app/ui/dlg_SearchDatabase.ui' # Form implementation generated from reading ui file 'dlg_TrackSelect.ui'
# #
# Created by: PyQt6 UI code generator 6.5.2 # Created by: PyQt6 UI code generator 6.5.3
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.