Compare commits
No commits in common. "f2a27366d3ee0f5d380c78fea4f3bd6dbd81726d" and "be4f19757cf2cb6374aa9aeda688683df2214624" have entirely different histories.
f2a27366d3
...
be4f19757c
@ -2,7 +2,6 @@
|
|||||||
#
|
#
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import stackprinter
|
|
||||||
#
|
#
|
||||||
from dbconfig import Session
|
from dbconfig import Session
|
||||||
#
|
#
|
||||||
@ -418,7 +417,7 @@ class PlaylistRows(Base):
|
|||||||
return (
|
return (
|
||||||
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
|
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
|
||||||
f"track_id={self.track_id}, "
|
f"track_id={self.track_id}, "
|
||||||
f"note={self.note}, row_number={self.row_number}>"
|
f"note={self.note} row_number={self.row_number}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -453,22 +452,31 @@ class PlaylistRows(Base):
|
|||||||
plr.note)
|
plr.note)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_plrids_not_in_list(session: Session, playlist_id: int,
|
def delete_higher_rows(session: Session, playlist_id: int, row: int) \
|
||||||
plrids: List["PlaylistRows"]) -> None:
|
-> None:
|
||||||
"""
|
"""
|
||||||
Delete rows in given playlist that have a higher row number
|
Delete rows in given playlist that have a higher row number
|
||||||
than 'maxrow'
|
than 'row'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
session.execute(
|
# Log the rows to be deleted
|
||||||
delete(PlaylistRows)
|
rows_to_go = session.execute(
|
||||||
.where(
|
select(PlaylistRows)
|
||||||
PlaylistRows.playlist_id == playlist_id,
|
.where(PlaylistRows.playlist_id == playlist_id,
|
||||||
PlaylistRows.id.not_in(plrids)
|
PlaylistRows.row_number > row)
|
||||||
)
|
).scalars().all()
|
||||||
)
|
if not rows_to_go:
|
||||||
# Delete won't take effect until commit()
|
return
|
||||||
session.commit()
|
|
||||||
|
for row in rows_to_go:
|
||||||
|
log.debug(f"Should delete: {row}")
|
||||||
|
# If needed later:
|
||||||
|
# session.delete(row)
|
||||||
|
rows_to_go = session.execute(
|
||||||
|
select(PlaylistRows)
|
||||||
|
.where(PlaylistRows.playlist_id == playlist_id,
|
||||||
|
PlaylistRows.row_number > row)
|
||||||
|
).scalars().all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_rows(session: Session, ids: List[int]) -> None:
|
def delete_rows(session: Session, ids: List[int]) -> None:
|
||||||
@ -501,42 +509,6 @@ class PlaylistRows(Base):
|
|||||||
# Ensure new row numbers are available to the caller
|
# Ensure new row numbers are available to the caller
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_plr(session: Session, row_number: int,
|
|
||||||
playlist_id: int) -> "PlaylistRows":
|
|
||||||
"""Return playlistrows object matching passed parameters"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
select(PlaylistRows)
|
|
||||||
.where(
|
|
||||||
PlaylistRows.row_number == row_number,
|
|
||||||
PlaylistRows.playlist_id == playlist_id)
|
|
||||||
.limit(1)
|
|
||||||
).first()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_track_plr(session: Session, track_id: int,
|
|
||||||
playlist_id: int) -> Optional["PlaylistRows"]:
|
|
||||||
"""Return first matching PlaylistRows object or None"""
|
|
||||||
|
|
||||||
return session.scalars(
|
|
||||||
select(PlaylistRows)
|
|
||||||
.where(
|
|
||||||
PlaylistRows.track_id == track_id,
|
|
||||||
PlaylistRows.playlist_id == playlist_id
|
|
||||||
)
|
|
||||||
.limit(1)
|
|
||||||
).first()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_last_used_row(session: Session, playlist_id: int) -> Optional[int]:
|
|
||||||
"""Return the last used row for playlist, or None if no rows"""
|
|
||||||
|
|
||||||
return session.execute(
|
|
||||||
select(func.max(PlaylistRows.row_number))
|
|
||||||
.where(PlaylistRows.playlist_id == playlist_id)
|
|
||||||
).scalar_one()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_played_rows(cls, session: Session,
|
def get_played_rows(cls, session: Session,
|
||||||
playlist_id: int) -> List[int]:
|
playlist_id: int) -> List[int]:
|
||||||
@ -575,6 +547,15 @@ class PlaylistRows(Base):
|
|||||||
|
|
||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_last_used_row(session: Session, playlist_id: int) -> Optional[int]:
|
||||||
|
"""Return the last used row for playlist, or None if no rows"""
|
||||||
|
|
||||||
|
return session.execute(
|
||||||
|
select(func.max(PlaylistRows.row_number))
|
||||||
|
.where(PlaylistRows.playlist_id == playlist_id)
|
||||||
|
).scalar_one()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_unplayed_rows(cls, session: Session,
|
def get_unplayed_rows(cls, session: Session,
|
||||||
playlist_id: int) -> List[int]:
|
playlist_id: int) -> List[int]:
|
||||||
@ -613,22 +594,23 @@ class PlaylistRows(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def indexed_by_id(session: Session, plr_ids: List[int]) -> dict:
|
def indexed_by_row(session: Session, playlist_id: int) -> dict:
|
||||||
"""
|
"""
|
||||||
Return a dictionary of playlist_rows indexed by their plr id from
|
Return a dictionary of playlist_rows indexed by row number for
|
||||||
the passed plr_id list.
|
the passed playlist_id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plrs = session.execute(
|
plrs = session.execute(
|
||||||
select(PlaylistRows)
|
select(PlaylistRows)
|
||||||
.where(
|
.where(
|
||||||
PlaylistRows.id.in_(plr_ids)
|
PlaylistRows.playlist_id == playlist_id,
|
||||||
)
|
)
|
||||||
|
.order_by(PlaylistRows.row_number)
|
||||||
).scalars().all()
|
).scalars().all()
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
for plr in plrs:
|
for plr in plrs:
|
||||||
result[plr.id] = plr
|
result[plr.row_number] = plr
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from log import log
|
from log import log
|
||||||
import argparse
|
import argparse
|
||||||
import stackprinter # type: ignore
|
import stackprinter
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@ -437,8 +437,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
if not playlist_name:
|
if not playlist_name:
|
||||||
playlist_name = self.solicit_playlist_name()
|
playlist_name = self.solicit_playlist_name()
|
||||||
if not playlist_name:
|
|
||||||
return
|
|
||||||
|
|
||||||
playlist = Playlists(session, playlist_name)
|
playlist = Playlists(session, playlist_name)
|
||||||
return playlist
|
return playlist
|
||||||
@ -448,8 +446,7 @@ 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:
|
self.create_playlist_tab(session, playlist)
|
||||||
self.create_playlist_tab(session, playlist)
|
|
||||||
|
|
||||||
def create_playlist_tab(self, session: Session,
|
def create_playlist_tab(self, session: Session,
|
||||||
playlist: Playlists) -> int:
|
playlist: Playlists) -> int:
|
||||||
@ -739,7 +736,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
helpers.set_track_metadata(session, track)
|
helpers.set_track_metadata(session, track)
|
||||||
helpers.normalise_track(track.path)
|
helpers.normalise_track(track.path)
|
||||||
self.visible_playlist_tab().insert_track(session, track)
|
self.visible_playlist_tab().insert_track(session, track)
|
||||||
self.visible_playlist_tab().save_playlist(session)
|
|
||||||
|
|
||||||
def insert_header(self) -> None:
|
def insert_header(self) -> None:
|
||||||
"""Show dialog box to enter header text and add to playlist"""
|
"""Show dialog box to enter header text and add to playlist"""
|
||||||
@ -759,7 +755,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if ok:
|
if ok:
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
playlist_tab.insert_header(session, dlg.textValue())
|
playlist_tab.insert_header(session, dlg.textValue())
|
||||||
playlist_tab.save_playlist(session)
|
|
||||||
|
|
||||||
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"""
|
||||||
@ -797,8 +792,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Identify destination playlist
|
# Identify destination playlist
|
||||||
visible_tab = self.visible_playlist_tab()
|
visible_tab = self.visible_playlist_tab()
|
||||||
source_playlist = visible_tab.playlist_id
|
source_playlist = visible_tab.playlist_id
|
||||||
|
|
||||||
# Get destination playlist id
|
|
||||||
playlists = []
|
playlists = []
|
||||||
for playlist in Playlists.get_all(session):
|
for playlist in Playlists.get_all(session):
|
||||||
if playlist.id == source_playlist:
|
if playlist.id == source_playlist:
|
||||||
@ -806,17 +799,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
else:
|
else:
|
||||||
playlists.append(playlist)
|
playlists.append(playlist)
|
||||||
|
|
||||||
|
# Get destination playlist id
|
||||||
dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
|
dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
if not dlg.playlist:
|
if not dlg.playlist:
|
||||||
return
|
return
|
||||||
destination_playlist_id = dlg.playlist.id
|
destination_playlist_id = dlg.playlist.id
|
||||||
|
|
||||||
# Remove moved rows from display and save
|
# Remove moved rows from display
|
||||||
visible_tab.remove_rows([plr.row_number for plr in playlistrows])
|
visible_tab.remove_rows([plr.row_number for plr in playlistrows])
|
||||||
visible_tab.save_playlist(session)
|
|
||||||
|
|
||||||
# Update destination playlist in the database
|
# Update playlist for the rows in the database
|
||||||
last_row = PlaylistRows.get_last_used_row(session,
|
last_row = PlaylistRows.get_last_used_row(session,
|
||||||
destination_playlist_id)
|
destination_playlist_id)
|
||||||
if last_row is not None:
|
if last_row is not None:
|
||||||
@ -832,13 +825,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
# Update destination playlist_tab if visible (if not visible, it
|
# Update destination playlist_tab if visible (if not visible, it
|
||||||
# will be re-populated when it is opened)
|
# will be re-populated when it is opened)
|
||||||
destination_playlist_tab = None
|
destionation_playlist_tab = None
|
||||||
for tab in range(self.tabPlaylist.count()):
|
for tab in range(self.tabPlaylist.count()):
|
||||||
if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id:
|
if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id:
|
||||||
destination_playlist_tab = self.tabPlaylist.widget(tab)
|
destionation_playlist_tab = self.tabPlaylist.widget(tab)
|
||||||
break
|
break
|
||||||
if destination_playlist_tab:
|
if destionation_playlist_tab:
|
||||||
destination_playlist_tab.populate_display(session, dlg.playlist.id)
|
destionation_playlist_tab.populate(session, dlg.playlist.id)
|
||||||
|
|
||||||
def move_selected(self) -> None:
|
def move_selected(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -920,17 +913,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
playlist_tab = self.visible_playlist_tab()
|
playlist_tab = self.visible_playlist_tab()
|
||||||
dst_playlist_id = playlist_tab.playlist_id
|
dst_playlist_id = playlist_tab.playlist_id
|
||||||
dst_row = self.visible_playlist_tab().get_new_row_number()
|
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
# Create space in destination playlist
|
# Create space in destination playlist
|
||||||
PlaylistRows.move_rows_down(session, dst_playlist_id,
|
if playlist_tab.selectionModel().hasSelection():
|
||||||
dst_row, len(self.selected_plrs))
|
row = playlist_tab.currentRow()
|
||||||
session.commit()
|
PlaylistRows.move_rows_down(session, dst_playlist_id,
|
||||||
|
row, len(self.selected_plrs))
|
||||||
|
session.commit()
|
||||||
|
|
||||||
# Update plrs
|
|
||||||
row = dst_row
|
|
||||||
src_playlist_id = None
|
src_playlist_id = None
|
||||||
|
dst_row = row
|
||||||
for plr in self.selected_plrs:
|
for plr in self.selected_plrs:
|
||||||
# Update moved rows
|
# Update moved rows
|
||||||
session.add(plr)
|
session.add(plr)
|
||||||
@ -943,8 +936,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# Update display
|
# Update display
|
||||||
self.visible_playlist_tab().populate_display(
|
self.visible_playlist_tab().populate(session, dst_playlist_id,
|
||||||
session, dst_playlist_id, scroll_to_top=False)
|
scroll_to_top=False)
|
||||||
|
|
||||||
# If source playlist is not destination playlist, fixup row
|
# If source playlist is not destination playlist, fixup row
|
||||||
# numbers and update display
|
# numbers and update display
|
||||||
@ -959,8 +952,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
source_playlist_tab = self.tabPlaylist.widget(tab)
|
source_playlist_tab = self.tabPlaylist.widget(tab)
|
||||||
break
|
break
|
||||||
if source_playlist_tab:
|
if source_playlist_tab:
|
||||||
source_playlist_tab.populate_display(
|
source_playlist_tab.populate(session, src_playlist_id,
|
||||||
session, src_playlist_id, scroll_to_top=False)
|
scroll_to_top=False)
|
||||||
|
|
||||||
# Reset so rows can't be repasted
|
# Reset so rows can't be repasted
|
||||||
self.selected_plrs = None
|
self.selected_plrs = None
|
||||||
@ -1504,8 +1497,8 @@ class DbDialog(QDialog):
|
|||||||
|
|
||||||
self.parent().visible_playlist_tab().insert_track(
|
self.parent().visible_playlist_tab().insert_track(
|
||||||
self.session, track, note=self.ui.txtNote.text())
|
self.session, track, note=self.ui.txtNote.text())
|
||||||
# Save to database (which will also commit changes)
|
# Commit session to get correct row numbers if more tracks added
|
||||||
self.parent().visible_playlist_tab().save_playlist(self.session)
|
self.session.commit()
|
||||||
# Clear note field and select search text to make it easier for
|
# Clear note field and select search text to make it easier for
|
||||||
# next search
|
# next search
|
||||||
self.ui.txtNote.clear()
|
self.ui.txtNote.clear()
|
||||||
|
|||||||
316
app/playlists.py
316
app/playlists.py
@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
import stackprinter # type: ignore
|
import stackprinter
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
# self.setSortingEnabled(True)
|
# self.setSortingEnabled(True)
|
||||||
|
|
||||||
# Now load our tracks and notes
|
# Now load our tracks and notes
|
||||||
self.populate_display(session, self.playlist_id)
|
self.populate(session, self.playlist_id)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<PlaylistTab(id={self.playlist_id}>"
|
return f"<PlaylistTab(id={self.playlist_id}>"
|
||||||
@ -327,8 +327,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
act_setnext.triggered.connect(
|
act_setnext.triggered.connect(
|
||||||
lambda: self._set_next(session, row_number))
|
lambda: self._set_next(session, row_number))
|
||||||
|
|
||||||
|
# Open in Audacity
|
||||||
if not current:
|
if not current:
|
||||||
# Open in Audacity
|
|
||||||
act_audacity = self.menu.addAction(
|
act_audacity = self.menu.addAction(
|
||||||
"Open in Audacity")
|
"Open in Audacity")
|
||||||
act_audacity.triggered.connect(
|
act_audacity.triggered.connect(
|
||||||
@ -557,17 +557,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
self.setDragEnabled(False)
|
self.setDragEnabled(False)
|
||||||
|
|
||||||
def get_new_row_number(self) -> int:
|
|
||||||
"""
|
|
||||||
Return the selected row or the row count if no row selected
|
|
||||||
(ie, new row will be appended)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.selectionModel().hasSelection():
|
|
||||||
return self.currentRow()
|
|
||||||
else:
|
|
||||||
return self.rowCount()
|
|
||||||
|
|
||||||
def get_selected_playlistrow_ids(self) -> Optional[List]:
|
def get_selected_playlistrow_ids(self) -> Optional[List]:
|
||||||
"""
|
"""
|
||||||
Return a list of PlaylistRow ids of the selected rows
|
Return a list of PlaylistRow ids of the selected rows
|
||||||
@ -595,49 +584,63 @@ class PlaylistTab(QTableWidget):
|
|||||||
to do the heavy lifing.
|
to do the heavy lifing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
row_number = self.get_new_row_number()
|
# PlaylistRows object requires a row number, but that number
|
||||||
|
# can be reset by calling PlaylistRows.fixup_rownumbers() later,
|
||||||
|
# so just fudge a row number for now.
|
||||||
|
row_number = 0
|
||||||
plr = PlaylistRows(session, self.playlist_id, None, row_number, note)
|
plr = PlaylistRows(session, self.playlist_id, None, row_number, note)
|
||||||
self.insert_row(session, plr, repaint)
|
self.insert_row(session, plr)
|
||||||
self.save_playlist(session)
|
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
|
||||||
|
if repaint:
|
||||||
|
self.update_display(session, clear_selection=False)
|
||||||
|
|
||||||
def insert_row(self, session: Session, plr: PlaylistRows,
|
def insert_row(self, session: Session, row_data: PlaylistRows,
|
||||||
repaint: bool = True) -> None:
|
repaint: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Insert passed playlist row (plr) into playlist tab.
|
Insert a row into playlist tab.
|
||||||
|
|
||||||
|
If playlist has a row selected, add new row above. Otherwise,
|
||||||
|
add to end of playlist.
|
||||||
|
|
||||||
|
Note: we ignore the row number in the PlaylistRows record. That is
|
||||||
|
used only to order the query that generates the records.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
row = plr.row_number
|
if self.selectionModel().hasSelection():
|
||||||
|
row = self.currentRow()
|
||||||
|
else:
|
||||||
|
row = self.rowCount()
|
||||||
self.insertRow(row)
|
self.insertRow(row)
|
||||||
|
|
||||||
# Add row metadata to userdata column
|
# Add row metadata to userdata column
|
||||||
userdata_item = QTableWidgetItem()
|
userdata_item = QTableWidgetItem()
|
||||||
userdata_item.setData(self.ROW_FLAGS, 0)
|
userdata_item.setData(self.ROW_FLAGS, 0)
|
||||||
userdata_item.setData(self.PLAYLISTROW_ID, plr.id)
|
userdata_item.setData(self.PLAYLISTROW_ID, row_data.id)
|
||||||
userdata_item.setData(self.ROW_TRACK_ID, plr.track_id)
|
userdata_item.setData(self.ROW_TRACK_ID, row_data.track_id)
|
||||||
self.setItem(row, USERDATA, userdata_item)
|
self.setItem(row, USERDATA, userdata_item)
|
||||||
|
|
||||||
if plr.track_id:
|
if row_data.track_id:
|
||||||
# Add track details to items
|
# Add track details to items
|
||||||
try:
|
try:
|
||||||
start_gap = plr.track.start_gap
|
start_gap = row_data.track.start_gap
|
||||||
except AttributeError:
|
except:
|
||||||
return
|
return
|
||||||
start_gap_item = QTableWidgetItem(str(start_gap))
|
start_gap_item = QTableWidgetItem(str(start_gap))
|
||||||
if start_gap and start_gap >= 500:
|
if start_gap and start_gap >= 500:
|
||||||
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START))
|
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START))
|
||||||
self.setItem(row, START_GAP, start_gap_item)
|
self.setItem(row, START_GAP, start_gap_item)
|
||||||
|
|
||||||
title_item = QTableWidgetItem(plr.track.title)
|
title_item = QTableWidgetItem(row_data.track.title)
|
||||||
log.debug(f"KAE: insert_row:619, {title_item.text()=}")
|
log.debug(f"KAE: insert_row:619, {title_item.text()=}")
|
||||||
self.setItem(row, TITLE, title_item)
|
self.setItem(row, TITLE, title_item)
|
||||||
|
|
||||||
artist_item = QTableWidgetItem(plr.track.artist)
|
artist_item = QTableWidgetItem(row_data.track.artist)
|
||||||
self.setItem(row, ARTIST, artist_item)
|
self.setItem(row, ARTIST, artist_item)
|
||||||
|
|
||||||
duration_item = QTableWidgetItem(
|
duration_item = QTableWidgetItem(
|
||||||
ms_to_mmss(plr.track.duration))
|
ms_to_mmss(row_data.track.duration))
|
||||||
self.setItem(row, DURATION, duration_item)
|
self.setItem(row, DURATION, duration_item)
|
||||||
self._set_row_duration(row, plr.track.duration)
|
self._set_row_duration(row, row_data.track.duration)
|
||||||
|
|
||||||
start_item = QTableWidgetItem()
|
start_item = QTableWidgetItem()
|
||||||
self.setItem(row, START_TIME, start_item)
|
self.setItem(row, START_TIME, start_item)
|
||||||
@ -645,8 +648,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
end_item = QTableWidgetItem()
|
end_item = QTableWidgetItem()
|
||||||
self.setItem(row, END_TIME, end_item)
|
self.setItem(row, END_TIME, end_item)
|
||||||
|
|
||||||
if plr.track.bitrate:
|
if row_data.track.bitrate:
|
||||||
bitrate = str(plr.track.bitrate)
|
bitrate = str(row_data.track.bitrate)
|
||||||
else:
|
else:
|
||||||
bitrate = ""
|
bitrate = ""
|
||||||
bitrate_item = QTableWidgetItem(bitrate)
|
bitrate_item = QTableWidgetItem(bitrate)
|
||||||
@ -654,23 +657,23 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
# As we have track info, any notes should be contained in
|
# As we have track info, any notes should be contained in
|
||||||
# the notes column
|
# the notes column
|
||||||
notes_item = QTableWidgetItem(plr.note)
|
notes_item = QTableWidgetItem(row_data.note)
|
||||||
self.setItem(row, ROW_NOTES, notes_item)
|
self.setItem(row, ROW_NOTES, notes_item)
|
||||||
|
|
||||||
last_playtime = Playdates.last_played(session, plr.track.id)
|
last_playtime = Playdates.last_played(session, row_data.track.id)
|
||||||
last_played_str = get_relative_date(last_playtime)
|
last_played_str = get_relative_date(last_playtime)
|
||||||
last_played_item = QTableWidgetItem(last_played_str)
|
last_played_item = QTableWidgetItem(last_played_str)
|
||||||
self.setItem(row, LASTPLAYED, last_played_item)
|
self.setItem(row, LASTPLAYED, last_played_item)
|
||||||
|
|
||||||
# Mark track if file is unreadable
|
# Mark track if file is unreadable
|
||||||
if not file_is_readable(plr.track.path):
|
if not file_is_readable(row_data.track.path):
|
||||||
self._set_unreadable_row(row)
|
self._set_unreadable_row(row)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# This is a section header so it must have note text
|
# This is a section header so it must have note text
|
||||||
if plr.note is None:
|
if row_data.note is None:
|
||||||
log.debug(
|
log.debug(
|
||||||
f"insert_row({plr=}) with no track_id and no note"
|
f"insert_row({row_data=}) with no track_id and no note"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -684,16 +687,17 @@ class PlaylistTab(QTableWidget):
|
|||||||
continue
|
continue
|
||||||
self.setItem(row, i, QTableWidgetItem())
|
self.setItem(row, i, QTableWidgetItem())
|
||||||
self.setSpan(row, HEADER_NOTES_COLUMN, 1, len(columns) - 1)
|
self.setSpan(row, HEADER_NOTES_COLUMN, 1, len(columns) - 1)
|
||||||
notes_item = QTableWidgetItem(plr.note)
|
notes_item = QTableWidgetItem(row_data.note)
|
||||||
self.setItem(row, HEADER_NOTES_COLUMN, notes_item)
|
self.setItem(row, HEADER_NOTES_COLUMN, notes_item)
|
||||||
|
|
||||||
# Save (no) track_id
|
# Save (no) track_id
|
||||||
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
||||||
|
|
||||||
if repaint:
|
if repaint:
|
||||||
|
self.save_playlist(session)
|
||||||
self.update_display(session, clear_selection=False)
|
self.update_display(session, clear_selection=False)
|
||||||
|
|
||||||
def insert_track(self, session: Session, track: Tracks,
|
def insert_track(self, session: Session, track: Optional[Tracks],
|
||||||
note: str = None, repaint: bool = True) -> None:
|
note: str = None, repaint: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Insert track into playlist tab.
|
Insert track into playlist tab.
|
||||||
@ -705,30 +709,20 @@ class PlaylistTab(QTableWidget):
|
|||||||
to do the heavy lifing.
|
to do the heavy lifing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not track and track.id:
|
# PlaylistRows object requires a row number, but that number
|
||||||
log.debug(
|
# can be reset by calling PlaylistRows.fixup_rownumbers() later,
|
||||||
f"insert_track({session=}, {track=}, {note=}, {repaint=}"
|
# so just fudge a row number for now.
|
||||||
" called with either no track or no track.id"
|
row_number = 0
|
||||||
)
|
if track:
|
||||||
return
|
track_id = track.id
|
||||||
|
else:
|
||||||
row_number = self.get_new_row_number()
|
track_id = None
|
||||||
|
plr = PlaylistRows(session, self.playlist_id,
|
||||||
# Check to see whether track is already in playlist
|
track_id, row_number, note)
|
||||||
existing_plr = PlaylistRows.get_track_plr(session, track.id,
|
self.insert_row(session, plr)
|
||||||
self.playlist_id)
|
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
|
||||||
if existing_plr and ask_yes_no("Duplicate row",
|
if repaint:
|
||||||
"Track already in playlist. "
|
self.update_display(session, clear_selection=False)
|
||||||
"Move to new location?"):
|
|
||||||
# Yes it is and we shoudl reuse it
|
|
||||||
return self._move_row(session, existing_plr, row_number)
|
|
||||||
|
|
||||||
# Build playlist_row object
|
|
||||||
plr = PlaylistRows(session, self.playlist_id, track.id,
|
|
||||||
row_number, note)
|
|
||||||
self.insert_row(session, plr, repaint)
|
|
||||||
# Let display update, then save playlist
|
|
||||||
QTimer.singleShot(0, lambda: self.save_playlist(session))
|
|
||||||
|
|
||||||
def play_started(self, session: Session) -> None:
|
def play_started(self, session: Session) -> None:
|
||||||
"""
|
"""
|
||||||
@ -777,10 +771,10 @@ class PlaylistTab(QTableWidget):
|
|||||||
self._clear_current_track_row()
|
self._clear_current_track_row()
|
||||||
self.current_track_start_time = None
|
self.current_track_start_time = None
|
||||||
|
|
||||||
def populate_display(self, session: Session, playlist_id: int,
|
def populate(self, session: Session, playlist_id: int,
|
||||||
scroll_to_top: bool = True) -> None:
|
scroll_to_top: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Populate display from the associated playlist ID
|
Populate from the associated playlist ID
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Sanity check row numbering before we load
|
# Sanity check row numbering before we load
|
||||||
@ -791,8 +785,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
# Add the rows
|
# Add the rows
|
||||||
playlist = session.get(Playlists, playlist_id)
|
playlist = session.get(Playlists, playlist_id)
|
||||||
for plr in playlist.rows:
|
for row in playlist.rows:
|
||||||
self.insert_row(session, plr, repaint=False)
|
self.insert_row(session, row, repaint=False)
|
||||||
|
|
||||||
# Scroll to top
|
# Scroll to top
|
||||||
if scroll_to_top:
|
if scroll_to_top:
|
||||||
@ -828,39 +822,22 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
def save_playlist(self, session: Session) -> None:
|
def save_playlist(self, session: Session) -> None:
|
||||||
"""
|
"""
|
||||||
Get the PlaylistRow objects for each row in the display. Correct
|
All playlist rows have a PlaylistRows id. Check that that id points
|
||||||
the row_number and playlist_id if necessary. Remove any row
|
to this playlist (in case track has been moved from other) and that
|
||||||
numbers in the database that are higher than the last row in
|
the row number is correct (in case tracks have been reordered).
|
||||||
the display.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Build a dictionary of
|
plr_dict = PlaylistRows.indexed_by_row(session, self.playlist_id)
|
||||||
# {display_row_number: display_row_plr_id}
|
|
||||||
display_plr_ids = {row_number: self._get_playlistrow_id(row_number)
|
|
||||||
for row_number in range(self.rowCount())}
|
|
||||||
|
|
||||||
# Now build a dictionary of
|
|
||||||
# {display_row_number: display_row_plr}
|
|
||||||
plr_dict_by_id = PlaylistRows.indexed_by_id(session,
|
|
||||||
display_plr_ids.values())
|
|
||||||
|
|
||||||
# Finally a dictionary of
|
|
||||||
# {display_row_number: plr}
|
|
||||||
row_plr = {row_number: plr_dict_by_id[display_plr_ids[row_number]]
|
|
||||||
for row_number in range(self.rowCount())}
|
|
||||||
|
|
||||||
# Ensure all row plrs have correct row number and playlist_id
|
|
||||||
for row in range(self.rowCount()):
|
for row in range(self.rowCount()):
|
||||||
row_plr[row].row_number = row
|
# Set the row number and playlist id (even if correct)
|
||||||
row_plr[row].playlist_id = self.playlist_id
|
plr_dict[row].row_number = row
|
||||||
|
plr_dict[row].playlist_id = self.playlist_id
|
||||||
|
|
||||||
# Any rows in the database for this playlist that have a plr id
|
# Any rows in the database with a row_number higher that the
|
||||||
# that's not in the displayed playlist need to be deleted.
|
# current value of 'row' should not be there. Commit session
|
||||||
|
# first to ensure any changes made above are committed.
|
||||||
# Ensure changes flushed
|
|
||||||
session.commit()
|
session.commit()
|
||||||
PlaylistRows.delete_plrids_not_in_list(session, self.playlist_id,
|
PlaylistRows.delete_higher_rows(session, self.playlist_id, row)
|
||||||
display_plr_ids.values())
|
|
||||||
|
|
||||||
def scroll_current_to_top(self) -> None:
|
def scroll_current_to_top(self) -> None:
|
||||||
"""Scroll currently-playing row to top"""
|
"""Scroll currently-playing row to top"""
|
||||||
@ -883,6 +860,65 @@ class PlaylistTab(QTableWidget):
|
|||||||
return
|
return
|
||||||
self._search(next=True)
|
self._search(next=True)
|
||||||
|
|
||||||
|
def _search(self, next: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Select next/previous row containg self.search_string. Start from
|
||||||
|
top selected row if there is one, else from top.
|
||||||
|
|
||||||
|
Wrap at last/first row.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.search_text:
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_row = self._get_selected_row()
|
||||||
|
if next:
|
||||||
|
if selected_row is not None and selected_row < self.rowCount() - 1:
|
||||||
|
starting_row = selected_row + 1
|
||||||
|
else:
|
||||||
|
starting_row = 0
|
||||||
|
else:
|
||||||
|
if selected_row is not None and selected_row > 0:
|
||||||
|
starting_row = selected_row - 1
|
||||||
|
else:
|
||||||
|
starting_row = self.rowCount() - 1
|
||||||
|
|
||||||
|
wrapped = False
|
||||||
|
match_row = None
|
||||||
|
row = starting_row
|
||||||
|
needle = self.search_text.lower()
|
||||||
|
while True:
|
||||||
|
# Check for match in title, artist or notes
|
||||||
|
title = self._get_row_title(row)
|
||||||
|
if title and needle in title.lower():
|
||||||
|
match_row = row
|
||||||
|
break
|
||||||
|
artist = self._get_row_artist(row)
|
||||||
|
if artist and needle in artist.lower():
|
||||||
|
match_row = row
|
||||||
|
break
|
||||||
|
note = self._get_row_note(row)
|
||||||
|
if note and needle in note.lower():
|
||||||
|
match_row = row
|
||||||
|
break
|
||||||
|
if next:
|
||||||
|
row += 1
|
||||||
|
if wrapped and row >= starting_row:
|
||||||
|
break
|
||||||
|
if row >= self.rowCount():
|
||||||
|
row = 0
|
||||||
|
wrapped = True
|
||||||
|
else:
|
||||||
|
row -= 1
|
||||||
|
if wrapped and row <= starting_row:
|
||||||
|
break
|
||||||
|
if row < 0:
|
||||||
|
row = self.rowCount() - 1
|
||||||
|
wrapped = True
|
||||||
|
|
||||||
|
if match_row is not None:
|
||||||
|
self.selectRow(row)
|
||||||
|
|
||||||
def search_next(self) -> None:
|
def search_next(self) -> None:
|
||||||
"""
|
"""
|
||||||
Select next row containg self.search_string.
|
Select next row containg self.search_string.
|
||||||
@ -1308,8 +1344,9 @@ class PlaylistTab(QTableWidget):
|
|||||||
Delete mutliple rows
|
Delete mutliple rows
|
||||||
|
|
||||||
Actions required:
|
Actions required:
|
||||||
|
- Delete the rows from the PlaylistRows table
|
||||||
|
- Correct the row numbers in the PlaylistRows table
|
||||||
- Remove the rows from the display
|
- Remove the rows from the display
|
||||||
- Save the playlist
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Delete rows from database
|
# Delete rows from database
|
||||||
@ -1322,10 +1359,13 @@ class PlaylistTab(QTableWidget):
|
|||||||
f"Really delete {row_count} row{plural}?"):
|
f"Really delete {row_count} row{plural}?"):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.remove_selected_rows()
|
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
QTimer.singleShot(0, lambda: self.save_playlist(session))
|
PlaylistRows.delete_rows(session, plr_ids)
|
||||||
|
|
||||||
|
# Fix up row numbers left in this playlist
|
||||||
|
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
|
||||||
|
# Remove selected rows from display
|
||||||
|
self.remove_selected_rows()
|
||||||
|
|
||||||
def _drop_on(self, event):
|
def _drop_on(self, event):
|
||||||
"""
|
"""
|
||||||
@ -1505,6 +1545,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
return self._meta_search(RowMeta.UNREADABLE, one=False)
|
return self._meta_search(RowMeta.UNREADABLE, one=False)
|
||||||
|
|
||||||
|
# def _header_click(self, index: int) -> None:
|
||||||
|
# """Handle playlist header click"""
|
||||||
|
|
||||||
|
# print(f"_header_click({index=})")
|
||||||
|
|
||||||
def _info_row(self, track_id: int) -> None:
|
def _info_row(self, track_id: int) -> None:
|
||||||
"""Display popup with info re row"""
|
"""Display popup with info re row"""
|
||||||
|
|
||||||
@ -1613,20 +1658,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
new_metadata = self._meta_get(row) | (1 << attribute)
|
new_metadata = self._meta_get(row) | (1 << attribute)
|
||||||
self.item(row, USERDATA).setData(self.ROW_FLAGS, new_metadata)
|
self.item(row, USERDATA).setData(self.ROW_FLAGS, new_metadata)
|
||||||
|
|
||||||
def _move_row(self, session: Session, plr: PlaylistRows,
|
|
||||||
new_row_number: int) -> None:
|
|
||||||
"""Move playlist row to new_row_number using parent copy/paste"""
|
|
||||||
|
|
||||||
# Remove source row
|
|
||||||
self.removeRow(plr.row_number)
|
|
||||||
# Fixup plr row number
|
|
||||||
if plr.row_number < new_row_number:
|
|
||||||
plr.row_number = new_row_number - 1
|
|
||||||
else:
|
|
||||||
plr.row_number = new_row_number
|
|
||||||
self.insert_row(session, plr)
|
|
||||||
self.save_playlist(session)
|
|
||||||
|
|
||||||
def _mplayer_play(self, track_id: int) -> None:
|
def _mplayer_play(self, track_id: int) -> None:
|
||||||
"""Play track with mplayer"""
|
"""Play track with mplayer"""
|
||||||
|
|
||||||
@ -1738,65 +1769,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
scroll_item = self.item(top_row, 0)
|
scroll_item = self.item(top_row, 0)
|
||||||
self.scrollToItem(scroll_item, QAbstractItemView.PositionAtTop)
|
self.scrollToItem(scroll_item, QAbstractItemView.PositionAtTop)
|
||||||
|
|
||||||
def _search(self, next: bool = True) -> None:
|
|
||||||
"""
|
|
||||||
Select next/previous row containg self.search_string. Start from
|
|
||||||
top selected row if there is one, else from top.
|
|
||||||
|
|
||||||
Wrap at last/first row.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not self.search_text:
|
|
||||||
return
|
|
||||||
|
|
||||||
selected_row = self._get_selected_row()
|
|
||||||
if next:
|
|
||||||
if selected_row is not None and selected_row < self.rowCount() - 1:
|
|
||||||
starting_row = selected_row + 1
|
|
||||||
else:
|
|
||||||
starting_row = 0
|
|
||||||
else:
|
|
||||||
if selected_row is not None and selected_row > 0:
|
|
||||||
starting_row = selected_row - 1
|
|
||||||
else:
|
|
||||||
starting_row = self.rowCount() - 1
|
|
||||||
|
|
||||||
wrapped = False
|
|
||||||
match_row = None
|
|
||||||
row = starting_row
|
|
||||||
needle = self.search_text.lower()
|
|
||||||
while True:
|
|
||||||
# Check for match in title, artist or notes
|
|
||||||
title = self._get_row_title(row)
|
|
||||||
if title and needle in title.lower():
|
|
||||||
match_row = row
|
|
||||||
break
|
|
||||||
artist = self._get_row_artist(row)
|
|
||||||
if artist and needle in artist.lower():
|
|
||||||
match_row = row
|
|
||||||
break
|
|
||||||
note = self._get_row_note(row)
|
|
||||||
if note and needle in note.lower():
|
|
||||||
match_row = row
|
|
||||||
break
|
|
||||||
if next:
|
|
||||||
row += 1
|
|
||||||
if wrapped and row >= starting_row:
|
|
||||||
break
|
|
||||||
if row >= self.rowCount():
|
|
||||||
row = 0
|
|
||||||
wrapped = True
|
|
||||||
else:
|
|
||||||
row -= 1
|
|
||||||
if wrapped and row <= starting_row:
|
|
||||||
break
|
|
||||||
if row < 0:
|
|
||||||
row = self.rowCount() - 1
|
|
||||||
wrapped = True
|
|
||||||
|
|
||||||
if match_row is not None:
|
|
||||||
self.selectRow(row)
|
|
||||||
|
|
||||||
def _select_event(self) -> None:
|
def _select_event(self) -> None:
|
||||||
"""
|
"""
|
||||||
Called when item selection changes.
|
Called when item selection changes.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user