Compare commits
8 Commits
eae8870d4d
...
262ab202fc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
262ab202fc | ||
|
|
4f4408400f | ||
|
|
f4a374f68c | ||
|
|
77774dc403 | ||
|
|
8f2ab98be0 | ||
|
|
199f0e27fa | ||
|
|
e37f62fe87 | ||
|
|
be7071aae0 |
@ -90,7 +90,7 @@ class Config(object):
|
|||||||
ROWS_FROM_ZERO = True
|
ROWS_FROM_ZERO = True
|
||||||
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||||
SCROLL_TOP_MARGIN = 3
|
SCROLL_TOP_MARGIN = 3
|
||||||
START_GAP_WARNING_THRESHOLD = 500
|
START_GAP_WARNING_THRESHOLD = 300
|
||||||
TEXT_NO_TRACK_NO_NOTE = "[Section header]"
|
TEXT_NO_TRACK_NO_NOTE = "[Section header]"
|
||||||
TOD_TIME_FORMAT = "%H:%M:%S"
|
TOD_TIME_FORMAT = "%H:%M:%S"
|
||||||
TRACK_TIME_FORMAT = "%H:%M:%S"
|
TRACK_TIME_FORMAT = "%H:%M:%S"
|
||||||
|
|||||||
@ -25,7 +25,6 @@ from sqlalchemy import (
|
|||||||
from sqlalchemy.orm import (
|
from sqlalchemy.orm import (
|
||||||
DeclarativeBase,
|
DeclarativeBase,
|
||||||
joinedload,
|
joinedload,
|
||||||
lazyload,
|
|
||||||
Mapped,
|
Mapped,
|
||||||
mapped_column,
|
mapped_column,
|
||||||
relationship,
|
relationship,
|
||||||
@ -217,7 +216,8 @@ class Playlists(Base):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
name: Mapped[str] = mapped_column(String(32), unique=True)
|
name: Mapped[str] = mapped_column(String(32), unique=True)
|
||||||
last_used: Mapped[Optional[datetime]] = mapped_column(DateTime, default=None)
|
last_used: Mapped[Optional[datetime]] = mapped_column(DateTime, default=None)
|
||||||
tab: Mapped[Optional[int]] = mapped_column(default=None, unique=True)
|
tab: Mapped[Optional[int]] = mapped_column(default=None)
|
||||||
|
open: Mapped[bool] = mapped_column(default=False)
|
||||||
is_template: Mapped[bool] = mapped_column(default=False)
|
is_template: Mapped[bool] = mapped_column(default=False)
|
||||||
deleted: Mapped[bool] = mapped_column(default=False)
|
deleted: Mapped[bool] = mapped_column(default=False)
|
||||||
rows: Mapped[List["PlaylistRows"]] = relationship(
|
rows: Mapped[List["PlaylistRows"]] = relationship(
|
||||||
@ -230,7 +230,7 @@ class Playlists(Base):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Playlists(id={self.id}, name={self.name}, "
|
f"<Playlists(id={self.id}, name={self.name}, "
|
||||||
f"is_templatee={self.is_template}>"
|
f"is_templatee={self.is_template}, open={self.open}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, session: scoped_session, name: str):
|
def __init__(self, session: scoped_session, name: str):
|
||||||
@ -238,19 +238,10 @@ class Playlists(Base):
|
|||||||
session.add(self)
|
session.add(self)
|
||||||
session.flush()
|
session.flush()
|
||||||
|
|
||||||
def close(self, session: scoped_session) -> None:
|
def close(self) -> None:
|
||||||
"""Mark playlist as unloaded"""
|
"""Mark playlist as unloaded"""
|
||||||
|
|
||||||
closed_idx = self.tab
|
self.open = False
|
||||||
self.tab = None
|
|
||||||
|
|
||||||
# Closing this tab will mean all higher-number tabs have moved
|
|
||||||
# down by one
|
|
||||||
session.execute(
|
|
||||||
update(Playlists)
|
|
||||||
.where(Playlists.tab > closed_idx)
|
|
||||||
.values(tab=Playlists.tab - 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_playlist_from_template(
|
def create_playlist_from_template(
|
||||||
@ -283,7 +274,7 @@ class Playlists(Base):
|
|||||||
return session.scalars(
|
return session.scalars(
|
||||||
select(cls)
|
select(cls)
|
||||||
.filter(cls.is_template.is_(False))
|
.filter(cls.is_template.is_(False))
|
||||||
.order_by(cls.tab.desc(), cls.last_used.desc())
|
.order_by(cls.last_used.desc())
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -301,7 +292,7 @@ class Playlists(Base):
|
|||||||
return session.scalars(
|
return session.scalars(
|
||||||
select(cls)
|
select(cls)
|
||||||
.filter(
|
.filter(
|
||||||
cls.tab.is_(None),
|
cls.open.is_(False),
|
||||||
cls.is_template.is_(False),
|
cls.is_template.is_(False),
|
||||||
cls.deleted.is_(False),
|
cls.deleted.is_(False),
|
||||||
)
|
)
|
||||||
@ -311,32 +302,29 @@ class Playlists(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_open(cls, session: scoped_session) -> Sequence[Optional["Playlists"]]:
|
def get_open(cls, session: scoped_session) -> Sequence[Optional["Playlists"]]:
|
||||||
"""
|
"""
|
||||||
Return a list of loaded playlists ordered by tab order.
|
Return a list of loaded playlists ordered by tab.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return session.scalars(
|
return session.scalars(
|
||||||
select(cls).where(cls.tab.is_not(None)).order_by(cls.tab)
|
select(cls).where(cls.open.is_(True))
|
||||||
|
.order_by(cls.tab)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
def mark_open(self, session: scoped_session, tab_index: int) -> None:
|
def mark_open(self) -> None:
|
||||||
"""Mark playlist as loaded and used now"""
|
"""Mark playlist as loaded and used now"""
|
||||||
|
|
||||||
self.tab = tab_index
|
self.open = True
|
||||||
self.last_used = datetime.now()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def move_tab(session: scoped_session, frm: int, to: int) -> None:
|
def name_is_available(session: scoped_session, name: str) -> bool:
|
||||||
"""Move tabs"""
|
"""
|
||||||
|
Return True if no playlist of this name exists else false.
|
||||||
|
"""
|
||||||
|
|
||||||
row_frm = session.execute(select(Playlists).filter_by(tab=frm)).scalar_one()
|
return session.execute(
|
||||||
|
select(Playlists)
|
||||||
row_to = session.execute(select(Playlists).filter_by(tab=to)).scalar_one()
|
.where(Playlists.name == name)
|
||||||
|
).first() is None
|
||||||
row_frm.tab = None
|
|
||||||
row_to.tab = None
|
|
||||||
session.commit()
|
|
||||||
row_to.tab = frm
|
|
||||||
row_frm.tab = to
|
|
||||||
|
|
||||||
def rename(self, session: scoped_session, new_name: str) -> None:
|
def rename(self, session: scoped_session, new_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -489,17 +477,17 @@ class PlaylistRows(Base):
|
|||||||
session.flush()
|
session.flush()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_rows(
|
def delete_row(
|
||||||
session: scoped_session, playlist_id: int, row_numbers: List[int]
|
session: scoped_session, playlist_id: int, row_number: int
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Delete passed rows in given playlist.
|
Delete passed row in given playlist.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
session.execute(
|
session.execute(
|
||||||
delete(PlaylistRows).where(
|
delete(PlaylistRows).where(
|
||||||
PlaylistRows.playlist_id == playlist_id,
|
PlaylistRows.playlist_id == playlist_id,
|
||||||
PlaylistRows.plr_rownum.in_(row_numbers),
|
PlaylistRows.plr_rownum == row_number,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -684,7 +672,6 @@ class Settings(Base):
|
|||||||
f_string: Mapped[Optional[str]] = mapped_column(String(128), default=None)
|
f_string: Mapped[Optional[str]] = mapped_column(String(128), default=None)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
value = self.f_datetime or self.f_int or self.f_string
|
|
||||||
return (
|
return (
|
||||||
f"<Settings(id={self.id}, name={self.name}, "
|
f"<Settings(id={self.id}, name={self.name}, "
|
||||||
f"f_datetime={self.f_datetime}, f_int={self.f_int}, f_string={self.f_string}>"
|
f"f_datetime={self.f_datetime}, f_int={self.f_int}, f_string={self.f_string}>"
|
||||||
|
|||||||
@ -442,6 +442,14 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if record.f_int != splitter_bottom:
|
if record.f_int != splitter_bottom:
|
||||||
record.update(session, {"f_int": splitter_bottom})
|
record.update(session, {"f_int": splitter_bottom})
|
||||||
|
|
||||||
|
# Save tab number of open playlists
|
||||||
|
for idx in range(self.tabPlaylist.count()):
|
||||||
|
playlist_id = self.tabPlaylist.widget(idx).playlist_id
|
||||||
|
playlist = session.get(Playlists, playlist_id)
|
||||||
|
if playlist:
|
||||||
|
playlist.tab = idx
|
||||||
|
session.flush()
|
||||||
|
|
||||||
# Save current tab
|
# Save current tab
|
||||||
record = settings["active_tab"]
|
record = settings["active_tab"]
|
||||||
record.update(session, {"f_int": self.tabPlaylist.currentIndex()})
|
record.update(session, {"f_int": self.tabPlaylist.currentIndex()})
|
||||||
@ -463,29 +471,25 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
Return True if tab closed else False.
|
Return True if tab closed else False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return False
|
# Don't close current track playlist
|
||||||
# TODO Reimplement without ussing self.current_track.playlist_tab
|
current_track_playlist_id = track_sequence.now.playlist_id
|
||||||
# # Don't close current track playlist
|
closing_tab_playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
|
||||||
# if self.tabPlaylist.widget(tab_index) == (self.current_track.playlist_tab):
|
if current_track_playlist_id:
|
||||||
# self.statusbar.showMessage("Can't close current track playlist", 5000)
|
if closing_tab_playlist_id == current_track_playlist_id:
|
||||||
# return False
|
self.statusbar.showMessage("Can't close current track playlist", 5000)
|
||||||
|
return False
|
||||||
|
|
||||||
# # Attempt to close next track playlist
|
# Record playlist as closed and update remaining playlist tabs
|
||||||
# if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
|
with Session() as session:
|
||||||
# self.next_track.playlist_tab.clear_next()
|
playlist = session.get(Playlists, closing_tab_playlist_id)
|
||||||
|
if playlist:
|
||||||
|
playlist.close()
|
||||||
|
|
||||||
# # Record playlist as closed and update remaining playlist tabs
|
# Close playlist and remove tab
|
||||||
# with Session() as session:
|
self.tabPlaylist.widget(tab_index).close()
|
||||||
# playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
|
self.tabPlaylist.removeTab(tab_index)
|
||||||
# playlist = session.get(Playlists, playlist_id)
|
|
||||||
# if playlist:
|
|
||||||
# playlist.close(session)
|
|
||||||
|
|
||||||
# # Close playlist and remove tab
|
return True
|
||||||
# self.tabPlaylist.widget(tab_index).close()
|
|
||||||
# self.tabPlaylist.removeTab(tab_index)
|
|
||||||
|
|
||||||
# return True
|
|
||||||
|
|
||||||
def connect_signals_slots(self) -> None:
|
def connect_signals_slots(self) -> None:
|
||||||
self.action_About.triggered.connect(self.about)
|
self.action_About.triggered.connect(self.about)
|
||||||
@ -525,7 +529,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
lambda: self.tabPlaylist.currentWidget().lookup_row_in_wikipedia()
|
lambda: self.tabPlaylist.currentWidget().lookup_row_in_wikipedia()
|
||||||
)
|
)
|
||||||
self.actionSearch.triggered.connect(self.search_playlist)
|
self.actionSearch.triggered.connect(self.search_playlist)
|
||||||
self.actionSelect_duplicate_rows.triggered.connect(self.select_duplicate_rows)
|
self.actionSelect_duplicate_rows.triggered.connect(
|
||||||
|
lambda: self.active_tab().select_duplicate_rows()
|
||||||
|
)
|
||||||
self.actionSelect_next_track.triggered.connect(self.select_next_row)
|
self.actionSelect_next_track.triggered.connect(self.select_next_row)
|
||||||
self.actionSelect_previous_track.triggered.connect(self.select_previous_row)
|
self.actionSelect_previous_track.triggered.connect(self.select_previous_row)
|
||||||
self.actionMoveUnplayed.triggered.connect(self.move_unplayed)
|
self.actionMoveUnplayed.triggered.connect(self.move_unplayed)
|
||||||
@ -539,10 +545,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.btnStop.clicked.connect(self.stop)
|
self.btnStop.clicked.connect(self.stop)
|
||||||
self.hdrCurrentTrack.clicked.connect(self.show_current)
|
self.hdrCurrentTrack.clicked.connect(self.show_current)
|
||||||
self.hdrNextTrack.clicked.connect(self.show_next)
|
self.hdrNextTrack.clicked.connect(self.show_next)
|
||||||
self.tabPlaylist.currentChanged.connect(self.tab_change)
|
|
||||||
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
|
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
|
||||||
self.tabBar = self.tabPlaylist.tabBar()
|
self.tabBar = self.tabPlaylist.tabBar()
|
||||||
self.tabBar.tabMoved.connect(self.move_tab)
|
|
||||||
self.txtSearch.returnPressed.connect(self.search_playlist_return)
|
self.txtSearch.returnPressed.connect(self.search_playlist_return)
|
||||||
|
|
||||||
self.signals.enable_escape_signal.connect(self.enable_escape)
|
self.signals.enable_escape_signal.connect(self.enable_escape)
|
||||||
@ -557,12 +561,16 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
) -> Optional[Playlists]:
|
) -> Optional[Playlists]:
|
||||||
"""Create new playlist"""
|
"""Create new playlist"""
|
||||||
|
|
||||||
playlist_name = self.solicit_playlist_name()
|
playlist_name = self.solicit_playlist_name(session)
|
||||||
if not playlist_name:
|
if not playlist_name:
|
||||||
return None
|
return None
|
||||||
playlist = Playlists(session, playlist_name)
|
playlist = Playlists(session, playlist_name)
|
||||||
|
|
||||||
return playlist
|
if playlist:
|
||||||
|
playlist.mark_open()
|
||||||
|
return playlist
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def create_and_show_playlist(self) -> None:
|
def create_and_show_playlist(self) -> None:
|
||||||
"""Create new playlist and display it"""
|
"""Create new playlist and display it"""
|
||||||
@ -570,9 +578,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
with Session() as session:
|
with Session() as session:
|
||||||
playlist = self.create_playlist(session)
|
playlist = self.create_playlist(session)
|
||||||
if playlist:
|
if playlist:
|
||||||
self.create_playlist_tab(session, playlist)
|
self.create_playlist_tab(playlist)
|
||||||
|
|
||||||
def create_playlist_tab(self, session: scoped_session, playlist: Playlists) -> int:
|
def create_playlist_tab(self, playlist: Playlists) -> int:
|
||||||
"""
|
"""
|
||||||
Take the passed playlist database object, create a playlist tab and
|
Take the passed playlist database object, create a playlist tab and
|
||||||
add tab to display. Return index number of tab.
|
add tab to display. Return index number of tab.
|
||||||
@ -851,8 +859,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
dlg.resize(500, 100)
|
dlg.resize(500, 100)
|
||||||
ok = dlg.exec()
|
ok = dlg.exec()
|
||||||
if ok:
|
if ok:
|
||||||
model.insert_header_row(
|
model.insert_row(
|
||||||
self.active_tab().get_selected_row_number(), dlg.textValue()
|
proposed_row_number=self.active_tab().get_selected_row_number(),
|
||||||
|
note=dlg.textValue(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def insert_track(self) -> None:
|
def insert_track(self) -> None:
|
||||||
@ -872,7 +881,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
with Session() as session:
|
with Session() as session:
|
||||||
for playlist in Playlists.get_open(session):
|
for playlist in Playlists.get_open(session):
|
||||||
if playlist:
|
if playlist:
|
||||||
_ = self.create_playlist_tab(session, playlist)
|
_ = self.create_playlist_tab(playlist)
|
||||||
# Set active tab
|
# Set active tab
|
||||||
record = Settings.get_int_settings(session, "active_tab")
|
record = Settings.get_int_settings(session, "active_tab")
|
||||||
if record.f_int and record.f_int >= 0:
|
if record.f_int and record.f_int >= 0:
|
||||||
@ -966,12 +975,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
self.move_playlist_rows(session, selected_plrs)
|
self.move_playlist_rows(session, selected_plrs)
|
||||||
|
|
||||||
def move_tab(self, frm: int, to: int) -> None:
|
|
||||||
"""Handle tabs being moved"""
|
|
||||||
|
|
||||||
with Session() as session:
|
|
||||||
Playlists.move_tab(session, frm, to)
|
|
||||||
|
|
||||||
def move_unplayed(self) -> None:
|
def move_unplayed(self) -> None:
|
||||||
"""
|
"""
|
||||||
Move unplayed rows to another playlist
|
Move unplayed rows to another playlist
|
||||||
@ -994,18 +997,22 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
dlg.exec()
|
dlg.exec()
|
||||||
template = dlg.playlist
|
template = dlg.playlist
|
||||||
if template:
|
if template:
|
||||||
playlist_name = self.solicit_playlist_name()
|
playlist_name = self.solicit_playlist_name(session)
|
||||||
if not playlist_name:
|
if not playlist_name:
|
||||||
return
|
return
|
||||||
playlist = Playlists.create_playlist_from_template(
|
playlist = Playlists.create_playlist_from_template(
|
||||||
session, template, playlist_name
|
session, template, playlist_name
|
||||||
)
|
)
|
||||||
if not playlist:
|
|
||||||
return
|
|
||||||
tab_index = self.create_playlist_tab(session, playlist)
|
|
||||||
playlist.mark_open(session, tab_index)
|
|
||||||
|
|
||||||
def open_playlist(self):
|
# Need to ensure that the new playlist is committed to
|
||||||
|
# the database before it is opened by the model.
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
if playlist:
|
||||||
|
playlist.mark_open()
|
||||||
|
self.create_playlist_tab(playlist)
|
||||||
|
|
||||||
|
def open_playlist(self) -> None:
|
||||||
"""Open existing playlist"""
|
"""Open existing playlist"""
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
@ -1014,8 +1021,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
dlg.exec()
|
dlg.exec()
|
||||||
playlist = dlg.playlist
|
playlist = dlg.playlist
|
||||||
if playlist:
|
if playlist:
|
||||||
tab_index = self.create_playlist_tab(session, playlist)
|
self.create_playlist_tab(playlist)
|
||||||
playlist.mark_open(session, tab_index)
|
playlist.mark_open()
|
||||||
|
|
||||||
def paste_rows(self) -> None:
|
def paste_rows(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1291,35 +1298,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.active_tab().set_search(self.txtSearch.text())
|
self.active_tab().set_search(self.txtSearch.text())
|
||||||
self.enable_play_next_controls()
|
self.enable_play_next_controls()
|
||||||
|
|
||||||
def select_duplicate_rows(self) -> None:
|
|
||||||
"""
|
|
||||||
Select the last of any rows with duplicate tracks in current playlist.
|
|
||||||
This allows the selection to typically come towards the end of the playlist away
|
|
||||||
from any show specific sections.
|
|
||||||
If there a track is selected on three or more rows, only the last one is selected.
|
|
||||||
"""
|
|
||||||
|
|
||||||
visible_playlist_id = self.active_tab().playlist_id
|
|
||||||
# Get row number of duplicate rows
|
|
||||||
sql = text(
|
|
||||||
f"""
|
|
||||||
SELECT max(plr_rownum)
|
|
||||||
FROM playlist_rows
|
|
||||||
WHERE playlist_id = {visible_playlist_id}
|
|
||||||
AND track_id != 0
|
|
||||||
GROUP BY track_id
|
|
||||||
HAVING count(id) > 1
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
with Session() as session:
|
|
||||||
row_numbers = [int(a) for a in session.execute(sql).scalars().all()]
|
|
||||||
if row_numbers:
|
|
||||||
self.active_tab().select_rows(row_numbers)
|
|
||||||
self.statusbar.showMessage(
|
|
||||||
f"{len(row_numbers)} duplicate rows selected", 10000
|
|
||||||
)
|
|
||||||
|
|
||||||
def select_next_row(self) -> None:
|
def select_next_row(self) -> None:
|
||||||
"""Select next or first row in playlist"""
|
"""Select next or first row in playlist"""
|
||||||
|
|
||||||
@ -1386,20 +1364,32 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
|
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
|
||||||
# self.tabPlaylist.currentWidget().scroll_next_to_top()
|
# self.tabPlaylist.currentWidget().scroll_next_to_top()
|
||||||
|
|
||||||
def solicit_playlist_name(self, default: Optional[str] = "") -> Optional[str]:
|
def solicit_playlist_name(
|
||||||
"""Get name of playlist from user"""
|
self, session: scoped_session, default: str = ""
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""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("Playlist name:")
|
||||||
if default:
|
while True:
|
||||||
dlg.setTextValue(default)
|
if default:
|
||||||
dlg.resize(500, 100)
|
dlg.setTextValue(default)
|
||||||
ok = dlg.exec()
|
dlg.resize(500, 100)
|
||||||
if ok:
|
ok = dlg.exec()
|
||||||
return dlg.textValue()
|
if ok:
|
||||||
else:
|
proposed_name = dlg.textValue()
|
||||||
return None
|
if Playlists.name_is_available(session, proposed_name):
|
||||||
|
return proposed_name
|
||||||
|
else:
|
||||||
|
helpers.show_warning(
|
||||||
|
self,
|
||||||
|
"Name in use",
|
||||||
|
f"There's already a playlist called '{proposed_name}'",
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Stop playing immediately"""
|
"""Stop playing immediately"""
|
||||||
@ -1463,15 +1453,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Enable controls
|
# Enable controls
|
||||||
self.enable_play_next_controls()
|
self.enable_play_next_controls()
|
||||||
|
|
||||||
def tab_change(self):
|
|
||||||
"""Called when active tab changed"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.tabPlaylist.currentWidget().tab_visible()
|
|
||||||
except AttributeError:
|
|
||||||
# May also be called when last tab is closed
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_next_plr_id(
|
def set_next_plr_id(
|
||||||
self, next_plr_id: Optional[int], playlist_tab: PlaylistTab
|
self, next_plr_id: Optional[int], playlist_tab: PlaylistTab
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@ -124,6 +124,9 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
self.signals.add_track_to_playlist_signal.connect(self.add_track)
|
self.signals.add_track_to_playlist_signal.connect(self.add_track)
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
|
# Ensure row numbers in playlist are contiguous
|
||||||
|
PlaylistRows.fixup_rownumbers(session, playlist_id)
|
||||||
|
# Populate self.playlist_rows
|
||||||
self.refresh_data(session)
|
self.refresh_data(session)
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
|
|
||||||
@ -147,15 +150,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
if playlist_id != self.playlist_id:
|
if playlist_id != self.playlist_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Insert track if we have one
|
self.insert_row(proposed_row_number=new_row_number, track_id=track_id, note=note)
|
||||||
if track_id:
|
|
||||||
self.insert_track_row(new_row_number, track_id, note)
|
|
||||||
# If we only have a note, add as a header row
|
|
||||||
elif note:
|
|
||||||
self.insert_header_row(new_row_number, note)
|
|
||||||
else:
|
|
||||||
# No track, no note, no point
|
|
||||||
return
|
|
||||||
|
|
||||||
def add_track_to_header(
|
def add_track_to_header(
|
||||||
self,
|
self,
|
||||||
@ -345,10 +340,17 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
def delete_rows(self, row_numbers: List[int]) -> None:
|
def delete_rows(self, row_numbers: List[int]) -> None:
|
||||||
"""
|
"""
|
||||||
Delete passed rows from model
|
Delete passed rows from model
|
||||||
|
|
||||||
|
Need to delete them in contiguous groups wrapped in beginRemoveRows / endRemoveRows
|
||||||
|
calls. To keep it simple, if inefficient, delete rows one by one.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
PlaylistRows.delete_rows(session, self.playlist_id, row_numbers)
|
for row_number in row_numbers:
|
||||||
|
super().beginRemoveRows(QModelIndex(), row_number, row_number)
|
||||||
|
PlaylistRows.delete_row(session, self.playlist_id, row_number)
|
||||||
|
super().endRemoveRows()
|
||||||
|
|
||||||
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
|
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
|
||||||
self.refresh_data(session)
|
self.refresh_data(session)
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
@ -397,6 +399,26 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
|
def get_duplicate_rows(self) -> List[int]:
|
||||||
|
"""
|
||||||
|
Return a list of duplicate rows. If track appears in rows 2, 3 and 4, return [3, 4]
|
||||||
|
(ie, ignore the first, not-yet-duplicate, track).
|
||||||
|
"""
|
||||||
|
|
||||||
|
found = []
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for i in range(len(self.playlist_rows)):
|
||||||
|
track_id = self.playlist_rows[i].track_id
|
||||||
|
if track_id is None:
|
||||||
|
continue
|
||||||
|
if track_id in found:
|
||||||
|
result.append(i)
|
||||||
|
else:
|
||||||
|
found.append(track_id)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
|
def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
|
||||||
"""
|
"""
|
||||||
Return text for editing
|
Return text for editing
|
||||||
@ -448,6 +470,25 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return QVariant(boldfont)
|
return QVariant(boldfont)
|
||||||
|
|
||||||
|
def _get_new_row_number(self, proposed_row_number: Optional[int]) -> int:
|
||||||
|
"""
|
||||||
|
Sanitises proposed new row number.
|
||||||
|
|
||||||
|
If proposed_row_number given, ensure it is valid.
|
||||||
|
If not given, return row number to add to end of model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if proposed_row_number is None or proposed_row_number > len(self.playlist_rows):
|
||||||
|
# We are adding to the end of the list
|
||||||
|
new_row_number = len(self.playlist_rows)
|
||||||
|
elif proposed_row_number < 0:
|
||||||
|
# Add to start of list
|
||||||
|
new_row_number = 0
|
||||||
|
else:
|
||||||
|
new_row_number = proposed_row_number
|
||||||
|
|
||||||
|
return new_row_number
|
||||||
|
|
||||||
def get_row_track_path(self, row_number: int) -> str:
|
def get_row_track_path(self, row_number: int) -> str:
|
||||||
"""
|
"""
|
||||||
Return path of track associated with row or empty string if no track associated
|
Return path of track associated with row or empty string if no track associated
|
||||||
@ -466,6 +507,13 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return duration
|
return duration
|
||||||
|
|
||||||
|
def get_row_info(self, row_number: int) -> PlaylistRowData:
|
||||||
|
"""
|
||||||
|
Return info about passed row
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.playlist_rows[row_number]
|
||||||
|
|
||||||
def headerData(
|
def headerData(
|
||||||
self,
|
self,
|
||||||
section: int,
|
section: int,
|
||||||
@ -600,66 +648,33 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return self.playlist_rows[row_number].played
|
return self.playlist_rows[row_number].played
|
||||||
|
|
||||||
def insert_header_row(self, row_number: Optional[int], text: str) -> None:
|
def insert_row(
|
||||||
"""
|
self,
|
||||||
Insert a header row.
|
proposed_row_number: Optional[int],
|
||||||
"""
|
track_id: Optional[int] = None,
|
||||||
|
note: Optional[str] = None,
|
||||||
with Session() as session:
|
|
||||||
plr = self._insert_row(session, row_number)
|
|
||||||
# Update the PlaylistRows object
|
|
||||||
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 _insert_row(
|
|
||||||
self, session: scoped_session, row_number: Optional[int]
|
|
||||||
) -> PlaylistRows:
|
) -> PlaylistRows:
|
||||||
"""
|
|
||||||
Insert a row in the database.
|
|
||||||
|
|
||||||
If row_number is greater than length of list plus 1, or if row
|
|
||||||
number is None, put row at end of list.
|
|
||||||
|
|
||||||
Move existing rows to make space if ncessary.
|
|
||||||
|
|
||||||
Return the new PlaylistRows object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if row_number is None or row_number > len(self.playlist_rows):
|
|
||||||
# We are adding to the end of the list so we can optimise
|
|
||||||
new_row_number = len(self.playlist_rows)
|
|
||||||
return PlaylistRows(session, self.playlist_id, new_row_number)
|
|
||||||
elif row_number < 0:
|
|
||||||
raise ValueError(
|
|
||||||
f"playlistmodel._insert_row, invalid row number ({row_number})"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
new_row_number = row_number
|
|
||||||
|
|
||||||
# Insert the new row and return it
|
|
||||||
return PlaylistRows.insert_row(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.
|
Insert a track row.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
new_row_number = self._get_new_row_number(proposed_row_number)
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
plr = self._insert_row(session, row_number)
|
super().beginInsertRows(QModelIndex(), new_row_number, new_row_number)
|
||||||
# Update the PlaylistRows object
|
plr = PlaylistRows.insert_row(session, self.playlist_id, new_row_number)
|
||||||
|
|
||||||
plr.track_id = track_id
|
plr.track_id = track_id
|
||||||
if text:
|
if note:
|
||||||
plr.note = text
|
plr.note = note
|
||||||
# Repopulate self.playlist_rows
|
|
||||||
self.refresh_data(session)
|
self.refresh_data(session)
|
||||||
# Update the display from the new row onwards
|
super().endInsertRows()
|
||||||
|
|
||||||
self.invalidate_rows(list(range(plr.plr_rownum, len(self.playlist_rows))))
|
self.invalidate_rows(list(range(plr.plr_rownum, len(self.playlist_rows))))
|
||||||
|
|
||||||
|
return plr
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@ -205,7 +205,26 @@ class PlaylistTab(QTableView):
|
|||||||
self.setModel(PlaylistModel(playlist_id))
|
self.setModel(PlaylistModel(playlist_id))
|
||||||
self._set_column_widths()
|
self._set_column_widths()
|
||||||
|
|
||||||
# ########## Events other than cell editing ##########
|
def closeEditor(
|
||||||
|
self, editor: QWidget | None, hint: QAbstractItemDelegate.EndEditHint
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Override closeEditor to enable play controls and update display.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.musicmuster.enable_play_next_controls()
|
||||||
|
self.musicmuster.actionSetNext.setEnabled(True)
|
||||||
|
self.musicmuster.action_Clear_selection.setEnabled(True)
|
||||||
|
|
||||||
|
super(PlaylistTab, self).closeEditor(editor, hint)
|
||||||
|
|
||||||
|
# Optimise row heights after increasing row height for editing
|
||||||
|
self.resizeRowsToContents()
|
||||||
|
|
||||||
|
# Update start times in case a start time in a note has been
|
||||||
|
# edited
|
||||||
|
model = cast(PlaylistModel, self.model())
|
||||||
|
model.update_track_times()
|
||||||
|
|
||||||
def dropEvent(self, event):
|
def dropEvent(self, event):
|
||||||
if event.source() is not self or (
|
if event.source() is not self or (
|
||||||
@ -230,6 +249,25 @@ class PlaylistTab(QTableView):
|
|||||||
|
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
def edit(
|
||||||
|
self,
|
||||||
|
index: QModelIndex,
|
||||||
|
trigger: QAbstractItemView.EditTrigger,
|
||||||
|
event: Optional[QEvent],
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Override PySide2.QAbstractItemView.edit to catch when editing starts
|
||||||
|
|
||||||
|
Editing only ever starts with a double click on a cell
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 'result' will only be true on double-click
|
||||||
|
result = super().edit(index, trigger, event)
|
||||||
|
if result:
|
||||||
|
self.musicmuster.disable_play_next_controls()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def _add_context_menu(
|
def _add_context_menu(
|
||||||
self,
|
self,
|
||||||
text: str,
|
text: str,
|
||||||
@ -538,9 +576,9 @@ class PlaylistTab(QTableView):
|
|||||||
parent_menu=sort_menu,
|
parent_menu=sort_menu,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Info TODO
|
# Info
|
||||||
if track_row:
|
if track_row:
|
||||||
self._add_context_menu("Info", lambda: print("Track info"))
|
self._add_context_menu("Info", lambda: self._info_row(row_number))
|
||||||
|
|
||||||
# Track path TODO
|
# Track path TODO
|
||||||
if track_row:
|
if track_row:
|
||||||
@ -644,25 +682,23 @@ class PlaylistTab(QTableView):
|
|||||||
# items in that row selected)
|
# items in that row selected)
|
||||||
return sorted(list(set([a.row() for a in self.selectedIndexes()])))
|
return sorted(list(set([a.row() for a in self.selectedIndexes()])))
|
||||||
|
|
||||||
def _info_row(self, track_id: int) -> None:
|
def _info_row(self, row_number: int) -> None:
|
||||||
"""Display popup with info re row"""
|
"""Display popup with info re row"""
|
||||||
|
|
||||||
with Session() as session:
|
model = cast(PlaylistModel, self.model())
|
||||||
track = session.get(Tracks, track_id)
|
prd = model.get_row_info(row_number)
|
||||||
if track:
|
if prd:
|
||||||
txt = (
|
txt = (
|
||||||
f"Title: {track.title}\n"
|
f"Title: {prd.title}\n"
|
||||||
f"Artist: {track.artist}\n"
|
f"Artist: {prd.artist}\n"
|
||||||
f"Track ID: {track.id}\n"
|
f"Track ID: {prd.track_id}\n"
|
||||||
f"Track duration: {ms_to_mmss(track.duration)}\n"
|
f"Track duration: {ms_to_mmss(prd.duration)}\n"
|
||||||
f"Track bitrate: {track.bitrate}\n"
|
f"Track bitrate: {prd.bitrate}\n"
|
||||||
f"Track fade at: {ms_to_mmss(track.fade_at)}\n"
|
"\n\n"
|
||||||
f"Track silence at: {ms_to_mmss(track.silence_at)}"
|
f"Path: {prd.path}\n"
|
||||||
"\n\n"
|
)
|
||||||
f"Path: {track.path}\n"
|
else:
|
||||||
)
|
txt = f"Can't find info about row{row_number}"
|
||||||
else:
|
|
||||||
txt = f"Can't find {track_id=}"
|
|
||||||
|
|
||||||
info: QMessageBox = QMessageBox(self)
|
info: QMessageBox = QMessageBox(self)
|
||||||
info.setIcon(QMessageBox.Icon.Information)
|
info.setIcon(QMessageBox.Icon.Information)
|
||||||
@ -879,6 +915,26 @@ class PlaylistTab(QTableView):
|
|||||||
# if match_row is not None:
|
# if match_row is not None:
|
||||||
# self.selectRow(row_number)
|
# self.selectRow(row_number)
|
||||||
|
|
||||||
|
def select_duplicate_rows(self) -> None:
|
||||||
|
"""
|
||||||
|
Select the last of any rows with duplicate tracks in current playlist.
|
||||||
|
This allows the selection to typically come towards the end of the playlist away
|
||||||
|
from any show specific sections.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Clear any selected rows to avoid confustion
|
||||||
|
self.clear_selection()
|
||||||
|
# We need to be in MultiSelection mode
|
||||||
|
self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
||||||
|
# Get the duplicate rows
|
||||||
|
model = cast(PlaylistModel, self.model())
|
||||||
|
duplicate_rows = model.get_duplicate_rows()
|
||||||
|
# Select the rows
|
||||||
|
for duplicate_row in duplicate_rows:
|
||||||
|
self.selectRow(duplicate_row)
|
||||||
|
# Reset selection mode
|
||||||
|
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
|
|
||||||
def selectionChanged(
|
def selectionChanged(
|
||||||
self, selected: QItemSelection, deselected: QItemSelection
|
self, selected: QItemSelection, deselected: QItemSelection
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
"""Add 'open' field to Playlists
|
||||||
|
|
||||||
|
Revision ID: 5bb2c572e1e5
|
||||||
|
Revises: 3a53a9fb26ab
|
||||||
|
Create Date: 2023-11-18 14:19:02.643914
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '5bb2c572e1e5'
|
||||||
|
down_revision = '3a53a9fb26ab'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('carts', 'duration',
|
||||||
|
existing_type=mysql.INTEGER(display_width=11),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('carts', 'path',
|
||||||
|
existing_type=mysql.VARCHAR(length=2048),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('carts', 'enabled',
|
||||||
|
existing_type=mysql.TINYINT(display_width=1),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('playlist_rows', 'note',
|
||||||
|
existing_type=mysql.VARCHAR(length=2048),
|
||||||
|
nullable=False)
|
||||||
|
op.add_column('playlists', sa.Column('open', sa.Boolean(), nullable=False))
|
||||||
|
op.alter_column('settings', 'name',
|
||||||
|
existing_type=mysql.VARCHAR(length=32),
|
||||||
|
type_=sa.String(length=64),
|
||||||
|
existing_nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('settings', 'name',
|
||||||
|
existing_type=sa.String(length=64),
|
||||||
|
type_=mysql.VARCHAR(length=32),
|
||||||
|
existing_nullable=False)
|
||||||
|
op.drop_column('playlists', 'open')
|
||||||
|
op.alter_column('playlist_rows', 'note',
|
||||||
|
existing_type=mysql.VARCHAR(length=2048),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('carts', 'enabled',
|
||||||
|
existing_type=mysql.TINYINT(display_width=1),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('carts', 'path',
|
||||||
|
existing_type=mysql.VARCHAR(length=2048),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('carts', 'duration',
|
||||||
|
existing_type=mysql.INTEGER(display_width=11),
|
||||||
|
nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -2,6 +2,8 @@ from app.models import (
|
|||||||
Playlists,
|
Playlists,
|
||||||
Tracks,
|
Tracks,
|
||||||
)
|
)
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
from app.helpers import get_file_metadata
|
from app.helpers import get_file_metadata
|
||||||
from app import playlistmodel
|
from app import playlistmodel
|
||||||
from dbconfig import scoped_session
|
from dbconfig import scoped_session
|
||||||
@ -25,7 +27,7 @@ def create_model_with_tracks(session: scoped_session) -> "playlistmodel.Playlist
|
|||||||
track_path = test_tracks[row % len(test_tracks)]
|
track_path = test_tracks[row % len(test_tracks)]
|
||||||
metadata = get_file_metadata(track_path)
|
metadata = get_file_metadata(track_path)
|
||||||
track = Tracks(session, **metadata)
|
track = Tracks(session, **metadata)
|
||||||
model.insert_track_row(row, track.id, f"{row=}")
|
model.insert_row(proposed_row_number=row, track_id=track.id, note=f"{row=}")
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
return model
|
return model
|
||||||
@ -38,10 +40,8 @@ def create_model_with_playlist_rows(
|
|||||||
# Create a model
|
# Create a model
|
||||||
model = playlistmodel.PlaylistModel(playlist.id)
|
model = playlistmodel.PlaylistModel(playlist.id)
|
||||||
for row in range(rows):
|
for row in range(rows):
|
||||||
plr = model._insert_row(session, row)
|
plr = model.insert_row(proposed_row_number=row, note=str(row))
|
||||||
newrow = plr.plr_rownum
|
model.playlist_rows[plr.plr_rownum] = playlistmodel.PlaylistRowData(plr)
|
||||||
plr.note = str(newrow)
|
|
||||||
model.playlist_rows[newrow] = playlistmodel.PlaylistRowData(plr)
|
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
return model
|
return model
|
||||||
@ -195,7 +195,7 @@ def test_insert_header_row_end(monkeypatch, session):
|
|||||||
initial_row_count = 11
|
initial_row_count = 11
|
||||||
|
|
||||||
model = create_model_with_playlist_rows(session, initial_row_count)
|
model = create_model_with_playlist_rows(session, initial_row_count)
|
||||||
model.insert_header_row(None, note_text)
|
model.insert_row(proposed_row_number=None, note=note_text)
|
||||||
assert model.rowCount() == initial_row_count + 1
|
assert model.rowCount() == initial_row_count + 1
|
||||||
prd = model.playlist_rows[model.rowCount() - 1]
|
prd = model.playlist_rows[model.rowCount() - 1]
|
||||||
# Test against edit_role because display_role for headers is
|
# Test against edit_role because display_role for headers is
|
||||||
@ -215,7 +215,7 @@ def test_insert_header_row_middle(monkeypatch, session):
|
|||||||
insert_row = 6
|
insert_row = 6
|
||||||
|
|
||||||
model = create_model_with_playlist_rows(session, initial_row_count)
|
model = create_model_with_playlist_rows(session, initial_row_count)
|
||||||
model.insert_header_row(insert_row, note_text)
|
model.insert_row(proposed_row_number=insert_row, note=note_text)
|
||||||
assert model.rowCount() == initial_row_count + 1
|
assert model.rowCount() == initial_row_count + 1
|
||||||
prd = model.playlist_rows[insert_row]
|
prd = model.playlist_rows[insert_row]
|
||||||
# Test against edit_role because display_role for headers is
|
# Test against edit_role because display_role for headers is
|
||||||
@ -239,14 +239,35 @@ def test_timing_one_track(monkeypatch, session):
|
|||||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||||
model = create_model_with_tracks(session)
|
model = create_model_with_tracks(session)
|
||||||
|
|
||||||
model.insert_header_row(START_ROW, "start+")
|
model.insert_row(proposed_row_number=START_ROW, note="start+")
|
||||||
model.insert_header_row(END_ROW, "-")
|
model.insert_row(proposed_row_number=END_ROW, note="-")
|
||||||
|
|
||||||
prd = model.playlist_rows[START_ROW]
|
prd = model.playlist_rows[START_ROW]
|
||||||
qv_value = model.display_role(START_ROW, playlistmodel.HEADER_NOTES_COLUMN, prd)
|
qv_value = model.display_role(START_ROW, playlistmodel.HEADER_NOTES_COLUMN, prd)
|
||||||
assert qv_value.value() == "start [1 tracks, 4:23 unplayed]"
|
assert qv_value.value() == "start [1 tracks, 4:23 unplayed]"
|
||||||
|
|
||||||
|
|
||||||
|
def test_insert_track_new_playlist(monkeypatch, session):
|
||||||
|
# insert a track into a new playlist
|
||||||
|
|
||||||
|
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||||
|
|
||||||
|
playlist = Playlists(session, "test playlist")
|
||||||
|
# Create a model
|
||||||
|
model = playlistmodel.PlaylistModel(playlist.id)
|
||||||
|
|
||||||
|
track_path = test_tracks[0]
|
||||||
|
metadata = get_file_metadata(track_path)
|
||||||
|
track = Tracks(session, **metadata)
|
||||||
|
model.insert_row(proposed_row_number=0, track_id=track.id)
|
||||||
|
|
||||||
|
prd = model.playlist_rows[model.rowCount() - 1]
|
||||||
|
assert (
|
||||||
|
model.edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd)
|
||||||
|
== metadata["title"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# def test_edit_header(monkeypatch, session): # edit header row in middle of playlist
|
# def test_edit_header(monkeypatch, session): # edit header row in middle of playlist
|
||||||
|
|
||||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user