[gnome-music] playlistview: Undo song removal from playlist



commit ed79c184ee0d37fc6499d660d69f09f3874a605c
Author: Jean Felder <jean felder gmail com>
Date:   Wed Jan 31 10:58:50 2018 +0100

    playlistview: Undo song removal from playlist
    
    Multiple songs can be removed independently.
    Closes: #50

 gnomemusic/views/playlistview.py         | 110 ++++++++++++++++++++++---------
 gnomemusic/widgets/notificationspopup.py |  15 +++--
 po/POTFILES.in                           |   1 +
 3 files changed, 91 insertions(+), 35 deletions(-)
---
diff --git a/gnomemusic/views/playlistview.py b/gnomemusic/views/playlistview.py
index 756b64d..bf59a4d 100644
--- a/gnomemusic/views/playlistview.py
+++ b/gnomemusic/views/playlistview.py
@@ -98,7 +98,8 @@ class PlaylistView(BaseView):
         self._window.add_action(add_song_to_playlist)
 
         self._remove_song_action = Gio.SimpleAction.new('remove_song', None)
-        self._remove_song_action.connect('activate', self._remove_song)
+        self._remove_song_action.connect(
+            'activate', self._stage_song_for_deletion)
         self._window.add_action(self._remove_song_action)
 
         playlist_play_action = Gio.SimpleAction.new('playlist_play', None)
@@ -134,6 +135,7 @@ class PlaylistView(BaseView):
         self._current_playlist = None
         self._current_playlist_index = None
         self.pls_todelete = {}
+        self._songs_todelete = {}
         self._songs_count = 0
         self._handler_rename_done_button = 0
         self._handler_rename_entry = 0
@@ -148,8 +150,6 @@ class PlaylistView(BaseView):
         playlists.connect('playlist-updated', self._on_playlist_update)
         playlists.connect(
             '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)
 
@@ -457,10 +457,18 @@ class PlaylistView(BaseView):
         playlist_dialog.destroy()
 
     @log
-    def _remove_song(self, menuitem, data=None):
+    def _stage_song_for_deletion(self, menuitem, data=None):
         model, _iter = self._view.get_selection().get_selected()
         song = model[_iter][5]
-        playlists.remove_from_playlist(self._current_playlist, [song])
+        index = int(model.get_path(_iter).to_string())
+        song_id = song.get_id()
+        self._songs_todelete[song_id] = {
+            'playlist': self._current_playlist,
+            'song': song,
+            'index': index
+        }
+        self._remove_song_from_playlist(self._current_playlist, song)
+        self._create_notification(PlaylistNotification.Type.SONG, song_id)
 
     @log
     def _on_playlist_update(self, playlists, playlist_id):
@@ -577,7 +585,7 @@ class PlaylistView(BaseView):
             self._view.set_model(self.model)
 
     @log
-    def _add_song_to_model(self, song, model):
+    def _add_song_to_model(self, song, model, index=-1):
         """Add song to a playlist
         :param Grl.Media song: song to add
         :param Gtk.ListStore model: model
@@ -592,7 +600,7 @@ class PlaylistView(BaseView):
         title = utils.get_media_title(song)
         song.set_title(title)
         artist = utils.get_artist_name(song)
-        model.insert_with_valuesv(-1, [2, 3, 5, 9],
+        model.insert_with_valuesv(index, [2, 3, 5, 9],
                                   [title, artist, song, song.get_favourite()])
 
         self._songs_count += 1
@@ -631,28 +639,45 @@ class PlaylistView(BaseView):
         return False
 
     @log
-    def _get_removal_notification_message(self, playlist_id):
-        pl_todelete = self.pls_todelete[playlist_id]
-        playlist_title = utils.get_media_title(pl_todelete['playlist'])
-        msg = _("Playlist {} removed".format(playlist_title))
+    def _get_removal_notification_message(self, type_, media_id):
+        """ Returns a label for the playlist notification popup
+
+        Handles two cases:
+        - playlist removal
+        - songs from playlist removal
+        """
+        msg = ""
+
+        if type_ == PlaylistNotification.Type.PLAYLIST:
+            pl_todelete = self.pls_todelete[media_id]
+            playlist_title = utils.get_media_title(pl_todelete['playlist'])
+            msg = _("Playlist {} removed".format(playlist_title))
+
+        else:
+            song_todelete = self._songs_todelete[media_id]
+            playlist_title = utils.get_media_title(song_todelete['playlist'])
+            song_title = utils.get_media_title(song_todelete['song'])
+            msg = _("{} removed from {}".format(
+                song_title, playlist_title))
+
         return msg
 
     @log
-    def _create_playlist_notification(self, playlist_id):
-        msg = self._get_removal_notification_message(playlist_id)
+    def _create_notification(self, type_, media_id):
+        msg = self._get_removal_notification_message(type_, media_id)
         playlist_notification = PlaylistNotification(
-            self._window.notifications_popup, msg, playlist_id)
+            self._window.notifications_popup, type_, msg, media_id)
         playlist_notification.connect(
-            'undo-deletion', self._undo_playlist_deletion)
+            'undo-deletion', self._undo_pending_deletion)
         playlist_notification.connect(
-            'finish-deletion', self._finish_playlist_deletion)
+            'finish-deletion', self._finish_pending_deletion)
 
     @log
     def _stage_playlist_for_deletion(self, menutime, data=None):
         self.model.clear()
         selection = self._sidebar.get_selected_row()
         index = selection.get_index()
-        playlist_id = self.current_playlist.get_id()
+        playlist_id = self._current_playlist.get_id()
         self.pls_todelete[playlist_id] = {
             'playlist': selection.playlist,
             'index': index
@@ -665,23 +690,46 @@ class PlaylistView(BaseView):
             self._sidebar.select_row(row_next)
             self._sidebar.emit('row-activated', row_next)
 
-        self._create_playlist_notification(playlist_id)
+        self._create_notification(
+            PlaylistNotification.Type.PLAYLIST, playlist_id)
 
     @log
-    def _undo_playlist_deletion(self, playlist_notification):
+    def _undo_pending_deletion(self, playlist_notification):
         """Revert the last playlist removal"""
-        playlist_id = playlist_notification.playlist_id
-        pl_todelete = self.pls_todelete[playlist_id]
-        self._add_playlist_to_sidebar(
-            pl_todelete['playlist'], pl_todelete['index'])
-        self.pls_todelete.pop(playlist_id)
+        notification_type = playlist_notification.type_
+        media_id = playlist_notification.media_id
+
+        if notification_type == PlaylistNotification.Type.PLAYLIST:
+            pl_todelete = self.pls_todelete[media_id]
+            self._add_playlist_to_sidebar(
+                pl_todelete['playlist'], pl_todelete['index'])
+            self.pls_todelete.pop(media_id)
+
+        else:
+            song_todelete = self._songs_todelete[media_id]
+            playlist = song_todelete['playlist']
+            if (self._current_playlist
+                    and playlist.get_id() == self._current_playlist.get_id()):
+                self._add_song_to_model(
+                    song_todelete['song'], self.model, song_todelete['index'])
+                self._update_songs_count()
+            self._songs_todelete.pop(media_id)
 
     @log
-    def _finish_playlist_deletion(self, playlist_notification):
-        playlist_id = playlist_notification.playlist_id
-        pl_todelete = self.pls_todelete[playlist_id]
-        playlists.delete_playlist(pl_todelete['playlist'])
-        self.pls_todelete.pop(playlist_id)
+    def _finish_pending_deletion(self, playlist_notification):
+        notification_type = playlist_notification.type_
+        media_id = playlist_notification.media_id
+
+        if notification_type == PlaylistNotification.Type.PLAYLIST:
+            pl_todelete = self.pls_todelete[media_id]
+            playlists.delete_playlist(pl_todelete['playlist'])
+            self.pls_todelete.pop(media_id)
+
+        else:
+            song_todelete = self._songs_todelete[media_id]
+            playlists.remove_from_playlist(
+                song_todelete['playlist'], [song_todelete['song']])
+            self._songs_todelete.pop(media_id)
 
     @log
     @property
@@ -748,8 +796,8 @@ class PlaylistView(BaseView):
             self._add_song_to_model(item, self.model)
 
     @log
-    def _on_song_removed_from_playlist(self, playlists, playlist, item):
-        if self._is_current_playlist(playlist):
+    def _remove_song_from_playlist(self, playlist, item):
+        if (self._is_current_playlist(playlist)):
             model = self.model
         else:
             return
diff --git a/gnomemusic/widgets/notificationspopup.py b/gnomemusic/widgets/notificationspopup.py
index 455c6af..be16b1f 100644
--- a/gnomemusic/widgets/notificationspopup.py
+++ b/gnomemusic/widgets/notificationspopup.py
@@ -22,6 +22,7 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+from enum import IntEnum
 from gettext import gettext as _
 from gi.repository import GLib, GObject, Gtk
 
@@ -33,7 +34,7 @@ class NotificationsPopup(Gtk.Revealer):
 
     There are two types of messages:
     - loading notification
-    - playlist deletion
+    - playlist or song deletion
     Messages are arranged under each other
     """
 
@@ -189,12 +190,17 @@ class LoadingNotification(Gtk.Grid):
 
 
 class PlaylistNotification(Gtk.Grid):
-    """Show a notification on playlist deletion.
+    """Show a notification on playlist or song deletion.
 
     It also provides an option to undo removal. Notification is added
     to the NotificationsPopup.
     """
 
+    class Type(IntEnum):
+        """Enum for Playlists Notifications"""
+        PLAYLIST = 0
+        SONG = 1
+
     __gsignals__ = {
         'undo-deletion': (GObject.SignalFlags.RUN_FIRST, None, ()),
         'finish-deletion': (GObject.SignalFlags.RUN_FIRST, None, ())
@@ -204,10 +210,11 @@ class PlaylistNotification(Gtk.Grid):
         return '<PlaylistNotification>'
 
     @log
-    def __init__(self, notifications_popup, message, playlist_id):
+    def __init__(self, notifications_popup, type_, message, media_id):
         super().__init__(column_spacing=18)
         self._notifications_popup = notifications_popup
-        self.playlist_id = playlist_id
+        self.type_ = type_
+        self.media_id = media_id
 
         self._label = Gtk.Label(
             label=message, halign=Gtk.Align.START, hexpand=True)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 58128b5..04f894b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -28,6 +28,7 @@ gnomemusic/widgets/albumwidget.py
 gnomemusic/widgets/artistalbumswidget.py
 gnomemusic/widgets/artistalbumwidget.py
 gnomemusic/widgets/disclistboxwidget.py
+gnomemusic/widgets/notificationspopup.py
 gnomemusic/widgets/playlistdialog.py
 gnomemusic/widgets/starhandlerwidget.py
 gnomemusic/window.py


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]