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
lineno = frame.lineno
Session = scoped_session(sessionmaker(bind=engine))
log.debug(f"SqlA: session acquired [{hex(id(Session))}]")
log.debug(
f"Session acquisition: {file}:{function}:{lineno} " f"[{hex(id(Session))}]"
f"Session acquired: {file}:{function}:{lineno} " f"[{hex(id(Session))}]"
)
yield Session
log.debug(f" SqlA: session released [{hex(id(Session))}]")
log.debug(f" Session released [{hex(id(Session))}]")
Session.commit()
Session.close()

View File

@ -71,7 +71,7 @@ from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tr
from config import Config
from playlists import PlaylistTab
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.downloadcsv_ui import Ui_DateSelect # 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
"""
enable_escape_signal = pyqtSignal(bool)
set_next_track_signal = pyqtSignal(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:
@ -745,9 +746,7 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session:
# Save the selected PlaylistRows items ready for a later
# paste
self.selected_plrs = self.active_tab().get_selected_playlistrows(
session
)
self.selected_plrs = self.active_tab().get_selected_playlistrows(session)
def debug(self):
"""Invoke debugger"""
@ -928,7 +927,10 @@ class Window(QMainWindow, Ui_MainWindow):
def get_one_track(self, session: scoped_session) -> Optional[Tracks]:
"""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():
return dlg.track
else:
@ -1054,15 +1056,18 @@ class Window(QMainWindow, Ui_MainWindow):
ok = dlg.exec()
if ok:
model.insert_header_row(
self.active_tab().get_selected_row_number(),
dlg.textValue()
self.active_tab().get_selected_row_number(), dlg.textValue()
)
def insert_track(self) -> None:
"""Show dialog box to select and add track from database"""
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()
def load_last_playlists(self) -> None:
@ -1159,9 +1164,7 @@ class Window(QMainWindow, Ui_MainWindow):
"""
with Session() as session:
selected_plrs = self.active_tab().get_selected_playlistrows(
session
)
selected_plrs = self.active_tab().get_selected_playlistrows(session)
if not selected_plrs:
return
@ -1914,66 +1917,42 @@ class CartDialog(QDialog):
self.ui.lblPath.setText(self.path)
class DbDialog(QDialog):
class TrackSelectDialog(QDialog):
"""Select track from database"""
def __init__(
self,
musicmuster: Window,
session: scoped_session,
get_one_track: bool = False,
signals: MusicMusterSignals,
playlist_id: int,
*args,
**kwargs,
) -> None:
"""
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)
self.musicmuster = musicmuster
self.session = session
self.get_one_track = get_one_track
self.signals = signals
self.playlist_id = playlist_id
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.ui.btnAdd.clicked.connect(self.add_selected)
self.ui.btnAddClose.clicked.connect(self.add_selected_and_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.radioTitle.toggled.connect(self.title_artist_toggle)
self.ui.searchString.textEdited.connect(self.chars_typed)
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")
width = record.f_int or 800
record = Settings.get_int_settings(self.session, "dbdialog_height")
height = record.f_int or 600
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:
"""Handle Add button"""
@ -1989,7 +1968,13 @@ class DbDialog(QDialog):
if not note and not track:
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:
"""Handle Add and Close button"""
@ -1997,24 +1982,6 @@ class DbDialog(QDialog):
self.add_selected()
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:
"""Handle text typed in search box"""
@ -2042,12 +2009,23 @@ class DbDialog(QDialog):
t.setData(Qt.ItemDataRole.UserRole, track)
self.ui.matchList.addItem(t)
def double_click(self, entry: QListWidgetItem) -> None:
"""Add items that are double-clicked"""
def closeEvent(self, event: Optional[QEvent]) -> None:
"""
Override close and save dialog coordinates
"""
track = entry.data(Qt.ItemDataRole.UserRole)
note = self.ui.txtNote.text()
self.add_track(track, note)
if not event:
return
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):
"""
@ -2059,7 +2037,7 @@ class DbDialog(QDialog):
self.ui.matchList.clearSelection()
return
super(DbDialog, self).keyPressEvent(event)
super(TrackSelectDialog, self).keyPressEvent(event)
def select_searchtext(self) -> None:
"""Select the searchbox"""

View File

@ -19,11 +19,10 @@ from PyQt6.QtGui import (
)
from config import Config
from playlists import PlaylistTab
from helpers import (
file_is_unreadable,
)
from models import PlaylistRows, Tracks
if TYPE_CHECKING:
@ -98,14 +97,22 @@ class PlaylistModel(QAbstractTableModel):
"""
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.signals = signals
super().__init__(*args, **kwargs)
self.playlist_rows: dict[int, PlaylistRowData] = {}
self.signals.add_track_to_playlist_signal.connect(self.add_track)
with Session() as session:
self.refresh_data(session)
@ -114,6 +121,29 @@ class PlaylistModel(QAbstractTableModel):
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:
"""Return background setting"""
@ -299,7 +329,7 @@ class PlaylistModel(QAbstractTableModel):
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:
@ -347,6 +377,24 @@ class PlaylistModel(QAbstractTableModel):
# Insert the new row and return it
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:
"""
Signal to view to refresh invlidated row
@ -394,7 +442,8 @@ class PlaylistModel(QAbstractTableModel):
[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
row_map[old_row] = new_row
if old_row != new_row:
row_map[old_row] = new_row
# For SQLAlchemy, build a list of dictionaries that map plrid to
# new row number:
@ -404,7 +453,7 @@ class PlaylistModel(QAbstractTableModel):
sqla_map.append({"plrid": plrid, "plr_rownum": newrow})
# 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 = (
update(PlaylistRows)
.where(

View File

@ -51,7 +51,7 @@ from log import log
from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours
from playlistmodel import PlaylistModel
import playlistmodel
if TYPE_CHECKING:
from musicmuster import Window, MusicMusterSignals
@ -205,7 +205,7 @@ class PlaylistTab(QTableView):
# self.edit_cell_type: Optional[int]
# Load playlist rows
self.setModel(PlaylistModel(playlist_id, signals))
self.setModel(playlistmodel.PlaylistModel(self, playlist_id, signals))
self._set_column_widths()
# 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
# run again. Do not edit this file unless you know what you are doing.