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