Move tracks between playlists
This commit is contained in:
parent
997627582f
commit
0465fb45c4
35
app/model.py
35
app/model.py
@ -200,16 +200,29 @@ class Playlists(Base):
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
session.query(Playlists)
|
session.query(Playlists)
|
||||||
.filter(Playlists.loaded == True)
|
.filter(Playlists.loaded == True) # noqa E712
|
||||||
.order_by(Playlists.last_used.desc())
|
.order_by(Playlists.last_used.desc())
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_playlists():
|
def get_all_playlists():
|
||||||
"Returns a list of (id, name) of all playlists"
|
"Returns a list of all playlists"
|
||||||
|
|
||||||
return session.query(Playlists).all()
|
return session.query(Playlists).all()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_all_closed_playlists():
|
||||||
|
"Returns a list of all playlists not currently open"
|
||||||
|
|
||||||
|
return (
|
||||||
|
session.query(Playlists)
|
||||||
|
.filter(
|
||||||
|
(Playlists.loaded == False) | # noqa E712
|
||||||
|
(Playlists.loaded == None)
|
||||||
|
)
|
||||||
|
.order_by(Playlists.last_used.desc())
|
||||||
|
).all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_name(plid):
|
def get_name(plid):
|
||||||
"""
|
"""
|
||||||
@ -271,6 +284,24 @@ class PlaylistTracks(Base):
|
|||||||
session.add(plt)
|
session.add(plt)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def move_track(from_playlist_id, row, to_playlist_id):
|
||||||
|
DEBUG(
|
||||||
|
f"PlaylistTracks.move_tracks({from_playlist_id=}, {row=}, "
|
||||||
|
f"{to_playlist_id=})"
|
||||||
|
)
|
||||||
|
new_row = (
|
||||||
|
session.query(func.max(PlaylistTracks.row)).filter(
|
||||||
|
PlaylistTracks.playlist_id == to_playlist_id).scalar()
|
||||||
|
) + 1
|
||||||
|
record = session.query(PlaylistTracks).filter(
|
||||||
|
PlaylistTracks.playlist_id == from_playlist_id,
|
||||||
|
PlaylistTracks.row == row
|
||||||
|
).one()
|
||||||
|
record.playlist_id = to_playlist_id
|
||||||
|
record.row = new_row
|
||||||
|
session.commit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remove_track(playlist_id, row):
|
def remove_track(playlist_id, row):
|
||||||
DEBUG(
|
DEBUG(
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import helpers
|
|||||||
import music
|
import music
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from model import Playdates, Playlists, Settings, Tracks
|
from model import Playdates, Playlists, PlaylistTracks, Settings, Tracks
|
||||||
from playlists import Playlist
|
from playlists import Playlist
|
||||||
from songdb import add_path_to_db
|
from songdb import add_path_to_db
|
||||||
from ui.dlg_search_database_ui import Ui_Dialog
|
from ui.dlg_search_database_ui import Ui_Dialog
|
||||||
@ -214,7 +214,42 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
playlist_table = Playlist()
|
playlist_table = Playlist()
|
||||||
playlist_table.db = playlist_db
|
playlist_table.db = playlist_db
|
||||||
playlist_table.populate()
|
playlist_table.populate()
|
||||||
self.tabPlaylist.addTab(playlist_table, playlist_db.name)
|
idx = self.tabPlaylist.addTab(playlist_table, playlist_db.name)
|
||||||
|
self.tabPlaylist.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
def move_selected(self):
|
||||||
|
"Move selected rows to another playlist"
|
||||||
|
|
||||||
|
playlists = list(
|
||||||
|
set(Playlists.get_all_playlists()) - {self.visible_playlist().db}
|
||||||
|
)
|
||||||
|
dlg = SelectPlaylistDialog(self, playlists=playlists)
|
||||||
|
dlg.exec()
|
||||||
|
if not dlg.plid:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If destination playlist is visible, we need to add the moved
|
||||||
|
# tracks to it. If not, they will be automatically loaded when
|
||||||
|
# the playlistis opened.
|
||||||
|
destination_playlist = None
|
||||||
|
for tab in range(self.tabPlaylist.count()):
|
||||||
|
if self.tabPlaylist.widget(tab).db.id == dlg.plid:
|
||||||
|
destination_playlist = self.tabPlaylist.widget(tab)
|
||||||
|
break
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for (row, track_id) in (
|
||||||
|
self.visible_playlist().get_selected_rows_and_tracks()):
|
||||||
|
rows.append(row)
|
||||||
|
# Update database
|
||||||
|
PlaylistTracks.move_track(
|
||||||
|
self.visible_playlist().db.id, row, dlg.plid)
|
||||||
|
# Update destination playlist if visible
|
||||||
|
if destination_playlist:
|
||||||
|
destination_playlist.add_track(Tracks.track_from_id(track_id))
|
||||||
|
|
||||||
|
# Update source playlist
|
||||||
|
self.visible_playlist().remove_rows(rows)
|
||||||
|
|
||||||
def play_next(self):
|
def play_next(self):
|
||||||
"""
|
"""
|
||||||
@ -293,12 +328,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
dlg = DbDialog(self)
|
dlg = DbDialog(self)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
|
|
||||||
def select_playlist(self):
|
def open_playlist(self):
|
||||||
# TODO don't show those that are currently open
|
playlists = Playlists.get_all_closed_playlists()
|
||||||
dlg = SelectPlaylistDialog(self)
|
dlg = SelectPlaylistDialog(self, playlists=playlists)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
playlist = Playlists.get_playlist_by_id(dlg.plid)
|
if dlg.plid:
|
||||||
self.load_playlist(playlist)
|
playlist = Playlists.open(dlg.plid)
|
||||||
|
self.load_playlist(playlist)
|
||||||
|
|
||||||
def set_next_track(self):
|
def set_next_track(self):
|
||||||
"Set selected track as next"
|
"Set selected track as next"
|
||||||
@ -353,7 +389,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.update_headers()
|
self.update_headers()
|
||||||
|
|
||||||
def tab_change(self):
|
def tab_change(self):
|
||||||
"User has changed tabs, so refresh next track"
|
"User has changed tabs"
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -566,13 +602,17 @@ class DbDialog(QDialog):
|
|||||||
|
|
||||||
|
|
||||||
class SelectPlaylistDialog(QDialog):
|
class SelectPlaylistDialog(QDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None, playlists=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
if playlists is None:
|
||||||
|
return
|
||||||
self.ui = Ui_dlgSelectPlaylist()
|
self.ui = Ui_dlgSelectPlaylist()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick)
|
self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick)
|
||||||
self.ui.buttonBox.accepted.connect(self.open)
|
self.ui.buttonBox.accepted.connect(self.open)
|
||||||
self.ui.buttonBox.rejected.connect(self.close)
|
self.ui.buttonBox.rejected.connect(self.close)
|
||||||
|
self.plid = None
|
||||||
|
|
||||||
record = Settings.get_int("select_playlist_dialog_width")
|
record = Settings.get_int("select_playlist_dialog_width")
|
||||||
width = record.f_int or 800
|
width = record.f_int or 800
|
||||||
@ -580,9 +620,7 @@ class SelectPlaylistDialog(QDialog):
|
|||||||
height = record.f_int or 600
|
height = record.f_int or 600
|
||||||
self.resize(width, height)
|
self.resize(width, height)
|
||||||
|
|
||||||
for (plid, plname) in [
|
for (plid, plname) in [(a.id, a.name) for a in playlists]:
|
||||||
(a.id, a.name) for a in Playlists.get_all_playlists()
|
|
||||||
]:
|
|
||||||
p = QListWidgetItem()
|
p = QListWidgetItem()
|
||||||
p.setText(plname)
|
p.setText(plname)
|
||||||
p.setData(Qt.UserRole, plid)
|
p.setData(Qt.UserRole, plid)
|
||||||
|
|||||||
@ -266,6 +266,32 @@ class Playlist(QTableWidget):
|
|||||||
next_row = self._meta_get_next()
|
next_row = self._meta_get_next()
|
||||||
return self._get_row_id(next_row)
|
return self._get_row_id(next_row)
|
||||||
|
|
||||||
|
def get_selected_rows_and_tracks(self):
|
||||||
|
"Return a list of selected (rows, track_id) tuples"
|
||||||
|
|
||||||
|
if not self.selectionModel().hasSelection():
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for row in [r.row() for r in self.selectionModel().selectedRows()]:
|
||||||
|
track_id = self._get_row_id(row)
|
||||||
|
result.append((row, track_id))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def remove_rows(self, rows):
|
||||||
|
"Remove rows passed in rows list"
|
||||||
|
|
||||||
|
# Row number will change as we delete rows. We could use
|
||||||
|
# QPersistentModelIndex, but easier just to remove them lowest
|
||||||
|
# row first
|
||||||
|
|
||||||
|
for row in sorted(rows, reverse=True):
|
||||||
|
self.removeRow(row)
|
||||||
|
|
||||||
|
self._repaint(save_playlist=False)
|
||||||
|
|
||||||
def get_selected_title(self):
|
def get_selected_title(self):
|
||||||
"Return title of selected row or None"
|
"Return title of selected row or None"
|
||||||
|
|
||||||
@ -322,6 +348,10 @@ class Playlist(QTableWidget):
|
|||||||
|
|
||||||
self._repaint()
|
self._repaint()
|
||||||
|
|
||||||
|
def repaint(self):
|
||||||
|
# Called when we change tabs
|
||||||
|
self._repaint(save_playlist=False)
|
||||||
|
|
||||||
def set_selected_as_next(self):
|
def set_selected_as_next(self):
|
||||||
"""
|
"""
|
||||||
Sets the selected track as the next track.
|
Sets the selected track as the next track.
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1114</width>
|
<width>1164</width>
|
||||||
<height>857</height>
|
<height>857</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@ -750,8 +750,8 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1114</width>
|
<width>1164</width>
|
||||||
<height>18</height>
|
<height>29</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
@ -773,6 +773,8 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
<addaction name="actionSearch_database"/>
|
<addaction name="actionSearch_database"/>
|
||||||
<addaction name="actionAdd_file"/>
|
<addaction name="actionAdd_file"/>
|
||||||
<addaction name="action_Clear_selection"/>
|
<addaction name="action_Clear_selection"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionMoveSelected"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menu_Tracks">
|
<widget class="QMenu" name="menu_Tracks">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -951,6 +953,11 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
<string>Dele&te...</string>
|
<string>Dele&te...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionMoveSelected">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mo&ve selected tracks to...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="icons.qrc"/>
|
<include location="icons.qrc"/>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user