WIP V3: insert track works
This commit is contained in:
parent
e4b986fd2e
commit
3557d22c54
@ -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()
|
||||||
|
|||||||
@ -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"""
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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.
|
||||||
Loading…
Reference in New Issue
Block a user