[gnome-music/wip/mschraal/core] playlistsview: Restore play song from the context menu



commit e489edbb1adc197540d167527195a65bfa5344e8
Author: Jean Felder <jfelder src gnome org>
Date:   Mon Jul 8 18:57:07 2019 +0200

    playlistsview: Restore play song from the context menu
    
    Also, move all the deletion logic into the NotificationsPopup.

 gnomemusic/grilowrappers/grltrackerplaylists.py | 100 +++++++++++++++++++++++-
 gnomemusic/views/playlistsview.py               |  22 +++++-
 gnomemusic/widgets/notificationspopup.py        |  57 ++++++++++----
 3 files changed, 157 insertions(+), 22 deletions(-)
---
diff --git a/gnomemusic/grilowrappers/grltrackerplaylists.py b/gnomemusic/grilowrappers/grltrackerplaylists.py
index 198c3cdd..b8d74888 100644
--- a/gnomemusic/grilowrappers/grltrackerplaylists.py
+++ b/gnomemusic/grilowrappers/grltrackerplaylists.py
@@ -160,6 +160,8 @@ class Playlist(GObject.GObject):
         self._fast_options.set_resolution_flags(
             Grl.ResolutionFlags.FAST_ONLY | Grl.ResolutionFlags.IDLE_RELAY)
 
+        self._songs_todelete = []
+
     @GObject.Property(type=Gio.ListStore, default=None)
     def model(self):
         if self._model is None:
@@ -220,7 +222,8 @@ class Playlist(GObject.GObject):
                 return
 
             coresong = CoreSong(media, self._coreselection, self._grilo)
-            self._model.append(coresong)
+            if coresong not in self._songs_todelete:
+                self._model.append(coresong)
 
         options = Grl.OperationOptions()
         options.set_resolution_flags(
@@ -262,6 +265,101 @@ class Playlist(GObject.GObject):
         self._tracker.update_async(
             query, GLib.PRIORITY_LOW, None, update_cb, None)
 
+    def stage_deletion(self, coresong, index):
+        """Adds a song to the list of songs to delete
+
+        :param CoreSong coresong: song to delete
+        :param int position: Song position in the playlist
+        """
+        self._songs_todelete.append(coresong)
+        self._model.remove(index)
+        self.props.count -= 1
+
+    def undo_pending_deletion(self, coresong, position):
+        """Adds a song to the list of songs to delete
+
+        :param CoreSong coresong: song to delete
+        :param int position: Song position in the playlist
+        """
+        self._songs_todelete.remove(coresong)
+        self._model.insert(position, coresong)
+        self.props.count += 1
+
+    def finish_deletion(self, coresong):
+        """Removes a song from the playlist
+
+        :param CoreSong coresong: song to remove
+        """
+
+        def update_cb(conn, res, data):
+            # FIXME: Check for failure.
+            conn.update_finish(res)
+
+        query = """
+        INSERT OR REPLACE {
+            ?entry nfo:listPosition ?position .
+
+        }
+        WHERE {
+            SELECT ?entry
+                   (?old_position - 1) AS ?position
+            WHERE {
+                ?entry a nfo:MediaFileListEntry ;
+                         nfo:listPosition ?old_position .
+                ?playlist nfo:hasMediaFileListEntry ?entry .
+                FILTER (?old_position > ?removed_position)
+                {
+                    SELECT ?playlist
+                           ?removed_position
+                    WHERE {
+                        ?playlist a nmm:Playlist ;
+                                  a nfo:MediaList ;
+                                    nfo:hasMediaFileListEntry ?removed_entry .
+                        ?removed_entry nfo:listPosition ?removed_position .
+                        FILTER (
+                            tracker:id(?playlist) = %(playlist_id)s &&
+                            tracker:id(?removed_entry) = %(song_id)s
+                        )
+                    }
+                }
+            }
+        }
+        INSERT OR REPLACE {
+            ?playlist nfo:entryCounter ?new_counter .
+        }
+        WHERE {
+            SELECT ?playlist
+                   (?counter - 1) AS ?new_counter
+            WHERE {
+                ?playlist a nmm:Playlist ;
+                          a nfo:MediaList ;
+                            nfo:entryCounter ?counter .
+                FILTER (
+                    tracker:id(?playlist) = %(playlist_id)s
+                )
+            }
+        }
+        DELETE {
+            ?playlist nfo:hasMediaFileListEntry ?entry .
+            ?entry a rdfs:Resource .
+        }
+        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": self.props.pl_id,
+            "song_id": coresong.props.media.get_id()
+        }
+
+        self._tracker.update_async(
+            query, GLib.PRIORITY_LOW, None, update_cb, None)
+
 
 class SmartPlaylist(Playlist):
     """Base class for smart playlists"""
diff --git a/gnomemusic/views/playlistsview.py b/gnomemusic/views/playlistsview.py
index df46ffc8..d1ee4880 100644
--- a/gnomemusic/views/playlistsview.py
+++ b/gnomemusic/views/playlistsview.py
@@ -29,6 +29,7 @@ from gi.repository import Gdk, GObject, Gio, Gtk
 from gnomemusic import log
 from gnomemusic.player import PlayerPlaylist
 from gnomemusic.views.baseview import BaseView
+from gnomemusic.widgets.notificationspopup import PlaylistNotification
 from gnomemusic.widgets.playlistcontextmenu import PlaylistContextMenu
 from gnomemusic.widgets.playlistcontrols import PlaylistControls
 from gnomemusic.widgets.sidebarrow import SidebarRow
@@ -78,10 +79,10 @@ class PlaylistsView(BaseView):
         # add_song_to_playlist.connect('activate', self._add_song_to_playlist)
         # 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._stage_song_for_deletion)
-        # self._window.add_action(self._remove_song_action)
+        self._remove_song_action = Gio.SimpleAction.new('remove_song', None)
+        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)
         # playlist_play_action.connect('activate', self._on_play_activate)
@@ -180,6 +181,19 @@ class PlaylistsView(BaseView):
         self._view.unselect_all()
         self._song_activated(song_widget, None)
 
+    @log
+    def _stage_song_for_deletion(self, menuitem, data=None):
+        selected_row = self._view.get_selected_row()
+        position = selected_row.get_index()
+        song_widget = selected_row.get_child()
+        coresong = song_widget.props.coresong
+
+        selected_playlist = self._sidebar.get_selected_row().playlist
+
+        notification = PlaylistNotification(  # noqa: F841
+            self._window.notifications_popup, PlaylistNotification.Type.SONG,
+            selected_playlist, coresong, position)
+
     @log
     def _on_playlist_activated(self, sidebar, row, data=None):
         """Update view with content from selected playlist"""
diff --git a/gnomemusic/widgets/notificationspopup.py b/gnomemusic/widgets/notificationspopup.py
index 08b2750a..061a811e 100644
--- a/gnomemusic/widgets/notificationspopup.py
+++ b/gnomemusic/widgets/notificationspopup.py
@@ -123,14 +123,12 @@ class NotificationsPopup(Gtk.Revealer):
         self.set_reveal_child(True)
 
     @log
-    def remove_notification(self, notification, signal):
-        """Remove notification and emit a signal.
+    def remove_notification(self, notification):
+        """Removes notification.
 
         :param notification: notification to remove
-        :param signal: signal to emit: deletion or undo action
         """
         self._set_visibility(notification, True)
-        notification.emit(signal)
 
     @log
     def terminate_pending(self):
@@ -138,7 +136,7 @@ class NotificationsPopup(Gtk.Revealer):
         children = self._grid.get_children()
         if len(children) > 1:
             for notification in children[:-1]:
-                self.remove_notification(notification, 'finish-deletion')
+                notification._finish_deletion()
 
 
 class LoadingNotification(Gtk.Grid):
@@ -211,41 +209,66 @@ class PlaylistNotification(Gtk.Grid):
         PLAYLIST = 0
         SONG = 1
 
-    __gsignals__ = {
-        'undo-deletion': (GObject.SignalFlags.RUN_FIRST, None, ()),
-        'finish-deletion': (GObject.SignalFlags.RUN_FIRST, None, ())
-    }
-
     def __repr__(self):
         return '<PlaylistNotification>'
 
     @log
-    def __init__(self, notifications_popup, type_, message, data):
+    def __init__(self, notifications_popup, type_, playlist, data, position):
+        """Creates a playlist deletion notification popup (song or playlist)
+
+        :param GtkRevealer notifications_popup: the popup object
+        :param type_: NotificationType (song or playlist)
+        :param Playlist playlist: playlist
+        :param object data: Data associated with the deletion
+        :param int position: position of the object to delete
+        """
         super().__init__(column_spacing=18)
         self._notifications_popup = notifications_popup
         self.type_ = type_
+        self._playlist = playlist
         self.data = data
+        self._position = position
 
+        message = self._create_notification_message()
         self._label = Gtk.Label(
             label=message, halign=Gtk.Align.START, hexpand=True)
         self.add(self._label)
 
         undo_button = Gtk.Button.new_with_mnemonic(_("_Undo"))
-        undo_button.connect("clicked", self._undo_clicked)
+        undo_button.connect("clicked", self._undo_deletion)
         self.add(undo_button)
         self.show_all()
 
-        self._timeout_id = GLib.timeout_add_seconds(
-            5, self._notifications_popup.remove_notification, self,
-            'finish-deletion')
+        if self.type_ == PlaylistNotification.Type.SONG:
+            playlist.stage_deletion(self.data, position)
 
+        self._timeout_id = GLib.timeout_add_seconds(5, self._finish_deletion)
         self._notifications_popup.add_notification(self)
 
+    def _create_notification_message(self):
+        if self.type_ == PlaylistNotification.Type.PLAYLIST:
+            return None
+            # pl_todelete = data
+            # msg = _("Playlist {} removed".format(pl_todelete.props.title))
+
+        else:
+            playlist_title = self._playlist.props.title
+            coresong = self.data
+            song_title = coresong.props.title
+            msg = _("{} removed from {}".format(
+                song_title, playlist_title))
+            return msg
+
     @log
-    def _undo_clicked(self, widget_):
+    def _undo_deletion(self, widget_):
         """Undo deletion and remove notification"""
         if self._timeout_id > 0:
             GLib.source_remove(self._timeout_id)
             self._timeout_id = 0
 
-        self._notifications_popup.remove_notification(self, 'undo-deletion')
+        self._notifications_popup.remove_notification(self)
+        self._playlist.undo_pending_deletion(self.data, self._position)
+
+    def _finish_deletion(self):
+        self._notifications_popup.remove_notification(self)
+        self._playlist.finish_deletion(self.data)


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