Compare commits
No commits in common. "c5c5c28583e746559a8ca49e7dfe97ea43e63117" and "c9c47c3133a11c6693655b402601c154d18f07df" have entirely different histories.
c5c5c28583
...
c9c47c3133
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
12
.idea/dataSources.xml
Normal file
12
.idea/dataSources.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?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>
|
||||
3
.idea/dictionaries/kae.xml
Normal file
3
.idea/dictionaries/kae.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="kae" />
|
||||
</component>
|
||||
14
.idea/inspectionProfiles/Project_Default.xml
Normal file
14
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<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>
|
||||
7
.idea/inspectionProfiles/profiles_settings.xml
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
Normal file
7
.idea/misc.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?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>
|
||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
14
.idea/musicmuster.iml
Normal file
14
.idea/musicmuster.iml
Normal file
@ -0,0 +1,14 @@
|
||||
<?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>
|
||||
6
.idea/sqldialects.xml
Normal file
6
.idea/sqldialects.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="PROJECT" dialect="MariaDB" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -18,19 +18,12 @@ from tinytag import TinyTag # type: ignore
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
|
||||
def ask_yes_no(title: str, question: str, default_yes: bool = False) -> bool:
|
||||
def ask_yes_no(title: str, question: str) -> bool:
|
||||
"""Ask question; return True for yes, False for no"""
|
||||
|
||||
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_()
|
||||
button_reply = QMessageBox.question(None, title, question)
|
||||
|
||||
return button == QMessageBox.Yes
|
||||
return button_reply == QMessageBox.Yes
|
||||
|
||||
|
||||
def fade_point(
|
||||
|
||||
@ -203,7 +203,7 @@ class Playlists(Base):
|
||||
"PlaylistRows",
|
||||
back_populates="playlist",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="PlaylistRows.plr_rownum"
|
||||
order_by="PlaylistRows.row_number"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -372,7 +372,7 @@ class PlaylistRows(Base):
|
||||
__tablename__ = 'playlist_rows'
|
||||
|
||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
plr_rownum: int = Column(Integer, nullable=False)
|
||||
row_number: int = Column(Integer, nullable=False)
|
||||
note: str = Column(String(2048), index=False, default="", nullable=False)
|
||||
playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
|
||||
nullable=False)
|
||||
@ -385,7 +385,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}, plr_rownum={self.plr_rownum}>"
|
||||
f"note={self.note}, row_number={self.row_number}>"
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
@ -399,7 +399,7 @@ class PlaylistRows(Base):
|
||||
|
||||
self.playlist_id = playlist_id
|
||||
self.track_id = track_id
|
||||
self.plr_rownum = row_number
|
||||
self.row_number = row_number
|
||||
self.note = note
|
||||
session.add(self)
|
||||
session.flush()
|
||||
@ -425,7 +425,7 @@ class PlaylistRows(Base):
|
||||
).scalars().all()
|
||||
|
||||
for plr in src_rows:
|
||||
PlaylistRows(session, dst_id, plr.track_id, plr.plr_rownum,
|
||||
PlaylistRows(session, dst_id, plr.track_id, plr.row_number,
|
||||
plr.note)
|
||||
|
||||
@staticmethod
|
||||
@ -440,7 +440,7 @@ class PlaylistRows(Base):
|
||||
delete(PlaylistRows)
|
||||
.where(
|
||||
PlaylistRows.playlist_id == playlist_id,
|
||||
PlaylistRows.plr_rownum > maxrow
|
||||
PlaylistRows.row_number > maxrow
|
||||
)
|
||||
)
|
||||
session.flush()
|
||||
@ -454,11 +454,11 @@ class PlaylistRows(Base):
|
||||
plrs = session.execute(
|
||||
select(PlaylistRows)
|
||||
.where(PlaylistRows.playlist_id == playlist_id)
|
||||
.order_by(PlaylistRows.plr_rownum)
|
||||
.order_by(PlaylistRows.row_number)
|
||||
).scalars().all()
|
||||
|
||||
for i, plr in enumerate(plrs):
|
||||
plr.plr_rownum = i
|
||||
plr.row_number = i
|
||||
|
||||
# Ensure new row numbers are available to the caller
|
||||
session.commit()
|
||||
@ -476,7 +476,7 @@ class PlaylistRows(Base):
|
||||
.where(
|
||||
cls.playlist_id == playlist_id,
|
||||
cls.id.in_(plr_ids)
|
||||
).order_by(cls.plr_rownum)).scalars().all()
|
||||
).order_by(cls.row_number)).scalars().all()
|
||||
|
||||
return plrs
|
||||
|
||||
@ -486,7 +486,7 @@ class PlaylistRows(Base):
|
||||
"""Return the last used row for playlist, or None if no rows"""
|
||||
|
||||
return session.execute(
|
||||
select(func.max(PlaylistRows.plr_rownum))
|
||||
select(func.max(PlaylistRows.row_number))
|
||||
.where(PlaylistRows.playlist_id == playlist_id)
|
||||
).scalar_one()
|
||||
|
||||
@ -518,7 +518,7 @@ class PlaylistRows(Base):
|
||||
cls.playlist_id == playlist_id,
|
||||
cls.played.is_(True)
|
||||
)
|
||||
.order_by(cls.plr_rownum)
|
||||
.order_by(cls.row_number)
|
||||
).scalars().all()
|
||||
|
||||
return plrs
|
||||
@ -538,12 +538,12 @@ class PlaylistRows(Base):
|
||||
cls.track_id.is_not(None)
|
||||
)
|
||||
if from_row is not None:
|
||||
query = query.where(cls.plr_rownum >= from_row)
|
||||
query = query.where(cls.row_number >= from_row)
|
||||
if to_row is not None:
|
||||
query = query.where(cls.plr_rownum <= to_row)
|
||||
query = query.where(cls.row_number <= to_row)
|
||||
|
||||
plrs = (
|
||||
session.execute((query).order_by(cls.plr_rownum)).scalars().all()
|
||||
session.execute((query).order_by(cls.row_number)).scalars().all()
|
||||
)
|
||||
|
||||
return plrs
|
||||
@ -563,7 +563,7 @@ class PlaylistRows(Base):
|
||||
cls.track_id.is_not(None),
|
||||
cls.played.is_(False)
|
||||
)
|
||||
.order_by(cls.plr_rownum)
|
||||
.order_by(cls.row_number)
|
||||
).scalars().all()
|
||||
|
||||
return plrs
|
||||
@ -580,9 +580,9 @@ class PlaylistRows(Base):
|
||||
update(PlaylistRows)
|
||||
.where(
|
||||
(PlaylistRows.playlist_id == playlist_id),
|
||||
(PlaylistRows.plr_rownum >= starting_row)
|
||||
(PlaylistRows.row_number >= starting_row)
|
||||
)
|
||||
.values(plr_rownum=PlaylistRows.plr_rownum + move_by)
|
||||
.values(row_number=PlaylistRows.row_number + move_by)
|
||||
)
|
||||
|
||||
|
||||
@ -731,7 +731,7 @@ class Tracks(Base):
|
||||
return (
|
||||
session.execute(
|
||||
select(cls)
|
||||
.where(cls.title.like(f"{text}%"))
|
||||
.where(cls.title.ilike(f"%{text}%"))
|
||||
.order_by(cls.title)
|
||||
)
|
||||
.scalars()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
from log import log
|
||||
from os.path import basename
|
||||
@ -142,7 +142,7 @@ class CartButton(QPushButton):
|
||||
class PlaylistTrack:
|
||||
"""
|
||||
Used to provide a single reference point for specific playlist tracks,
|
||||
typically the previous, current and next track.
|
||||
typicall the previous, current and next track.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
@ -257,7 +257,7 @@ class MusicMusterSignals(QObject):
|
||||
emit-a-signal-from-another-class-to-main-class
|
||||
"""
|
||||
|
||||
set_next_track_signal = pyqtSignal(int, int)
|
||||
update_row_note_signal = pyqtSignal(int)
|
||||
|
||||
|
||||
class Window(QMainWindow, Ui_MainWindow):
|
||||
@ -579,7 +579,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.actionSelect_previous_track.triggered.connect(
|
||||
self.select_previous_row)
|
||||
self.actionMoveUnplayed.triggered.connect(self.move_unplayed)
|
||||
self.actionSetNext.triggered.connect(self.set_selected_track_next)
|
||||
self.actionSetNext.triggered.connect(
|
||||
lambda: self.tabPlaylist.currentWidget().set_selected_as_next())
|
||||
self.actionSkipToNext.triggered.connect(self.play_next)
|
||||
self.actionStop.triggered.connect(self.stop)
|
||||
self.btnDrop3db.clicked.connect(self.drop3db)
|
||||
@ -997,8 +998,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.next_track.plr_id]
|
||||
]
|
||||
|
||||
rows_to_delete = [plr.plr_rownum for plr in plrs_to_move
|
||||
if plr.plr_rownum is not None]
|
||||
rows_to_delete = [plr.row_number for plr in plrs_to_move
|
||||
if plr.row_number is not None]
|
||||
if not rows_to_delete:
|
||||
return
|
||||
|
||||
@ -1028,7 +1029,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
next_row = 0
|
||||
|
||||
for plr in plrs_to_move:
|
||||
plr.plr_rownum = next_row
|
||||
plr.row_number = next_row
|
||||
next_row += 1
|
||||
plr.playlist_id = destination_playlist_id
|
||||
# Reset played as it's not been played on this playlist
|
||||
@ -1150,7 +1151,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
if not src_playlist_id:
|
||||
src_playlist_id = plr.playlist_id
|
||||
plr.playlist_id = dst_playlist_id
|
||||
plr.plr_rownum = row
|
||||
plr.row_number = row
|
||||
row += 1
|
||||
|
||||
if not src_playlist_id:
|
||||
@ -1284,45 +1285,43 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
def resume(self) -> None:
|
||||
"""
|
||||
Resume playing last track. We may be playing the next track
|
||||
or none; take care of both eventualities.
|
||||
Resume playing stopped track
|
||||
|
||||
Actions required:
|
||||
- Return if no saved position
|
||||
- Resume last track
|
||||
- If a track is playing, make that the next track
|
||||
- Store saved position
|
||||
- Store next track
|
||||
- Set previous track to be next track
|
||||
- Call play_next() from saved position
|
||||
- Reset next track
|
||||
"""
|
||||
|
||||
# Return if no saved position
|
||||
if not self.previous_track_position:
|
||||
return
|
||||
|
||||
# Note any playing track as this will become the next track
|
||||
playing_track = None
|
||||
if self.current_track.track_id:
|
||||
playing_track = self.current_track
|
||||
# Note resume point
|
||||
resume_from = self.previous_track_position
|
||||
|
||||
# Remember what was to have been the next track
|
||||
original_next_plr_id = self.next_track.plr_id
|
||||
original_next_plr_playlist_tab = self.next_track.playlist_tab
|
||||
|
||||
with Session() as session:
|
||||
# Set next plr to be track to resume
|
||||
if not self.previous_track.plr_id:
|
||||
return
|
||||
if not self.previous_track.playlist_tab:
|
||||
return
|
||||
# Set next track to be the last one played
|
||||
self.next_track = self.previous_track
|
||||
self.previous_track = PlaylistTrack()
|
||||
|
||||
# Resume last track
|
||||
self.set_next_plr_id(self.previous_track.plr_id,
|
||||
self.previous_track.playlist_tab)
|
||||
self.play_next(self.previous_track_position)
|
||||
self.play_next(resume_from)
|
||||
|
||||
# If a track was playing when we were called, get details to
|
||||
# set it as the next track
|
||||
if playing_track:
|
||||
if not playing_track.plr_id:
|
||||
# Reset next track if there was one
|
||||
if original_next_plr_id:
|
||||
next_plr = session.get(PlaylistRows, original_next_plr_id)
|
||||
if not next_plr or not original_next_plr_playlist_tab:
|
||||
return
|
||||
if not playing_track.playlist_tab:
|
||||
return
|
||||
self.set_next_plr_id(playing_track.plr_id,
|
||||
playing_track.playlist_tab)
|
||||
self.this_is_the_next_playlist_row(
|
||||
session, next_plr, original_next_plr_playlist_tab)
|
||||
|
||||
def save_as_template(self) -> None:
|
||||
"""Save current playlist as template"""
|
||||
@ -1495,94 +1494,67 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# May also be called when last tab is closed
|
||||
pass
|
||||
|
||||
def set_selected_track_next(self) -> None:
|
||||
def this_is_the_next_playlist_row(
|
||||
self, session: scoped_session, plr: PlaylistRows,
|
||||
next_track_playlist_tab: PlaylistTab) -> None:
|
||||
"""
|
||||
Set currently-selected row on visible playlist tab as next track
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
self.set_next_plr_id(selected_plr_ids[0], playlist_tab)
|
||||
|
||||
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
|
||||
This is notification from a playlist tab that it holds the next
|
||||
playlist row to be played.
|
||||
|
||||
Actions required:
|
||||
- Update self.next_track PlaylistTrack structure
|
||||
- Tell playlist tabs to update their 'next track' highlighting
|
||||
- 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
|
||||
- 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)
|
||||
if plr.track_id is None:
|
||||
return
|
||||
|
||||
# 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 we already have a next tab lined up and it's neither
|
||||
# the "new" next tab nor the current track tab then we need
|
||||
# to reset the tab colour.
|
||||
if (
|
||||
old_next_track and
|
||||
old_next_track.playlist_tab and
|
||||
old_next_track.playlist_tab not in [
|
||||
self.next_track.playlist_tab,
|
||||
self.current_track.playlist_tab
|
||||
]):
|
||||
self.set_tab_colour(old_next_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NORMAL_TAB))
|
||||
# If the new next playlist tab isn't the same as the
|
||||
# old one or the current track, it needs its colour set.
|
||||
if old_next_track:
|
||||
old_tab = old_next_track.playlist_tab
|
||||
else:
|
||||
old_tab = None
|
||||
if (
|
||||
self.next_track and
|
||||
self.next_track.playlist_tab and
|
||||
self.next_track.playlist_tab not in [
|
||||
old_tab,
|
||||
self.current_track.playlist_tab
|
||||
]):
|
||||
self.next_track.playlist_tab != next_track_playlist_tab and
|
||||
self.next_track.playlist_tab != self.current_track.playlist_tab
|
||||
):
|
||||
self.set_tab_colour(self.next_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NEXT_TAB))
|
||||
QColor(Config.COLOUR_NORMAL_TAB))
|
||||
|
||||
# Discard now-incorrect next_track PlaylistTrack and tell
|
||||
# playlist_tab too
|
||||
self.next_track.playlist_tab.clear_next()
|
||||
self.clear_next()
|
||||
|
||||
# Populate self.next_track
|
||||
self.next_track.set_plr(session, plr, next_track_playlist_tab)
|
||||
if self.next_track.playlist_tab:
|
||||
if self.current_track.playlist_tab != self.next_track.playlist_tab:
|
||||
self.set_tab_colour(self.next_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NEXT_TAB))
|
||||
|
||||
# Populate footer if we're not currently playing
|
||||
if not self.playing and self.next_track.track_id:
|
||||
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:
|
||||
"""
|
||||
@ -1837,7 +1809,7 @@ class DbDialog(QDialog):
|
||||
"""Handle text typed in search box"""
|
||||
|
||||
self.ui.matchList.clear()
|
||||
if len(s) > 0:
|
||||
if len(s) > 1:
|
||||
if self.ui.radioTitle.isChecked():
|
||||
matches = Tracks.search_titles(self.session, s)
|
||||
else:
|
||||
|
||||
156
app/playlists.py
156
app/playlists.py
@ -191,14 +191,13 @@ class PlaylistTab(QTableWidget):
|
||||
# Call self.eventFilter() for events
|
||||
self.viewport().installEventFilter(self)
|
||||
|
||||
self.itemSelectionChanged.connect(self._select_event)
|
||||
|
||||
self.search_text: str = ""
|
||||
self.edit_cell_type: Optional[int]
|
||||
self.selecting_in_progress = False
|
||||
|
||||
# Connect signals
|
||||
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
|
||||
self.populate_display(session, self.playlist_id)
|
||||
@ -464,7 +463,7 @@ class PlaylistTab(QTableWidget):
|
||||
else:
|
||||
return self.rowCount()
|
||||
|
||||
def get_selected_playlistrow_ids(self) -> list:
|
||||
def get_selected_playlistrow_ids(self) -> Optional[List]:
|
||||
"""
|
||||
Return a list of PlaylistRow ids of the selected rows
|
||||
"""
|
||||
@ -504,9 +503,6 @@ class PlaylistTab(QTableWidget):
|
||||
else:
|
||||
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:
|
||||
"""
|
||||
Insert section header into playlist tab.
|
||||
@ -531,7 +527,7 @@ class PlaylistTab(QTableWidget):
|
||||
Insert passed playlist row (plr) into playlist tab.
|
||||
"""
|
||||
|
||||
row_number = plr.plr_rownum
|
||||
row_number = plr.row_number
|
||||
bold = True
|
||||
self.insertRow(row_number)
|
||||
_ = self._set_row_plr_id(row_number, plr.id)
|
||||
@ -588,8 +584,7 @@ class PlaylistTab(QTableWidget):
|
||||
self.playlist_id)
|
||||
if existing_plr and ask_yes_no("Duplicate row",
|
||||
"Track already in playlist. "
|
||||
"Move to new location?",
|
||||
default_yes=True):
|
||||
"Move to new location?"):
|
||||
# Yes it is and we should reuse it
|
||||
# If we've been passed a note, we need to add that to the
|
||||
# existing track
|
||||
@ -652,8 +647,7 @@ class PlaylistTab(QTableWidget):
|
||||
# Set next track
|
||||
next_row = self._find_next_track_row(session, current_row + 1)
|
||||
if next_row:
|
||||
self.musicmuster.set_next_plr_id(self._get_row_plr_id(next_row),
|
||||
self)
|
||||
self._set_next(session, next_row)
|
||||
|
||||
# Display row as current track
|
||||
self._set_row_colour_current(current_row)
|
||||
@ -695,7 +689,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
for plr in playlist.rows:
|
||||
self.insert_row(session, plr, update_track_times=False,
|
||||
played=plr.plr_rownum in played_rows)
|
||||
played=plr.row_number in played_rows)
|
||||
|
||||
# Scroll to top
|
||||
if scroll_to_top:
|
||||
@ -738,11 +732,10 @@ class PlaylistTab(QTableWidget):
|
||||
row_number = self._get_next_track_row_number()
|
||||
if not row_number:
|
||||
return
|
||||
self.musicmuster.clear_next()
|
||||
self._set_row_colour_default(row_number)
|
||||
self.clear_selection()
|
||||
|
||||
self.musicmuster.set_next_plr_id(None, self)
|
||||
|
||||
def save_playlist(self, session: scoped_session) -> None:
|
||||
"""
|
||||
Get the PlaylistRow objects for each row in the display. Correct
|
||||
@ -756,7 +749,7 @@ class PlaylistTab(QTableWidget):
|
||||
plr = self._get_row_plr(session, row_number)
|
||||
if not plr:
|
||||
continue
|
||||
plr.plr_rownum = row_number
|
||||
plr.row_number = row_number
|
||||
plr.playlist_id = self.playlist_id
|
||||
|
||||
# Any rows in the database for this playlist that has a row
|
||||
@ -875,6 +868,14 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
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:
|
||||
"""Called when tab becomes visible"""
|
||||
|
||||
@ -889,6 +890,7 @@ class PlaylistTab(QTableWidget):
|
||||
def _add_track(self, row_number: int) -> None:
|
||||
"""Add a track to a section header making it a normal track row"""
|
||||
|
||||
print(f"_add_track({row_number=})")
|
||||
with Session() as session:
|
||||
# Add track to playlist row
|
||||
plr = self._get_row_plr(session, row_number)
|
||||
@ -982,7 +984,8 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Mark unplayed
|
||||
if self._get_row_userdata(row_number, self.PLAYED):
|
||||
self._add_context_menu("Mark unplayed", self._mark_unplayed)
|
||||
self._add_context_menu("Mark unplayed",
|
||||
lambda: self._mark_unplayed(row_number))
|
||||
|
||||
# Unmark as next
|
||||
if next_row:
|
||||
@ -1086,7 +1089,7 @@ class PlaylistTab(QTableWidget):
|
||||
f"Really delete {row_count} row{plural}?"):
|
||||
return
|
||||
|
||||
rows_to_delete = [plr.plr_rownum for plr in plrs]
|
||||
rows_to_delete = [plr.row_number for plr in plrs]
|
||||
|
||||
# Delete rows from database. Would be more efficient to
|
||||
# query then have a single delete.
|
||||
@ -1133,11 +1136,11 @@ class PlaylistTab(QTableWidget):
|
||||
starting_row = 0
|
||||
|
||||
track_rows = [
|
||||
p.plr_rownum for p in PlaylistRows.get_rows_with_tracks(
|
||||
p.row_number for p in PlaylistRows.get_rows_with_tracks(
|
||||
session, self.playlist_id)
|
||||
]
|
||||
played_rows = [
|
||||
p.plr_rownum for p in PlaylistRows.get_played_rows(
|
||||
p.row_number for p in PlaylistRows.get_played_rows(
|
||||
session, self.playlist_id)
|
||||
]
|
||||
for row_number in range(starting_row, self.rowCount()):
|
||||
@ -1194,8 +1197,8 @@ class PlaylistTab(QTableWidget):
|
||||
"""
|
||||
|
||||
return [
|
||||
p.plr_rownum for p in PlaylistRows.get_played_rows(
|
||||
session, self.playlist_id) if p.plr_rownum is not None
|
||||
p.row_number for p in PlaylistRows.get_played_rows(
|
||||
session, self.playlist_id) if p.row_number is not None
|
||||
]
|
||||
|
||||
def _get_row_artist(self, row_number: int) -> str:
|
||||
@ -1385,38 +1388,40 @@ class PlaylistTab(QTableWidget):
|
||||
and pos.y() >= rect.center().y() # noqa W503
|
||||
)
|
||||
|
||||
def _mark_unplayed(self) -> None:
|
||||
def _mark_unplayed(self, row_number: int) -> None:
|
||||
"""
|
||||
Mark selected rows as unplayed in this playlist
|
||||
Mark passed row 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:
|
||||
for row_number in self._get_selected_rows():
|
||||
_ = self._set_row_userdata(row_number, self.PLAYED, False)
|
||||
self._set_row_bold(row_number, True)
|
||||
|
||||
plr = self._get_row_plr(session, row_number)
|
||||
if not plr:
|
||||
continue
|
||||
plr.played = False
|
||||
plr = self._get_row_plr(session, row_number)
|
||||
if not plr:
|
||||
return
|
||||
plr.played = False
|
||||
self._update_start_end_times(session)
|
||||
self.clear_selection()
|
||||
self.hide_or_show_played_tracks()
|
||||
self.hide_or_show_played_tracks()
|
||||
|
||||
def _move_row(self, session: scoped_session, plr: PlaylistRows,
|
||||
new_row_number: int) -> None:
|
||||
"""Move playlist row to new_row_number using parent copy/paste"""
|
||||
|
||||
if plr.plr_rownum is None:
|
||||
if plr.row_number is None:
|
||||
return
|
||||
|
||||
# Remove source row
|
||||
self.removeRow(plr.plr_rownum)
|
||||
self.removeRow(plr.row_number)
|
||||
# Fixup plr row number
|
||||
if plr.plr_rownum < new_row_number:
|
||||
plr.plr_rownum = new_row_number - 1
|
||||
if plr.row_number < new_row_number:
|
||||
plr.row_number = new_row_number - 1
|
||||
else:
|
||||
plr.plr_rownum = new_row_number
|
||||
plr.row_number = new_row_number
|
||||
self.insert_row(session, plr)
|
||||
self.save_playlist(session)
|
||||
self.hide_or_show_played_tracks()
|
||||
@ -1707,42 +1712,53 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
return item
|
||||
|
||||
def _reset_next(self, old_plrid: int, new_plrid: int) -> None:
|
||||
def _set_next(self, session: scoped_session, row_number: int) -> None:
|
||||
"""
|
||||
Called when set_next_track_signal signal received.
|
||||
Set passed row as next playlist row to play.
|
||||
|
||||
Actions required:
|
||||
- If old_plrid points to this playlist:
|
||||
- Remove existing next track
|
||||
- If new_plrid points to this playlist:
|
||||
- Set track as next
|
||||
- Display row as next track
|
||||
- Update start/stop times
|
||||
- Check row has a track
|
||||
- Check track is readable
|
||||
- Notify musicmuster
|
||||
- Display row as next track
|
||||
- Update start/stop times
|
||||
"""
|
||||
|
||||
with Session() as session:
|
||||
# Get plrs
|
||||
old_plr = new_plr = None
|
||||
if old_plrid:
|
||||
old_plr = session.get(PlaylistRows, old_plrid)
|
||||
# Check row has a track
|
||||
track_id = self._get_row_track_id(row_number)
|
||||
if not track_id:
|
||||
log.error(
|
||||
f"playlists._set_next({row_number=}) has no track associated"
|
||||
)
|
||||
return
|
||||
|
||||
# Unmark next track
|
||||
if old_plr and old_plr.playlist_id == self.playlist_id:
|
||||
self._set_row_colour_default(old_plr.plr_rownum)
|
||||
track = session.get(Tracks, track_id)
|
||||
if not track:
|
||||
log.error(f"playlists._set_next({row_number=}): Track not found")
|
||||
return
|
||||
|
||||
# Mark next track
|
||||
if new_plrid:
|
||||
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)
|
||||
# Check track is readable
|
||||
if file_is_unreadable(track.path):
|
||||
return None
|
||||
|
||||
# Update start/stop times
|
||||
self._update_start_end_times(session)
|
||||
# Clear any existing next track
|
||||
next_track_row = self._get_next_track_row_number()
|
||||
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._update_start_end_times(session)
|
||||
|
||||
def _set_played_row(self, session: scoped_session,
|
||||
row_number: int) -> None:
|
||||
@ -2082,7 +2098,7 @@ class PlaylistTab(QTableWidget):
|
||||
"""
|
||||
|
||||
plr_tracks = PlaylistRows.get_rows_with_tracks(
|
||||
session, self.playlist_id, from_plr.plr_rownum, to_plr.plr_rownum)
|
||||
session, self.playlist_id, from_plr.row_number, to_plr.row_number)
|
||||
|
||||
total_time = 0
|
||||
total_time = sum([a.track.duration for a in plr_tracks
|
||||
@ -2134,7 +2150,7 @@ class PlaylistTab(QTableWidget):
|
||||
total_time = self._track_time_between_rows(
|
||||
session, from_plr, to_plr)
|
||||
time_str = self._get_section_timing_string(total_time)
|
||||
self._set_row_header_text(session, from_plr.plr_rownum,
|
||||
self._set_row_header_text(session, from_plr.row_number,
|
||||
from_plr.note + time_str)
|
||||
|
||||
# Update section end
|
||||
@ -2144,12 +2160,12 @@ class PlaylistTab(QTableWidget):
|
||||
section_header_cleanup_re, '', from_plr.note,
|
||||
).strip() + "]"
|
||||
)
|
||||
self._set_row_header_text(session, to_plr.plr_rownum,
|
||||
self._set_row_header_text(session, to_plr.row_number,
|
||||
new_text)
|
||||
except IndexError:
|
||||
# This ending row may have a time left from before a
|
||||
# starting row above was deleted, so replace content
|
||||
self._set_row_header_text(session, plr.plr_rownum,
|
||||
self._set_row_header_text(session, plr.row_number,
|
||||
plr.note)
|
||||
continue
|
||||
|
||||
@ -2163,7 +2179,7 @@ class PlaylistTab(QTableWidget):
|
||||
from_plr, to_plr)
|
||||
time_str = self._get_section_timing_string(total_time,
|
||||
no_end=True)
|
||||
self._set_row_header_text(session, from_plr.plr_rownum,
|
||||
self._set_row_header_text(session, from_plr.row_number,
|
||||
from_plr.note + time_str)
|
||||
|
||||
def _update_start_end_times(self, session: scoped_session) -> None:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user