[gnome-music] playlistview: Reorder a playlist
- From: Marinus Schraal <mschraal src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music] playlistview: Reorder a playlist
- Date: Sun, 4 Feb 2018 23:40:44 +0000 (UTC)
commit 094f1f5cbdde73c5d8625721360d2dff4d87bc9d
Author: Jean Felder <jean felder gmail com>
Date: Sun Jan 28 21:54:10 2018 +0100
playlistview: Reorder a playlist
Add drag and drop support
Static playlists can not be reordered
Closes: #108
gnomemusic/playlists.py | 21 +++++++
gnomemusic/query.py | 23 +++++++
gnomemusic/views/playlistview.py | 132 ++++++++++++++++++++++++++++++++-------
3 files changed, 153 insertions(+), 23 deletions(-)
---
diff --git a/gnomemusic/playlists.py b/gnomemusic/playlists.py
index ed32717..6d202b4 100644
--- a/gnomemusic/playlists.py
+++ b/gnomemusic/playlists.py
@@ -126,6 +126,9 @@ class Playlists(GObject.GObject):
'song-removed-from-playlist': (
GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, Grl.Media)
),
+ 'song-position-changed': (
+ GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, Grl.Media)
+ ),
}
instance = None
@@ -428,6 +431,24 @@ class Playlists(GObject.GObject):
None, update_callback, item
)
+ @log
+ def reorder_playlist(self, playlist, items, new_positions):
+ """Change the order of songs on a playlist.
+
+ :param GlrMedia playlist: playlist to reorder
+ :param list items: songs to reorder
+ :param list new_positions: new songs positions
+ """
+ def update_callback(conn, res, data):
+ conn.update_finish(res)
+ self.emit('song-position-changed', playlist, data)
+
+ for item, new_position in zip(items, new_positions):
+ self.tracker.update_async(
+ Query.change_song_position(
+ playlist.get_id(), item.get_id(), new_position),
+ GLib.PRIORITY_LOW, None, update_callback, item)
+
@log
def is_static_playlist(self, playlist):
"""Checks whether the given playlist is static or not
diff --git a/gnomemusic/query.py b/gnomemusic/query.py
index c4ff40c..5632868 100644
--- a/gnomemusic/query.py
+++ b/gnomemusic/query.py
@@ -606,6 +606,29 @@ class Query():
}
return query
+ @staticmethod
+ def change_song_position(playlist_id, song_id, new_position):
+ query = """
+ INSERT OR REPLACE {
+ ?entry
+ nfo:listPosition %(position)s
+ }
+ WHERE {
+ ?playlist a nmm:Playlist ;
+ a nfo:MediaList ;
+ nfo:hasMediaFileListEntry ?entry .
+ FILTER (
+ tracker:id(?playlist) = %(playlist_id)s &&
+ tracker:id(?entry) = %(song_id)s
+ )
+ }
+ """.replace("\n", " ").strip() % {
+ 'playlist_id': playlist_id,
+ 'song_id': song_id,
+ 'position': float(new_position)
+ }
+ return query
+
@staticmethod
def get_playlist_with_id(playlist_id):
query = """
diff --git a/gnomemusic/views/playlistview.py b/gnomemusic/views/playlistview.py
index af57942..bf3176f 100644
--- a/gnomemusic/views/playlistview.py
+++ b/gnomemusic/views/playlistview.py
@@ -140,6 +140,9 @@ class PlaylistView(BaseView):
self._update_songs_count()
+ self.model.connect('row-inserted', self._on_song_inserted)
+ self.model.connect('row-deleted', self._on_song_deleted)
+
self.player.connect('playlist-item-changed', self._update_model)
playlists.connect('playlist-created', self._on_playlist_created)
playlists.connect('playlist-updated', self._on_playlist_update)
@@ -147,6 +150,8 @@ class PlaylistView(BaseView):
'song-added-to-playlist', self._on_song_added_to_playlist)
playlists.connect(
'song-removed-from-playlist', self._on_song_removed_from_playlist)
+ playlists.connect(
+ 'song-position-changed', self._on_song_position_changed)
self.show_all()
@@ -167,6 +172,9 @@ class PlaylistView(BaseView):
self._view.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
self._view.connect('row-activated', self._on_song_activated)
self._view.connect('button-press-event', self._on_view_clicked)
+ self._view.connect('drag-begin', self._drag_begin)
+ self._view.connect('drag-end', self._drag_end)
+ self._song_drag = {'active': False}
view_container.add(self._view)
@@ -330,25 +338,30 @@ class PlaylistView(BaseView):
clicking on star column toggles favorite
clicking on an other columns launches player
+ Action is not performed if drag and drop is active
:param Gtk.Tree treeview: self._view
:param Gtk.TreePath path: activated row index
:param Gtk.TreeViewColumn column: activated column
"""
- if self._star_handler.star_renderer_click:
- self._star_handler.star_renderer_click = False
- return
+ def activate_song():
+ if self._song_drag['active']:
+ return
+
+ if self._star_handler.star_renderer_click:
+ self._star_handler.star_renderer_click = False
+ return
- try:
_iter = self.model.get_iter(path)
- except TypeError:
- return
+ if self.model[_iter][8] != self._error_icon_name:
+ self.player.set_playlist(
+ 'Playlist', self.current_playlist.get_id(), self.model,
+ _iter, 5, 11)
+ self.player.set_playing(True)
- if self.model[_iter][8] != self._error_icon_name:
- self.player.set_playlist(
- 'Playlist', self.current_playlist.get_id(), self.model, _iter,
- 5, 11)
- self.player.set_playing(True)
+ # 'row-activated' signal is emitted before 'drag-begin' signal.
+ # Need to wait to check if drag and drop operation is active.
+ GLib.idle_add(activate_song)
@log
def _on_view_clicked(self, treeview, event):
@@ -372,6 +385,59 @@ class PlaylistView(BaseView):
self._song_popover.popup()
return
+ @log
+ def _drag_begin(self, widget_, drag_context):
+ self._song_drag['active'] = True
+
+ @log
+ def _drag_end(self, widget_, drag_context):
+ self._song_drag['active'] = False
+
+ @log
+ def _on_song_inserted(self, model, path, iter_):
+ if not self._song_drag['active']:
+ return
+
+ self._song_drag['new_pos'] = int(path.to_string())
+
+ @log
+ def _on_song_deleted(self, model, path):
+ """Save new playlist order after drag and drop operation.
+
+ Update player's playlist if necessary.
+ """
+ if not self._song_drag['active']:
+ return
+
+ new_pos = self._song_drag['new_pos']
+ prev_pos = int(path.to_string())
+
+ if abs(new_pos - prev_pos) == 1:
+ return
+
+ # If playing song position has changed, update player's playlist.
+ if self.player.playing and not self.player.currentTrack.valid():
+ pos = new_pos
+ if new_pos > prev_pos:
+ pos -= 1
+ new_iter = self.model.get_iter_from_string(str(pos))
+ self._iter_to_clean = new_iter
+ self.player.set_playlist(
+ 'Playlist', self.current_playlist.get_id(), self.model,
+ new_iter, 5, 11)
+
+ first_pos = min(new_pos, prev_pos)
+ last_pos = max(new_pos, prev_pos)
+
+ positions = []
+ songs = []
+ for pos in range(first_pos, last_pos):
+ _iter = model.get_iter_from_string(str(pos))
+ songs.append(model[_iter][5])
+ positions.append(pos + 1)
+
+ playlists.reorder_playlist(self.current_playlist, songs, positions)
+
@log
def _play_song(self, menuitem, data=None):
model, _iter = self._view.get_selection().get_selected()
@@ -486,10 +552,12 @@ class PlaylistView(BaseView):
self._playlist_delete_action.set_enabled(False)
self._playlist_rename_action.set_enabled(False)
self._remove_song_action.set_enabled(False)
+ self._view.set_reorderable(False)
else:
self._playlist_delete_action.set_enabled(True)
self._playlist_rename_action.set_enabled(True)
self._remove_song_action.set_enabled(True)
+ self._view.set_reorderable(True)
@log
def _add_song(self, source, param, song, remaining=0, data=None):
@@ -634,6 +702,19 @@ class PlaylistView(BaseView):
"""Add new playlist to sidebar"""
self._add_playlist_to_sidebar(playlist)
+ @log
+ def _row_is_playing(self, playlist, row):
+ """Check if row is being played"""
+ if (self._is_current_playlist(playlist)
+ and self.player.currentTrack is not None
+ and self.player.currentTrack.valid()):
+ track_path = self.player.currentTrack.get_path()
+ track_path_str = track_path.to_string()
+ if (row.path is not None
+ and row.path.to_string() == track_path_str):
+ return True
+ return False
+
@log
def _on_song_added_to_playlist(self, playlists, playlist, item):
if self._is_current_playlist(playlist):
@@ -647,21 +728,10 @@ class PlaylistView(BaseView):
return
# checks if the to be removed track is now being played
- def is_playing(row):
- if self._is_current_playlist(playlist):
- if (self.player.currentTrack is not None
- and self.player.currentTrack.valid()):
- track_path = self.player.currentTrack.get_path()
- track_path_str = track_path.to_string()
- if (row.path is not None
- and row.path.to_string() == track_path_str):
- return True
- return False
-
for row in model:
if row[5].get_id() == item.get_id():
- is_being_played = is_playing(row)
+ is_being_played = self._row_is_playing(playlist, row)
next_iter = model.iter_next(row.iter)
model.remove(row.iter)
@@ -686,6 +756,22 @@ class PlaylistView(BaseView):
self._update_songs_count()
return
+ @log
+ def _on_song_position_changed(self, playlists, playlist, item):
+ """ If song is currently played, update next track"""
+ if not self._is_current_playlist(playlist):
+ return
+
+ if self.player.playing:
+ for row in self.model:
+ if (row[5].get_id() == item.get_id()
+ and self._row_is_playing(playlist, row)):
+ self._iter_to_clean = row.iter
+ self.player.set_playlist(
+ 'Playlist', playlist.get_id(), self.model, row.iter, 5,
+ 11)
+ return
+
@log
def populate(self):
"""Populate sidebar.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]