Compare commits

..

12 Commits

Author SHA1 Message Date
Keith Edmunds
c5c5c28583 Change PlaylistRows.row_number to plr_rownnum 2023-04-02 17:23:49 +01:00
Keith Edmunds
f3c86484fe Change remaining PlaylistRows.row_number to row_no 2023-04-02 15:14:11 +01:00
Keith Edmunds
034993b737 Remove .idea files 2023-04-01 19:59:58 +01:00
Keith Edmunds
f9f1e5f237 Change PlaylistRows.row_number to row_no 2023-04-01 19:59:25 +01:00
Keith Edmunds
5cb6e83cd5 Specifiy Python3 in hashbang line 2023-04-01 19:45:48 +01:00
Keith Edmunds
16a9880583 Improve track search performance
Searching for a track was wrapping the search string in % signs
(wildcards). The leading % meant the database didn't use the index.
Dropped leading % (user can add it manually if needed).
2023-04-01 19:45:07 +01:00
Keith Edmunds
69bfd3cff9 Default to moving existing track when adding a new track 2023-03-25 17:20:10 +00:00
Keith Edmunds
3a14207c71 Ensure we pass ints to signal 2023-03-25 16:30:58 +00:00
Keith Edmunds
25287c8f7f Tidy playlist header colours
Simplify and also ensure that playlist tab is uncoloured after
unsetting next track.
2023-03-25 15:52:17 +00:00
Keith Edmunds
4a03596bd3 Ensure current track visible toggling hide/show played 2023-03-25 10:30:18 +00:00
Keith Edmunds
9c66333729 musicmuster refactor: signal next tracks, tab colouring 2023-03-19 15:21:02 +00:00
Keith Edmunds
728feb1c8e Allow multiple selected rows to be marked unplayed 2023-03-19 13:47:49 +00:00
15 changed files with 212 additions and 283 deletions

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="musicmuster_dev@localhost" uuid="49514dbe-26ec-4cb2-b457-06666d93ac47">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://localhost:3306/musicmuster_dev</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -1,3 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="kae" />
</component>

View File

@ -1,14 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPep8Inspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="PyQt5.QtWidgets.customContextMenuRequested.*" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Default" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (musicmuster)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/musicmuster.iml" filepath="$PROJECT_DIR$/.idea/musicmuster.iml" />
</modules>
</component>
</project>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Poetry (musicmuster)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="MariaDB" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -18,12 +18,19 @@ from tinytag import TinyTag # type: ignore
from typing import Any, Dict, Optional, Union from typing import Any, Dict, Optional, Union
def ask_yes_no(title: str, question: str) -> bool: def ask_yes_no(title: str, question: str, default_yes: bool = False) -> bool:
"""Ask question; return True for yes, False for no""" """Ask question; return True for yes, False for no"""
button_reply = QMessageBox.question(None, title, question) dlg = QMessageBox()
dlg.setWindowTitle(title)
dlg.setText(question)
dlg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
dlg.setIcon(QMessageBox.Question)
if default_yes:
dlg.setDefaultButton(QMessageBox.Yes)
button = dlg.exec_()
return button_reply == QMessageBox.Yes return button == QMessageBox.Yes
def fade_point( def fade_point(

View File

@ -203,7 +203,7 @@ class Playlists(Base):
"PlaylistRows", "PlaylistRows",
back_populates="playlist", back_populates="playlist",
cascade="all, delete-orphan", cascade="all, delete-orphan",
order_by="PlaylistRows.row_number" order_by="PlaylistRows.plr_rownum"
) )
def __repr__(self) -> str: def __repr__(self) -> str:
@ -372,7 +372,7 @@ class PlaylistRows(Base):
__tablename__ = 'playlist_rows' __tablename__ = 'playlist_rows'
id: int = Column(Integer, primary_key=True, autoincrement=True) id: int = Column(Integer, primary_key=True, autoincrement=True)
row_number: int = Column(Integer, nullable=False) plr_rownum: int = Column(Integer, nullable=False)
note: str = Column(String(2048), index=False, default="", nullable=False) note: str = Column(String(2048), index=False, default="", nullable=False)
playlist_id: int = Column(Integer, ForeignKey('playlists.id'), playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
nullable=False) nullable=False)
@ -385,7 +385,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}, plr_rownum={self.plr_rownum}>"
) )
def __init__(self, def __init__(self,
@ -399,7 +399,7 @@ class PlaylistRows(Base):
self.playlist_id = playlist_id self.playlist_id = playlist_id
self.track_id = track_id self.track_id = track_id
self.row_number = row_number self.plr_rownum = row_number
self.note = note self.note = note
session.add(self) session.add(self)
session.flush() session.flush()
@ -425,7 +425,7 @@ class PlaylistRows(Base):
).scalars().all() ).scalars().all()
for plr in src_rows: for plr in src_rows:
PlaylistRows(session, dst_id, plr.track_id, plr.row_number, PlaylistRows(session, dst_id, plr.track_id, plr.plr_rownum,
plr.note) plr.note)
@staticmethod @staticmethod
@ -440,7 +440,7 @@ class PlaylistRows(Base):
delete(PlaylistRows) delete(PlaylistRows)
.where( .where(
PlaylistRows.playlist_id == playlist_id, PlaylistRows.playlist_id == playlist_id,
PlaylistRows.row_number > maxrow PlaylistRows.plr_rownum > maxrow
) )
) )
session.flush() session.flush()
@ -454,11 +454,11 @@ class PlaylistRows(Base):
plrs = session.execute( plrs = session.execute(
select(PlaylistRows) select(PlaylistRows)
.where(PlaylistRows.playlist_id == playlist_id) .where(PlaylistRows.playlist_id == playlist_id)
.order_by(PlaylistRows.row_number) .order_by(PlaylistRows.plr_rownum)
).scalars().all() ).scalars().all()
for i, plr in enumerate(plrs): for i, plr in enumerate(plrs):
plr.row_number = i plr.plr_rownum = i
# Ensure new row numbers are available to the caller # Ensure new row numbers are available to the caller
session.commit() session.commit()
@ -476,7 +476,7 @@ class PlaylistRows(Base):
.where( .where(
cls.playlist_id == playlist_id, cls.playlist_id == playlist_id,
cls.id.in_(plr_ids) cls.id.in_(plr_ids)
).order_by(cls.row_number)).scalars().all() ).order_by(cls.plr_rownum)).scalars().all()
return plrs return plrs
@ -486,7 +486,7 @@ class PlaylistRows(Base):
"""Return the last used row for playlist, or None if no rows""" """Return the last used row for playlist, or None if no rows"""
return session.execute( return session.execute(
select(func.max(PlaylistRows.row_number)) select(func.max(PlaylistRows.plr_rownum))
.where(PlaylistRows.playlist_id == playlist_id) .where(PlaylistRows.playlist_id == playlist_id)
).scalar_one() ).scalar_one()
@ -518,7 +518,7 @@ class PlaylistRows(Base):
cls.playlist_id == playlist_id, cls.playlist_id == playlist_id,
cls.played.is_(True) cls.played.is_(True)
) )
.order_by(cls.row_number) .order_by(cls.plr_rownum)
).scalars().all() ).scalars().all()
return plrs return plrs
@ -538,12 +538,12 @@ class PlaylistRows(Base):
cls.track_id.is_not(None) cls.track_id.is_not(None)
) )
if from_row is not None: if from_row is not None:
query = query.where(cls.row_number >= from_row) query = query.where(cls.plr_rownum >= from_row)
if to_row is not None: if to_row is not None:
query = query.where(cls.row_number <= to_row) query = query.where(cls.plr_rownum <= to_row)
plrs = ( plrs = (
session.execute((query).order_by(cls.row_number)).scalars().all() session.execute((query).order_by(cls.plr_rownum)).scalars().all()
) )
return plrs return plrs
@ -563,7 +563,7 @@ class PlaylistRows(Base):
cls.track_id.is_not(None), cls.track_id.is_not(None),
cls.played.is_(False) cls.played.is_(False)
) )
.order_by(cls.row_number) .order_by(cls.plr_rownum)
).scalars().all() ).scalars().all()
return plrs return plrs
@ -580,9 +580,9 @@ class PlaylistRows(Base):
update(PlaylistRows) update(PlaylistRows)
.where( .where(
(PlaylistRows.playlist_id == playlist_id), (PlaylistRows.playlist_id == playlist_id),
(PlaylistRows.row_number >= starting_row) (PlaylistRows.plr_rownum >= starting_row)
) )
.values(row_number=PlaylistRows.row_number + move_by) .values(plr_rownum=PlaylistRows.plr_rownum + move_by)
) )
@ -731,7 +731,7 @@ class Tracks(Base):
return ( return (
session.execute( session.execute(
select(cls) select(cls)
.where(cls.title.ilike(f"%{text}%")) .where(cls.title.like(f"{text}%"))
.order_by(cls.title) .order_by(cls.title)
) )
.scalars() .scalars()

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
from log import log from log import log
from os.path import basename from os.path import basename
@ -142,7 +142,7 @@ class CartButton(QPushButton):
class PlaylistTrack: class PlaylistTrack:
""" """
Used to provide a single reference point for specific playlist tracks, Used to provide a single reference point for specific playlist tracks,
typicall the previous, current and next track. typically the previous, current and next track.
""" """
def __init__(self) -> None: def __init__(self) -> None:
@ -257,7 +257,7 @@ class MusicMusterSignals(QObject):
emit-a-signal-from-another-class-to-main-class emit-a-signal-from-another-class-to-main-class
""" """
update_row_note_signal = pyqtSignal(int) set_next_track_signal = pyqtSignal(int, int)
class Window(QMainWindow, Ui_MainWindow): class Window(QMainWindow, Ui_MainWindow):
@ -579,8 +579,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionSelect_previous_track.triggered.connect( self.actionSelect_previous_track.triggered.connect(
self.select_previous_row) self.select_previous_row)
self.actionMoveUnplayed.triggered.connect(self.move_unplayed) self.actionMoveUnplayed.triggered.connect(self.move_unplayed)
self.actionSetNext.triggered.connect( self.actionSetNext.triggered.connect(self.set_selected_track_next)
lambda: self.tabPlaylist.currentWidget().set_selected_as_next())
self.actionSkipToNext.triggered.connect(self.play_next) self.actionSkipToNext.triggered.connect(self.play_next)
self.actionStop.triggered.connect(self.stop) self.actionStop.triggered.connect(self.stop)
self.btnDrop3db.clicked.connect(self.drop3db) self.btnDrop3db.clicked.connect(self.drop3db)
@ -998,8 +997,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.next_track.plr_id] self.next_track.plr_id]
] ]
rows_to_delete = [plr.row_number for plr in plrs_to_move rows_to_delete = [plr.plr_rownum for plr in plrs_to_move
if plr.row_number is not None] if plr.plr_rownum is not None]
if not rows_to_delete: if not rows_to_delete:
return return
@ -1029,7 +1028,7 @@ class Window(QMainWindow, Ui_MainWindow):
next_row = 0 next_row = 0
for plr in plrs_to_move: for plr in plrs_to_move:
plr.row_number = next_row plr.plr_rownum = next_row
next_row += 1 next_row += 1
plr.playlist_id = destination_playlist_id plr.playlist_id = destination_playlist_id
# Reset played as it's not been played on this playlist # Reset played as it's not been played on this playlist
@ -1151,7 +1150,7 @@ class Window(QMainWindow, Ui_MainWindow):
if not src_playlist_id: if not src_playlist_id:
src_playlist_id = plr.playlist_id src_playlist_id = plr.playlist_id
plr.playlist_id = dst_playlist_id plr.playlist_id = dst_playlist_id
plr.row_number = row plr.plr_rownum = row
row += 1 row += 1
if not src_playlist_id: if not src_playlist_id:
@ -1285,43 +1284,45 @@ class Window(QMainWindow, Ui_MainWindow):
def resume(self) -> None: def resume(self) -> None:
""" """
Resume playing stopped track Resume playing last track. We may be playing the next track
or none; take care of both eventualities.
Actions required: Actions required:
- Return if no saved position - Return if no saved position
- Store saved position - Resume last track
- Store next track - If a track is playing, make that the next track
- Set previous track to be next track
- Call play_next() from saved position
- Reset next track
""" """
# Return if no saved position # Return if no saved position
if not self.previous_track_position: if not self.previous_track_position:
return return
# Note resume point # Note any playing track as this will become the next track
resume_from = self.previous_track_position playing_track = None
if self.current_track.track_id:
# Remember what was to have been the next track playing_track = self.current_track
original_next_plr_id = self.next_track.plr_id
original_next_plr_playlist_tab = self.next_track.playlist_tab
with Session() as session: with Session() as session:
# Set next track to be the last one played # Set next plr to be track to resume
self.next_track = self.previous_track if not self.previous_track.plr_id:
self.previous_track = PlaylistTrack() return
if not self.previous_track.playlist_tab:
return
# Resume last track # Resume last track
self.play_next(resume_from) self.set_next_plr_id(self.previous_track.plr_id,
self.previous_track.playlist_tab)
self.play_next(self.previous_track_position)
# Reset next track if there was one # If a track was playing when we were called, get details to
if original_next_plr_id: # set it as the next track
next_plr = session.get(PlaylistRows, original_next_plr_id) if playing_track:
if not next_plr or not original_next_plr_playlist_tab: if not playing_track.plr_id:
return return
self.this_is_the_next_playlist_row( if not playing_track.playlist_tab:
session, next_plr, original_next_plr_playlist_tab) return
self.set_next_plr_id(playing_track.plr_id,
playing_track.playlist_tab)
def save_as_template(self) -> None: def save_as_template(self) -> None:
"""Save current playlist as template""" """Save current playlist as template"""
@ -1494,67 +1495,94 @@ class Window(QMainWindow, Ui_MainWindow):
# May also be called when last tab is closed # May also be called when last tab is closed
pass pass
def this_is_the_next_playlist_row( def set_selected_track_next(self) -> None:
self, session: scoped_session, plr: PlaylistRows,
next_track_playlist_tab: PlaylistTab) -> None:
""" """
This is notification from a playlist tab that it holds the next Set currently-selected row on visible playlist tab as next track
playlist row to be played.
Actions required:
- Clear next track if on other tab
- Reset tab colour if on other tab
- Note next playlist tab
- Set next next_track_playlist_tab tab colour
- Note next track
- Update headers
- Populate info tabs
""" """
if plr.track_id is None: playlist_tab = self.visible_playlist_tab()
selected_plr_ids = playlist_tab.get_selected_playlistrow_ids()
if len(selected_plr_ids) != 1:
log.error(f"set_next_track:_from_mm {selected_plr_ids=}")
return return
# If we already have a next tab lined up and it's neither self.set_next_plr_id(selected_plr_ids[0], playlist_tab)
# the "new" next tab nor the current track tab then we need
# to reset the tab colour. def set_next_plr_id(self, next_plr_id: Optional[int],
playlist_tab: PlaylistTab) -> None:
"""
Set passed plr_id as next track to play, or clear next track if None
Actions required:
- Update self.next_track PlaylistTrack structure
- Tell playlist tabs to update their 'next track' highlighting
- Update headers
- Set playlist tab colours
- Populate info tabs
"""
with Session() as session:
# Update self.next_track PlaylistTrack structure
old_next_track = self.next_track
self.next_track = PlaylistTrack()
if next_plr_id:
next_plr = session.get(PlaylistRows, next_plr_id)
if next_plr:
self.next_track.set_plr(session, next_plr, playlist_tab)
# Tell playlist tabs to update their 'next track' highlighting
# Args must both be ints, so use zero for no next track
self.signals.set_next_track_signal.emit(old_next_track.plr_id,
next_plr_id or 0)
# Update headers
self.update_headers()
# Set playlist tab colours
self._set_next_track_playlist_tab_colours(old_next_track)
if next_plr_id:
# Populate 'info' tabs with Wikipedia info, but queue it
# because it isn't quick
if self.next_track.title:
QTimer.singleShot(
1, lambda: self.tabInfolist.open_in_wikipedia(
self.next_track.title)
)
def _set_next_track_playlist_tab_colours(
self, old_next_track: Optional[PlaylistTrack]) -> None:
"""
Set playlist tab colour for next track. self.next_track needs
to be set before calling.
"""
# If the original next playlist tab isn't the same as the
# new one or the current track, it needs its colour reset.
if ( if (
self.next_track.playlist_tab and old_next_track and
self.next_track.playlist_tab != next_track_playlist_tab and old_next_track.playlist_tab and
self.next_track.playlist_tab != self.current_track.playlist_tab old_next_track.playlist_tab not in [
): self.next_track.playlist_tab,
self.set_tab_colour(self.next_track.playlist_tab, self.current_track.playlist_tab
]):
self.set_tab_colour(old_next_track.playlist_tab,
QColor(Config.COLOUR_NORMAL_TAB)) QColor(Config.COLOUR_NORMAL_TAB))
# If the new next playlist tab isn't the same as the
# Discard now-incorrect next_track PlaylistTrack and tell # old one or the current track, it needs its colour set.
# playlist_tab too if old_next_track:
self.next_track.playlist_tab.clear_next() old_tab = old_next_track.playlist_tab
self.clear_next() else:
old_tab = None
# Populate self.next_track if (
self.next_track.set_plr(session, plr, next_track_playlist_tab) self.next_track and
if self.next_track.playlist_tab: self.next_track.playlist_tab and
if self.current_track.playlist_tab != self.next_track.playlist_tab: self.next_track.playlist_tab not in [
self.set_tab_colour(self.next_track.playlist_tab, old_tab,
QColor(Config.COLOUR_NEXT_TAB)) self.current_track.playlist_tab
]):
# Populate footer if we're not currently playing self.set_tab_colour(self.next_track.playlist_tab,
if not self.playing and self.next_track.track_id: QColor(Config.COLOUR_NEXT_TAB))
self.label_track_length.setText(
helpers.ms_to_mmss(self.next_track.duration)
)
self.label_fade_length.setText(helpers.ms_to_mmss(
self.next_track.fade_length))
# Update headers
self.update_headers()
# Populate 'info' tabs with Wikipedia info, but queue it because
# it isn't quick
track_title = self.next_track.title
QTimer.singleShot(
1, lambda: self.tabInfolist.open_in_wikipedia(track_title)
)
def tick(self) -> None: def tick(self) -> None:
""" """
@ -1809,7 +1837,7 @@ class DbDialog(QDialog):
"""Handle text typed in search box""" """Handle text typed in search box"""
self.ui.matchList.clear() self.ui.matchList.clear()
if len(s) > 1: if len(s) > 0:
if self.ui.radioTitle.isChecked(): if self.ui.radioTitle.isChecked():
matches = Tracks.search_titles(self.session, s) matches = Tracks.search_titles(self.session, s)
else: else:

View File

@ -191,13 +191,14 @@ class PlaylistTab(QTableWidget):
# Call self.eventFilter() for events # Call self.eventFilter() for events
self.viewport().installEventFilter(self) self.viewport().installEventFilter(self)
self.itemSelectionChanged.connect(self._select_event)
self.search_text: str = "" self.search_text: str = ""
self.edit_cell_type: Optional[int] self.edit_cell_type: Optional[int]
self.selecting_in_progress = False self.selecting_in_progress = False
# Connect signals # Connect signals
self.horizontalHeader().sectionResized.connect(self._column_resize) self.horizontalHeader().sectionResized.connect(self._column_resize)
self.itemSelectionChanged.connect(self._select_event)
self.signals.set_next_track_signal.connect(self._reset_next)
# Load playlist rows # Load playlist rows
self.populate_display(session, self.playlist_id) self.populate_display(session, self.playlist_id)
@ -463,7 +464,7 @@ class PlaylistTab(QTableWidget):
else: else:
return self.rowCount() return self.rowCount()
def get_selected_playlistrow_ids(self) -> Optional[List]: def get_selected_playlistrow_ids(self) -> list:
""" """
Return a list of PlaylistRow ids of the selected rows Return a list of PlaylistRow ids of the selected rows
""" """
@ -503,6 +504,9 @@ class PlaylistTab(QTableWidget):
else: else:
self.showRow(row_number) self.showRow(row_number)
# This causes scrolling, so ensure current track is visible
self.scroll_current_to_top()
def insert_header(self, session: scoped_session, note: str) -> None: def insert_header(self, session: scoped_session, note: str) -> None:
""" """
Insert section header into playlist tab. Insert section header into playlist tab.
@ -527,7 +531,7 @@ class PlaylistTab(QTableWidget):
Insert passed playlist row (plr) into playlist tab. Insert passed playlist row (plr) into playlist tab.
""" """
row_number = plr.row_number row_number = plr.plr_rownum
bold = True bold = True
self.insertRow(row_number) self.insertRow(row_number)
_ = self._set_row_plr_id(row_number, plr.id) _ = self._set_row_plr_id(row_number, plr.id)
@ -584,7 +588,8 @@ class PlaylistTab(QTableWidget):
self.playlist_id) self.playlist_id)
if existing_plr and ask_yes_no("Duplicate row", if existing_plr and ask_yes_no("Duplicate row",
"Track already in playlist. " "Track already in playlist. "
"Move to new location?"): "Move to new location?",
default_yes=True):
# Yes it is and we should reuse it # Yes it is and we should reuse it
# If we've been passed a note, we need to add that to the # If we've been passed a note, we need to add that to the
# existing track # existing track
@ -647,7 +652,8 @@ class PlaylistTab(QTableWidget):
# Set next track # Set next track
next_row = self._find_next_track_row(session, current_row + 1) next_row = self._find_next_track_row(session, current_row + 1)
if next_row: if next_row:
self._set_next(session, next_row) self.musicmuster.set_next_plr_id(self._get_row_plr_id(next_row),
self)
# Display row as current track # Display row as current track
self._set_row_colour_current(current_row) self._set_row_colour_current(current_row)
@ -689,7 +695,7 @@ class PlaylistTab(QTableWidget):
for plr in playlist.rows: for plr in playlist.rows:
self.insert_row(session, plr, update_track_times=False, self.insert_row(session, plr, update_track_times=False,
played=plr.row_number in played_rows) played=plr.plr_rownum in played_rows)
# Scroll to top # Scroll to top
if scroll_to_top: if scroll_to_top:
@ -732,10 +738,11 @@ class PlaylistTab(QTableWidget):
row_number = self._get_next_track_row_number() row_number = self._get_next_track_row_number()
if not row_number: if not row_number:
return return
self.musicmuster.clear_next()
self._set_row_colour_default(row_number) self._set_row_colour_default(row_number)
self.clear_selection() self.clear_selection()
self.musicmuster.set_next_plr_id(None, self)
def save_playlist(self, session: scoped_session) -> None: def save_playlist(self, session: scoped_session) -> None:
""" """
Get the PlaylistRow objects for each row in the display. Correct Get the PlaylistRow objects for each row in the display. Correct
@ -749,7 +756,7 @@ class PlaylistTab(QTableWidget):
plr = self._get_row_plr(session, row_number) plr = self._get_row_plr(session, row_number)
if not plr: if not plr:
continue continue
plr.row_number = row_number plr.plr_rownum = row_number
plr.playlist_id = self.playlist_id plr.playlist_id = self.playlist_id
# Any rows in the database for this playlist that has a row # Any rows in the database for this playlist that has a row
@ -868,14 +875,6 @@ class PlaylistTab(QTableWidget):
self.selectRow(row_number) self.selectRow(row_number)
def set_selected_as_next(self) -> None:
"""Sets the select track as next to play"""
row_number = self._get_selected_row()
if row_number is not None:
with Session() as session:
self._set_next(session, row_number)
def tab_visible(self) -> None: def tab_visible(self) -> None:
"""Called when tab becomes visible""" """Called when tab becomes visible"""
@ -890,7 +889,6 @@ class PlaylistTab(QTableWidget):
def _add_track(self, row_number: int) -> None: def _add_track(self, row_number: int) -> None:
"""Add a track to a section header making it a normal track row""" """Add a track to a section header making it a normal track row"""
print(f"_add_track({row_number=})")
with Session() as session: with Session() as session:
# Add track to playlist row # Add track to playlist row
plr = self._get_row_plr(session, row_number) plr = self._get_row_plr(session, row_number)
@ -984,8 +982,7 @@ class PlaylistTab(QTableWidget):
# Mark unplayed # Mark unplayed
if self._get_row_userdata(row_number, self.PLAYED): if self._get_row_userdata(row_number, self.PLAYED):
self._add_context_menu("Mark unplayed", self._add_context_menu("Mark unplayed", self._mark_unplayed)
lambda: self._mark_unplayed(row_number))
# Unmark as next # Unmark as next
if next_row: if next_row:
@ -1089,7 +1086,7 @@ class PlaylistTab(QTableWidget):
f"Really delete {row_count} row{plural}?"): f"Really delete {row_count} row{plural}?"):
return return
rows_to_delete = [plr.row_number for plr in plrs] rows_to_delete = [plr.plr_rownum for plr in plrs]
# Delete rows from database. Would be more efficient to # Delete rows from database. Would be more efficient to
# query then have a single delete. # query then have a single delete.
@ -1136,11 +1133,11 @@ class PlaylistTab(QTableWidget):
starting_row = 0 starting_row = 0
track_rows = [ track_rows = [
p.row_number for p in PlaylistRows.get_rows_with_tracks( p.plr_rownum for p in PlaylistRows.get_rows_with_tracks(
session, self.playlist_id) session, self.playlist_id)
] ]
played_rows = [ played_rows = [
p.row_number for p in PlaylistRows.get_played_rows( p.plr_rownum for p in PlaylistRows.get_played_rows(
session, self.playlist_id) session, self.playlist_id)
] ]
for row_number in range(starting_row, self.rowCount()): for row_number in range(starting_row, self.rowCount()):
@ -1197,8 +1194,8 @@ class PlaylistTab(QTableWidget):
""" """
return [ return [
p.row_number for p in PlaylistRows.get_played_rows( p.plr_rownum for p in PlaylistRows.get_played_rows(
session, self.playlist_id) if p.row_number is not None session, self.playlist_id) if p.plr_rownum is not None
] ]
def _get_row_artist(self, row_number: int) -> str: def _get_row_artist(self, row_number: int) -> str:
@ -1388,40 +1385,38 @@ class PlaylistTab(QTableWidget):
and pos.y() >= rect.center().y() # noqa W503 and pos.y() >= rect.center().y() # noqa W503
) )
def _mark_unplayed(self, row_number: int) -> None: def _mark_unplayed(self) -> None:
""" """
Mark passed row as unplayed in this playlist Mark selected rows as unplayed in this playlist
""" """
if row_number is None:
return
_ = self._set_row_userdata(row_number, self.PLAYED, False)
self._set_row_bold(row_number, True)
self.clear_selection()
with Session() as session: with Session() as session:
plr = self._get_row_plr(session, row_number) for row_number in self._get_selected_rows():
if not plr: _ = self._set_row_userdata(row_number, self.PLAYED, False)
return self._set_row_bold(row_number, True)
plr.played = False
plr = self._get_row_plr(session, row_number)
if not plr:
continue
plr.played = False
self._update_start_end_times(session) self._update_start_end_times(session)
self.hide_or_show_played_tracks() self.clear_selection()
self.hide_or_show_played_tracks()
def _move_row(self, session: scoped_session, plr: PlaylistRows, def _move_row(self, session: scoped_session, plr: PlaylistRows,
new_row_number: int) -> None: new_row_number: int) -> None:
"""Move playlist row to new_row_number using parent copy/paste""" """Move playlist row to new_row_number using parent copy/paste"""
if plr.row_number is None: if plr.plr_rownum is None:
return return
# Remove source row # Remove source row
self.removeRow(plr.row_number) self.removeRow(plr.plr_rownum)
# Fixup plr row number # Fixup plr row number
if plr.row_number < new_row_number: if plr.plr_rownum < new_row_number:
plr.row_number = new_row_number - 1 plr.plr_rownum = new_row_number - 1
else: else:
plr.row_number = new_row_number plr.plr_rownum = new_row_number
self.insert_row(session, plr) self.insert_row(session, plr)
self.save_playlist(session) self.save_playlist(session)
self.hide_or_show_played_tracks() self.hide_or_show_played_tracks()
@ -1712,53 +1707,42 @@ class PlaylistTab(QTableWidget):
return item return item
def _set_next(self, session: scoped_session, row_number: int) -> None: def _reset_next(self, old_plrid: int, new_plrid: int) -> None:
""" """
Set passed row as next playlist row to play. Called when set_next_track_signal signal received.
Actions required: Actions required:
- Check row has a track - If old_plrid points to this playlist:
- Check track is readable - Remove existing next track
- Notify musicmuster - If new_plrid points to this playlist:
- Display row as next track - Set track as next
- Update start/stop times - Display row as next track
- Update start/stop times
""" """
# Check row has a track with Session() as session:
track_id = self._get_row_track_id(row_number) # Get plrs
if not track_id: old_plr = new_plr = None
log.error( if old_plrid:
f"playlists._set_next({row_number=}) has no track associated" old_plr = session.get(PlaylistRows, old_plrid)
)
return
track = session.get(Tracks, track_id) # Unmark next track
if not track: if old_plr and old_plr.playlist_id == self.playlist_id:
log.error(f"playlists._set_next({row_number=}): Track not found") self._set_row_colour_default(old_plr.plr_rownum)
return
# Check track is readable # Mark next track
if file_is_unreadable(track.path): if new_plrid:
return None new_plr = session.get(PlaylistRows, new_plrid)
if not new_plr:
log.error(f"_reset_next({new_plrid=}): plr not found")
return
if new_plr.playlist_id == self.playlist_id:
self._set_row_colour_next(new_plr.plr_rownum)
# Clear any existing next track # Update start/stop times
next_track_row = self._get_next_track_row_number() self._update_start_end_times(session)
if next_track_row:
self._set_row_colour_default(next_track_row)
# Notify musicmuster
plr = self._get_row_plr(session, row_number)
if not plr:
log.debug(f"playists._set_next({row_number=}) can't retrieve plr")
else:
self.musicmuster.this_is_the_next_playlist_row(session, plr, self)
# Display row as next track
self._set_row_colour_next(row_number)
# Update start/stop times
self.clear_selection() self.clear_selection()
self._update_start_end_times(session)
def _set_played_row(self, session: scoped_session, def _set_played_row(self, session: scoped_session,
row_number: int) -> None: row_number: int) -> None:
@ -2098,7 +2082,7 @@ class PlaylistTab(QTableWidget):
""" """
plr_tracks = PlaylistRows.get_rows_with_tracks( plr_tracks = PlaylistRows.get_rows_with_tracks(
session, self.playlist_id, from_plr.row_number, to_plr.row_number) session, self.playlist_id, from_plr.plr_rownum, to_plr.plr_rownum)
total_time = 0 total_time = 0
total_time = sum([a.track.duration for a in plr_tracks total_time = sum([a.track.duration for a in plr_tracks
@ -2150,7 +2134,7 @@ class PlaylistTab(QTableWidget):
total_time = self._track_time_between_rows( total_time = self._track_time_between_rows(
session, from_plr, to_plr) session, from_plr, to_plr)
time_str = self._get_section_timing_string(total_time) time_str = self._get_section_timing_string(total_time)
self._set_row_header_text(session, from_plr.row_number, self._set_row_header_text(session, from_plr.plr_rownum,
from_plr.note + time_str) from_plr.note + time_str)
# Update section end # Update section end
@ -2160,12 +2144,12 @@ class PlaylistTab(QTableWidget):
section_header_cleanup_re, '', from_plr.note, section_header_cleanup_re, '', from_plr.note,
).strip() + "]" ).strip() + "]"
) )
self._set_row_header_text(session, to_plr.row_number, self._set_row_header_text(session, to_plr.plr_rownum,
new_text) new_text)
except IndexError: except IndexError:
# This ending row may have a time left from before a # This ending row may have a time left from before a
# starting row above was deleted, so replace content # starting row above was deleted, so replace content
self._set_row_header_text(session, plr.row_number, self._set_row_header_text(session, plr.plr_rownum,
plr.note) plr.note)
continue continue
@ -2179,7 +2163,7 @@ class PlaylistTab(QTableWidget):
from_plr, to_plr) from_plr, to_plr)
time_str = self._get_section_timing_string(total_time, time_str = self._get_section_timing_string(total_time,
no_end=True) no_end=True)
self._set_row_header_text(session, from_plr.row_number, self._set_row_header_text(session, from_plr.plr_rownum,
from_plr.note + time_str) from_plr.note + time_str)
def _update_start_end_times(self, session: scoped_session) -> None: def _update_start_end_times(self, session: scoped_session) -> None: