commit 57420b9b093fa5f5d74a7a0d1ccd440fc9fe6532
Author: Yash Mehrotra <yashmehrotra95@gmail.com>
Date:   Mon Feb 22 17:52:33 2016 +0530

    Option to rename playlists
    
    Now you can rename user created playlists. https://bugzilla.gnome.org/show_bug.cgi?id=744822

diff --git a/data/PlaylistControls.ui b/data/PlaylistControls.ui
index be73e3c..e9ff686 100644
--- a/data/PlaylistControls.ui
+++ b/data/PlaylistControls.ui
@@ -7,6 +7,10 @@
       <attribute name="action">win.playlist_play</attribute>
     </item>
     <item>
+      <attribute name="label" translatable="yes">_Rename...</attribute>
+      <attribute name="action">win.playlist_rename</attribute>
+    </item>
+    <item>
       <attribute name="label" translatable="yes">_Delete</attribute>
       <attribute name="action">win.playlist_delete</attribute>
     </item>
@@ -19,24 +23,54 @@
     <property name="margin_top">18</property>
     <property name="margin_bottom">18</property>
     <child>
-      <object class="GtkLabel" id="playlist_name">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="hexpand">True</property>
-        <property name="xalign">0</property>
-        <property name="label" translatable="yes">Playlist Name</property>
-        <property name="ellipsize">middle</property>
-        <attributes>
-          <attribute name="weight" value="bold"/>
-          <attribute name="scale" value="1.5"/>
-        </attributes>
+      <object class="GtkStack" id="stack">
+        <child>
+          <object class="GtkLabel" id="playlist_name">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Playlist Name</property>
+            <property name="ellipsize">middle</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+              <attribute name="scale" value="1.5"/>
+            </attributes>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">horizontal</property>
+            <child>
+              <object class="GtkEntry" id="playlist_rename_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="is_focus">True</property>
+                <property name="has_focus">True</property>
+                <property name="receives_default">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="rename_done_button">
+                <property name="visible">True</property>
+                <property name="no_show_all">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Done</property>
+                <property name="valign">center</property>
+                <property name="sensitive">True</property>
+                <style>
+                  <class name="suggested-action"/>
+                </style>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">renaming_dialog</property>>
+          </packing>
+        </child>
       </object>
-      <packing>
-        <property name="left_attach">0</property>
-        <property name="top_attach">0</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
-      </packing>
     </child>
     <child>
       <object class="GtkLabel" id="songs_count">
diff --git a/gnomemusic/playlists.py b/gnomemusic/playlists.py
index a7aaaf8..c5b6089 100644
--- a/gnomemusic/playlists.py
+++ b/gnomemusic/playlists.py
@@ -94,6 +94,7 @@ class Playlists(GObject.GObject):
         'playlist-created': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media,)),
         'playlist-deleted': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media,)),
         'playlist-updated': (GObject.SignalFlags.RUN_FIRST, None, (int,)),
+        'playlist-renamed': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media,)),
         'song-added-to-playlist': (
             GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, Grl.Media)
         ),
@@ -245,6 +246,17 @@ class Playlists(GObject.GObject):
         )
 
     @log
+    def rename_playlist(self, item, name):
+        def update_callback(conn, res, data):
+            conn.update_finish(res)
+            self.emit('playlist-renamed', item)
+
+        self.tracker.update_async(
+            Query.rename_playlist(item.get_id(), name), GLib.PRIORITY_LOW,
+            None, update_callback, None
+        )
+
+    @log
     def add_to_playlist(self, playlist, items):
         def get_callback(source, param, item, count, data, error):
             if item:
diff --git a/gnomemusic/query.py b/gnomemusic/query.py
index b882fb7..46ebfa9 100644
--- a/gnomemusic/query.py
+++ b/gnomemusic/query.py
@@ -492,6 +492,31 @@ class Query():
         return query
 
     @staticmethod
+    def rename_playlist(playlist_id, name):
+        query = """
+    INSERT OR REPLACE {
+        ?playlist
+            nie:title "%(title)s"
+    }
+    WHERE {
+        ?playlist
+            a nmm:Playlist ;
+            a nfo:MediaList .
+        OPTIONAL {
+            ?playlist
+                nfo:hasMediaFileListEntry ?entry .
+        }
+        FILTER (
+            tracker:id(?playlist) = %(playlist_id)s
+        )
+    }
+    """.replace("\n", " ").strip() % {
+            'title': name,
+            'playlist_id': playlist_id
+        }
+        return query
+
+    @staticmethod
     def delete_playlist(playlist_id):
         query = """
     DELETE {
diff --git a/gnomemusic/view.py b/gnomemusic/view.py
index 4d19036..8a4d121 100644
--- a/gnomemusic/view.py
+++ b/gnomemusic/view.py
@@ -825,17 +825,24 @@ class Playlist(ViewContainer):
         builder = Gtk.Builder()
         builder.add_from_resource('/org/gnome/Music/PlaylistControls.ui')
         self.headerbar = builder.get_object('grid')
+        self.name_stack = builder.get_object('stack')
         self.name_label = builder.get_object('playlist_name')
+        self.rename_entry = builder.get_object('playlist_rename_entry')
+        self.rename_done_button = builder.get_object('rename_done_button')
         self.songs_count_label = builder.get_object('songs_count')
         self.menubutton = builder.get_object('playlist_menubutton')
         playlistPlayAction = Gio.SimpleAction.new('playlist_play', None)
         playlistPlayAction.connect('activate', self._on_play_activate)
         window.add_action(playlistPlayAction)
+        self.playlistRenameAction = Gio.SimpleAction.new('playlist_rename', None)
+        self.playlistRenameAction.connect('activate', self._on_rename_activate)
+        window.add_action(self.playlistRenameAction)
         self.playlistDeleteAction = Gio.SimpleAction.new('playlist_delete', None)
         self.playlistDeleteAction.connect('activate', self._on_delete_activate)
         window.add_action(self.playlistDeleteAction)
         self._grid.insert_row(0)
         self._grid.attach(self.headerbar, 1, 0, 1, 1)
+        self._is_rename_active = False
 
         self.playlists_model = Gtk.ListStore(
             GObject.TYPE_STRING,
@@ -1137,11 +1144,13 @@ class Playlist(ViewContainer):
         self.songs_count = 0
         GLib.idle_add(grilo.populate_playlist_songs, playlist, self._add_item)
 
-        # disable delete button if current playlist is a smart playlist
+        # disable delete button and rename button if current playlist is a smart playlist
         if self.current_playlist_is_protected():
             self.playlistDeleteAction.set_enabled(False)
+            self.playlistRenameAction.set_enabled(False)
         else:
             self.playlistDeleteAction.set_enabled(True)
+            self.playlistRenameAction.set_enabled(True)
 
     @log
     def _add_item(self, source, param, item, remaining=0, data=None):
@@ -1229,6 +1238,30 @@ class Playlist(ViewContainer):
         self.stage_playlist_for_deletion()
 
     @log
+    def _on_rename_activate(self, menuitem, data=None):
+        _iter = self.playlists_sidebar.get_generic_view().get_selection().get_selected()[1]
+        playlist = self.playlists_model.get_value(_iter, 5)
+
+        self.pl_torename = playlist
+
+        self.name_stack.set_visible_child_name('renaming_dialog')
+        self.rename_entry.set_text(self.pl_torename.get_title())
+        self.rename_entry.grab_focus()
+        self._is_rename_active = True
+
+        self.rename_done_button.connect('clicked', self._on_rename_clicked)
+
+    def _on_rename_clicked(self, widget):
+        updated_name = self.rename_entry.get_text()
+        playlists.rename_playlist(self.pl_torename, updated_name)
+        self._is_rename_active = False
+        self.name_label.set_text(updated_name)
+        self.name_stack.set_visible_child(self.name_label)
+
+        self.playlists_model.clear()
+        self._populate()
+
+    @log
     def _on_playlist_created(self, playlists, item):
         self._add_playlist_item_to_model(item)
         if self.playlists_model.iter_n_children(None) == 1:
diff --git a/gnomemusic/window.py b/gnomemusic/window.py
index e6bc69d..ef09daa 100644
--- a/gnomemusic/window.py
+++ b/gnomemusic/window.py
@@ -409,16 +409,18 @@ class Window(Gtk.ApplicationWindow):
         # Open search bar when typing printable chars if it not opened
         # Make sure we skip unprintable chars and don't grab space press
         # (this is used for play/pause)
-        if not self.toolbar.searchbar.get_reveal_child() and not event.keyval == Gdk.KEY_space:
-            if (event_and_modifiers == Gdk.ModifierType.SHIFT_MASK or
-                    event_and_modifiers == 0) and \
-                    GLib.unichar_isprint(chr(Gdk.keyval_to_unicode(event.keyval))):
-                self.toolbar.searchbar.show_bar(True)
-        else:
-            if not self.toolbar.searchbar.get_reveal_child():
-                if event.keyval == Gdk.KEY_space and self.player.actionbar.get_visible():
-                    if self.get_focus() != self.player.playBtn:
-                        self.player.play_pause()
+        # Exception when another GtkEntry is open
+        if not self.views[3]._is_rename_active:
+            if not self.toolbar.searchbar.get_reveal_child() and not event.keyval == Gdk.KEY_space:
+                if (event_and_modifiers == Gdk.ModifierType.SHIFT_MASK or
+                        event_and_modifiers == 0) and \
+                        GLib.unichar_isprint(chr(Gdk.keyval_to_unicode(event.keyval))):
+                    self.toolbar.searchbar.show_bar(True)
+            else:
+                if not self.toolbar.searchbar.get_reveal_child():
+                    if event.keyval == Gdk.KEY_space and self.player.actionbar.get_visible():
+                        if self.get_focus() != self.player.playBtn:
+                            self.player.play_pause()
 
     @log
     def _notify_mode_disconnect(self, data=None):
