Playlist creation and changing fragilely working
This commit is contained in:
parent
15a540234e
commit
67b3b804e1
37
app/model.py
37
app/model.py
@ -147,13 +147,26 @@ class Playlists(Base):
|
||||
def __repr__(self):
|
||||
return (f"<Playlist(id={self.id}, name={self.name}>")
|
||||
|
||||
# Currently we only support one playlist, so make that obvious from
|
||||
# function name
|
||||
@classmethod
|
||||
def get_playlist_by_name(cls, name):
|
||||
"Returns a playlist object for named playlist"
|
||||
@staticmethod
|
||||
def new(name):
|
||||
DEBUG(f"Playlists.new({name})")
|
||||
pl = Playlists()
|
||||
pl.name = name
|
||||
session.add(pl)
|
||||
session.commit()
|
||||
return pl.id
|
||||
|
||||
return session.query(Playlists).filter(Playlists.name == name).one()
|
||||
@staticmethod
|
||||
def get_all_playlists():
|
||||
"Returns a list of (id, name) of all playlists"
|
||||
|
||||
return session.query(Playlists).all()
|
||||
|
||||
@classmethod
|
||||
def get_playlist_by_id(cls, plid):
|
||||
"Returns a playlist object for playlist id"
|
||||
|
||||
return session.query(Playlists).filter(Playlists.id == plid).one()
|
||||
|
||||
def add_track(self, track, row):
|
||||
glue = PlaylistTracks(row=row)
|
||||
@ -178,14 +191,18 @@ class PlaylistTracks(Base):
|
||||
playlists = relationship("Playlists", back_populates="tracks")
|
||||
|
||||
@staticmethod
|
||||
def update_track_row(playlist_id, track_id, row):
|
||||
DEBUG(f"update_track({id}, {row})")
|
||||
def update_track_row(playlist_id, track_id, old_row, new_row):
|
||||
DEBUG(
|
||||
f"update_track_row({playlist_id}, "
|
||||
f"{track_id}, {old_row}, {new_row})"
|
||||
)
|
||||
|
||||
plt = session.query(PlaylistTracks).filter(
|
||||
PlaylistTracks.playlist_id == playlist_id,
|
||||
PlaylistTracks.track_id == track_id
|
||||
PlaylistTracks.track_id == track_id,
|
||||
PlaylistTracks.row == old_row
|
||||
).one()
|
||||
plt.row = row
|
||||
plt.row = new_row
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -7,21 +7,19 @@ import music
|
||||
from datetime import datetime, timedelta
|
||||
# from log import DEBUG
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtCore import QTimer
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
QDialog,
|
||||
QFileDialog,
|
||||
QInputDialog,
|
||||
QListWidgetItem,
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
)
|
||||
|
||||
import helpers
|
||||
|
||||
from config import Config
|
||||
from model import Playlists, Settings, Tracks
|
||||
from ui.dlg_search_database_ui import Ui_Dialog
|
||||
from model import Settings
|
||||
from ui.main_window_ui import Ui_MainWindow
|
||||
|
||||
|
||||
@ -47,10 +45,15 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
height = record.f_int or 981
|
||||
self.setGeometry(x, y, width, height)
|
||||
|
||||
# Hard code to the only playlist we have for now
|
||||
if self.playlist.load_playlist("Default"):
|
||||
# Hard code to the first playlist for now
|
||||
# TODO
|
||||
if self.playlist.load_playlist(1):
|
||||
self.update_headers()
|
||||
self.enable_play_next_controls()
|
||||
|
||||
self.plLabel = QLabel(f"Playlist: {self.playlist.playlist_name}")
|
||||
self.statusbar.addPermanentWidget(self.plLabel)
|
||||
|
||||
self.timer.start(Config.TIMER_MS)
|
||||
|
||||
def __del__(self):
|
||||
@ -90,6 +93,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
def connect_signals_slots(self):
|
||||
self.action_Clear_selection.triggered.connect(self.clear_selection)
|
||||
self.actionFade.triggered.connect(self.fade)
|
||||
self.actionNewPlaylist.triggered.connect(self.create_playlist)
|
||||
self.actionSelectPlaylist.triggered.connect(self.select_playlist)
|
||||
self.actionPlay_next.triggered.connect(self.play_next)
|
||||
self.actionSearch_database.triggered.connect(self.search_database)
|
||||
self.actionSkip_next.triggered.connect(self.play_next)
|
||||
@ -102,6 +107,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
self.timer.timeout.connect(self.tick)
|
||||
|
||||
def create_playlist(self):
|
||||
"Create new playlist"
|
||||
|
||||
dlg = QInputDialog(self)
|
||||
dlg.setInputMode(QInputDialog.TextInput)
|
||||
dlg.setLabelText("Playlist name:")
|
||||
dlg.resize(500, 100)
|
||||
ok = dlg.exec()
|
||||
if ok:
|
||||
self.playlist.create_playlist(dlg.textValue())
|
||||
|
||||
def disable_play_next_controls(self):
|
||||
self.actionPlay_next.setEnabled(False)
|
||||
|
||||
@ -143,8 +159,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
pass
|
||||
|
||||
def search_database(self):
|
||||
dlg = DbDialog(self)
|
||||
dlg.exec()
|
||||
self.playlist.search_database()
|
||||
|
||||
def select_playlist(self):
|
||||
self.playlist.select_playlist()
|
||||
|
||||
def set_next_track(self):
|
||||
"Set selected track as next"
|
||||
@ -237,62 +255,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
f"{self.playlist.get_next_artist()}"
|
||||
)
|
||||
|
||||
|
||||
class AddNoteDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_dlgAddNote()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
|
||||
class DbDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_Dialog()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.searchString.textEdited.connect(self.chars_typed)
|
||||
self.ui.matchList.itemDoubleClicked.connect(self.listdclick)
|
||||
|
||||
record = Settings.get_int("dbdialog_width")
|
||||
width = record.f_int or 800
|
||||
record = Settings.get_int("dbdialog_height")
|
||||
height = record.f_int or 600
|
||||
self.resize(width, height)
|
||||
|
||||
def __del__(self):
|
||||
record = Settings.get_int("dbdialog_height")
|
||||
if record.f_int != self.height():
|
||||
record.update({'f_int': self.height()})
|
||||
|
||||
record = Settings.get_int("dbdialog_width")
|
||||
if record.f_int != self.width():
|
||||
record.update({'f_int': self.width()})
|
||||
|
||||
def chars_typed(self, s):
|
||||
if len(s) >= 3:
|
||||
matches = Tracks.search_titles(s)
|
||||
self.ui.matchList.clear()
|
||||
if matches:
|
||||
for track in matches:
|
||||
t = QListWidgetItem()
|
||||
t.setText(
|
||||
f"{track.title} - {track.artist} "
|
||||
f"[{helpers.ms_to_mmss(track.duration)}]"
|
||||
)
|
||||
t.setData(Qt.UserRole, track.id)
|
||||
self.ui.matchList.addItem(t)
|
||||
|
||||
def listdclick(self, entry):
|
||||
track_id = entry.data(Qt.UserRole)
|
||||
track = Tracks.track_from_id(track_id)
|
||||
|
||||
# Store in current playlist in database
|
||||
db_playlist = Playlists.get_only_playlist()
|
||||
# TODO: hack position to be at end of list
|
||||
db_playlist.add_track(track, 99999)
|
||||
|
||||
# Add to on-screen playlist
|
||||
self.parent().add_to_playlist(track)
|
||||
def update_statusbar(self):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
110
app/playlists.py
110
app/playlists.py
@ -5,7 +5,9 @@ from PyQt5.QtGui import QColor, QDropEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QApplication,
|
||||
QDialog,
|
||||
QHBoxLayout,
|
||||
QListWidgetItem,
|
||||
QMenu,
|
||||
QMessageBox,
|
||||
QTableWidget,
|
||||
@ -19,6 +21,8 @@ from config import Config
|
||||
from datetime import datetime, timedelta
|
||||
from log import DEBUG, ERROR
|
||||
from model import Notes, Playdates, Playlists, PlaylistTracks, Settings, Tracks
|
||||
from ui.dlg_search_database_ui import Ui_Dialog
|
||||
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist
|
||||
|
||||
|
||||
class Playlist(QTableWidget):
|
||||
@ -199,6 +203,12 @@ class Playlist(QTableWidget):
|
||||
if repaint:
|
||||
self.repaint()
|
||||
|
||||
def create_playlist(self, name):
|
||||
"Create new playlist"
|
||||
|
||||
new_id = Playlists.new(name)
|
||||
self.load_playlist(new_id)
|
||||
|
||||
def drop_on(self, event):
|
||||
index = self.indexAt(event.pos())
|
||||
if not index.isValid():
|
||||
@ -327,21 +337,21 @@ class Playlist(QTableWidget):
|
||||
and pos.y() >= rect.center().y() # noqa W503
|
||||
)
|
||||
|
||||
def load_playlist(self, name):
|
||||
def load_playlist(self, plid):
|
||||
"""
|
||||
Load tracks and notes from named playlist.
|
||||
Load tracks and notes from playlist id.
|
||||
|
||||
Set first track as next track to play.
|
||||
|
||||
Return True if successful else False.
|
||||
"""
|
||||
|
||||
DEBUG(f"load_playlist({name})")
|
||||
DEBUG(f"load_playlist({plid})")
|
||||
|
||||
self.playlist_name = name
|
||||
|
||||
p = Playlists.get_playlist_by_name(name)
|
||||
self.playlist_id = p.id
|
||||
p = Playlists.get_playlist_by_id(plid)
|
||||
self.playlist_id = plid
|
||||
self.playlist_name = p.name
|
||||
self.parent().parent().update_statusbar()
|
||||
|
||||
# We need to retrieve playlist tracks and playlist notes, then
|
||||
# add them in row order. We don't mandate that an item will be
|
||||
@ -354,6 +364,9 @@ class Playlist(QTableWidget):
|
||||
for n in p.notes:
|
||||
data.append(([n.row], n))
|
||||
|
||||
# Clear playlist
|
||||
self.setRowCount(0)
|
||||
|
||||
# Now add data in row order
|
||||
for item in sorted(data, key=lambda x: x[0]):
|
||||
DEBUG(f"Adding {item}")
|
||||
@ -516,6 +529,14 @@ class Playlist(QTableWidget):
|
||||
# Update display
|
||||
self.tracks_changed()
|
||||
|
||||
def search_database(self):
|
||||
dlg = DbDialog(self)
|
||||
dlg.exec()
|
||||
|
||||
def select_playlist(self):
|
||||
dlg = SelectPlaylistDialog(self)
|
||||
dlg.exec()
|
||||
|
||||
def set_selected_as_next(self):
|
||||
"""
|
||||
Sets the selected track as the next track.
|
||||
@ -647,7 +668,7 @@ class Playlist(QTableWidget):
|
||||
# Get tracks and notes from database
|
||||
db_tracks = {}
|
||||
db_notes = {}
|
||||
p = Playlists.get_playlist_by_name(self.playlist_name)
|
||||
p = Playlists.get_playlist_by_id(self.playlist_id)
|
||||
|
||||
for track in p.tracks:
|
||||
db_tracks[track.track_id] = track.row
|
||||
@ -682,7 +703,8 @@ class Playlist(QTableWidget):
|
||||
if tracks[id] != db_tracks[id]:
|
||||
DEBUG(f"Update db track.id={id} in database")
|
||||
PlaylistTracks.update_track_row(
|
||||
self.playlist_id, id, row=tracks[id]
|
||||
self.playlist_id, track_id=id,
|
||||
old_row=db_tracks[id], new_row=tracks[id]
|
||||
)
|
||||
|
||||
def set_row_bold(self, row):
|
||||
@ -749,6 +771,76 @@ class Playlist(QTableWidget):
|
||||
self.repaint()
|
||||
|
||||
|
||||
class DbDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_Dialog()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.searchString.textEdited.connect(self.chars_typed)
|
||||
self.ui.matchList.itemDoubleClicked.connect(self.listdclick)
|
||||
|
||||
record = Settings.get_int("dbdialog_width")
|
||||
width = record.f_int or 800
|
||||
record = Settings.get_int("dbdialog_height")
|
||||
height = record.f_int or 600
|
||||
self.resize(width, height)
|
||||
|
||||
def __del__(self):
|
||||
record = Settings.get_int("dbdialog_height")
|
||||
if record.f_int != self.height():
|
||||
record.update({'f_int': self.height()})
|
||||
|
||||
record = Settings.get_int("dbdialog_width")
|
||||
if record.f_int != self.width():
|
||||
record.update({'f_int': self.width()})
|
||||
|
||||
def chars_typed(self, s):
|
||||
if len(s) >= 3:
|
||||
matches = Tracks.search_titles(s)
|
||||
self.ui.matchList.clear()
|
||||
if matches:
|
||||
for track in matches:
|
||||
t = QListWidgetItem()
|
||||
t.setText(
|
||||
f"{track.title} - {track.artist} "
|
||||
f"[{helpers.ms_to_mmss(track.duration)}]"
|
||||
)
|
||||
t.setData(Qt.UserRole, track.id)
|
||||
self.ui.matchList.addItem(t)
|
||||
|
||||
def listdclick(self, entry):
|
||||
track_id = entry.data(Qt.UserRole)
|
||||
track = Tracks.track_from_id(track_id)
|
||||
|
||||
# Store in current playlist in database
|
||||
db_playlist = Playlists.get_playlist_by_id(self.parent().playlist_id)
|
||||
# TODO: hack position to be at end of list
|
||||
db_playlist.add_track(track, 99999)
|
||||
|
||||
# Add to on-screen playlist
|
||||
self.parent().add_to_playlist(track)
|
||||
|
||||
|
||||
class SelectPlaylistDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_dlgSelectPlaylist()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.lstPlaylists.itemDoubleClicked.connect(self.listdclick)
|
||||
|
||||
for (plid, plname) in [
|
||||
(a.id, a.name) for a in Playlists.get_all_playlists()
|
||||
]:
|
||||
p = QListWidgetItem()
|
||||
p.setText(plname)
|
||||
p.setData(Qt.UserRole, plid)
|
||||
self.ui.lstPlaylists.addItem(p)
|
||||
|
||||
def listdclick(self, entry):
|
||||
plid = entry.data(Qt.UserRole)
|
||||
self.parent().load_playlist(plid)
|
||||
|
||||
|
||||
class Window(QWidget):
|
||||
def __init__(self):
|
||||
super(Window, self).__init__()
|
||||
|
||||
67
app/ui/dlg_SelectPlaylist.ui
Normal file
67
app/ui/dlg_SelectPlaylist.ui
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>dlgSelectPlaylist</class>
|
||||
<widget class="QDialog" name="dlgSelectPlaylist">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>276</width>
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="lstPlaylists"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>dlgSelectPlaylist</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>dlgSelectPlaylist</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>768</width>
|
||||
<width>931</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -710,7 +710,7 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>768</width>
|
||||
<width>931</width>
|
||||
<height>18</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -724,19 +724,26 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
<property name="title">
|
||||
<string>Pla&ylist</string>
|
||||
</property>
|
||||
<addaction name="actionPlay_next"/>
|
||||
<addaction name="actionSkip_next"/>
|
||||
<addaction name="actionSearch_database"/>
|
||||
<addaction name="actionAdd_file"/>
|
||||
<addaction name="action_Clear_selection"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSelectPlaylist"/>
|
||||
<addaction name="actionNewPlaylist"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Tracks">
|
||||
<property name="title">
|
||||
<string>&Tracks</string>
|
||||
</property>
|
||||
<addaction name="actionPlay_next"/>
|
||||
<addaction name="actionSkip_next"/>
|
||||
<addaction name="actionFade"/>
|
||||
<addaction name="actionS_top"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Resume_previous"/>
|
||||
<addaction name="action_Clear_selection"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuPlaylist"/>
|
||||
<addaction name="menu_Tracks"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar">
|
||||
<property name="enabled">
|
||||
@ -833,6 +840,16 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
<string>&Test</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSelectPlaylist">
|
||||
<property name="text">
|
||||
<string>Select &playlist</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNewPlaylist">
|
||||
<property name="text">
|
||||
<string>&New playlist</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user