[gnome-music] playlistdialog: Add empty state



commit 922353149a566384083f4ce276915134c1e4699f
Author: Yash Singh <yashdev10p gmail com>
Date:   Mon Jan 2 00:16:35 2017 +0530

    playlistdialog: Add empty state
    
    Adds empty state for the playlists dialog when no user-created playlists
    are available yet, in line with the design team mockups.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=772089

 data/PlaylistDialog.ui               |  342 +++++++++++++++++++++++++---------
 gnomemusic/grilo.py                  |   41 ++++
 gnomemusic/query.py                  |   14 ++
 gnomemusic/widgets/playlistdialog.py |   74 +++++---
 gnomemusic/window.py                 |    8 +-
 5 files changed, 365 insertions(+), 114 deletions(-)
---
diff --git a/data/PlaylistDialog.ui b/data/PlaylistDialog.ui
index 71de7d0..2c03e27 100644
--- a/data/PlaylistDialog.ui
+++ b/data/PlaylistDialog.ui
@@ -1,7 +1,260 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.19.0 -->
+<!-- Generated with glade 3.20.0 -->
 <interface>
   <requires lib="gtk+" version="3.10"/>
+  <object class="GtkDialog" id="dialog">
+    <property name="width_request">400</property>
+    <property name="height_request">500</property>
+    <property name="can_focus">False</property>
+    <property name="modal">True</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkStack" id="add_playlist_stack">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="transition_duration">250</property>
+            <child>
+              <object class="GtkBox" id="empty_state">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkImage" id="image1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="valign">center</property>
+                        <property name="pixel_size">200</property>
+                        <property name="icon_name">emblem-music-symbolic</property>
+                        <property name="icon_size">0</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="padding">10</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Enter a name for your first 
playlist</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="first-playlist-entry">
+                        <property name="width_request">300</property>
+                        <property name="height_request">10</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="halign">center</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="padding">10</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="create-first-playlist-button">
+                        <property name="label" translatable="yes">C_reate</property>
+                        <property name="use_underline">True</property>
+                        <property name="width_request">120</property>
+                        <property name="height_request">25</property>
+                        <property name="visible">True</property>
+                        <property name="sensitive">False</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="halign">center</property>
+                        <property name="valign">center</property>
+                        <property name="margin_top">10</property>
+                        <property name="margin_bottom">20</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButtonBox">
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox" id="normal_state">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkSeparator">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="new-playlist-hbox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="border_width">6</property>
+                        <child>
+                          <object class="GtkEntry" id="new-playlist-entry">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="hexpand">True</property>
+                            <property name="placeholder_text" translatable="yes">New Playlist</property>
+                            <style>
+                              <class name="linked"/>
+                            </style>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="new-playlist-button">
+                            <property name="label" translatable="yes">Add</property>
+                            <property name="visible">True</property>
+                            <property name="sensitive">False</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <style>
+                              <class name="suggested-action"/>
+                              <class name="linked"/>
+                            </style>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <style>
+                          <class name="linked"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="pack_type">end</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="vexpand">True</property>
+                    <child>
+                      <object class="GtkTreeView" id="treeview1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="model">liststore1</property>
+                        <property name="headers_visible">False</property>
+                        <property name="search_column">0</property>
+                        <property name="activate_on_single_click">True</property>
+                        <child internal-child="selection">
+                          <object class="GtkTreeSelection" id="treeview-selection1"/>
+                        </child>
+                        <style>
+                          <class name="list-row"/>
+                          <class name="playlists-list"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButtonBox">
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
   <object class="GtkHeaderBar" id="headerbar1">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
@@ -46,91 +299,4 @@
       <column type="GObject"/>
     </columns>
   </object>
-  <object class="GtkDialog" id="dialog1">
-    <property name="width_request">400</property>
-    <property name="height_request">500</property>
-    <property name="can_focus">False</property>
-    <property name="modal">True</property>
-    <property name="destroy_with_parent">True</property>
-    <property name="type_hint">dialog</property>
-    <property name="use_header_bar">1</property>
-    <child internal-child="vbox">
-      <object class="GtkBox" id="dialog-vbox1">
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="border_width">0</property>
-        <child>
-          <object class="GtkScrolledWindow" id="scrolledwindow1">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="vexpand">True</property>
-            <child>
-              <object class="GtkTreeView" id="treeview1">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="model">liststore1</property>
-                <property name="headers_visible">False</property>
-                <property name="activate_on_single_click">True</property>
-                <child internal-child="selection">
-                  <object class="GtkTreeSelection" id="treeview-selection1"/>
-                </child>
-                <style>
-                  <class name="list-row"/>
-                  <class name="playlists-list"/>
-                </style>
-              </object>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="GtkSeparator">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-              </object>
-            </child>
-            <child>
-              <object class="GtkBox" id="new-playlist-hbox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="border_width">6</property>
-                <child>
-                  <object class="GtkEntry" id="new-playlist-entry">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="placeholder_text" translatable="yes">New Playlist</property>
-                    <style>
-                      <class name="linked"/>
-                    </style>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkButton" id="new-playlist-button">
-                    <property name="label" translatable="yes">_Add</property>
-                    <property name="use_underline">True</property>
-                    <property name="visible">True</property>
-                    <property name="sensitive">False</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">True</property>
-                    <style>
-                      <class name="suggested-action"/>
-                      <class name="linked"/>
-                    </style>
-                  </object>
-                </child>
-                <style>
-                  <class name="linked"/>
-                </style>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
 </interface>
diff --git a/gnomemusic/grilo.py b/gnomemusic/grilo.py
index 929d3c3..1be2709 100644
--- a/gnomemusic/grilo.py
+++ b/gnomemusic/grilo.py
@@ -443,4 +443,45 @@ class Grilo(GObject.GObject):
         self.sparqltracker.query_async(Query.all_songs_count(), None,
                                        songs_query_cb, None)
 
+    @log
+    def playlists_available(self, callback):
+        """Checks if there are any non-static playlists available
+
+        Calls a callback function with True or False depending on the
+        availability of playlists.
+        :param callback: Function to call on result
+        """
+        def cursor_next_cb(conn, res, data):
+            try:
+                has_next = conn.next_finish(res)
+            except GLib.Error as err:
+                logger.warn("Error: %s, %s", err.__class__, err)
+                callback(False)
+                return
+
+            if has_next:
+                count = conn.get_integer(0)
+
+                if count > 0:
+                    callback(True)
+                    return
+
+            callback(False)
+
+        def playlists_query_cb(conn, res, data):
+            try:
+                cursor = conn.query_finish(res)
+            except GLib.Error as err:
+                logger.warn("Error: %s, %s", err.__class__, err)
+                callback(False)
+                return
+
+            cursor.next_async(None, cursor_next_cb, None)
+
+        # TODO: currently just checks tracker, should work with any
+        # queryable supported Grilo source.
+        self.sparqltracker.query_async(
+                Query.all_non_static_playlists_count(),
+                None, playlists_query_cb, None)
+
 grilo = Grilo()
diff --git a/gnomemusic/query.py b/gnomemusic/query.py
index 2992080..b471d04 100644
--- a/gnomemusic/query.py
+++ b/gnomemusic/query.py
@@ -132,6 +132,20 @@ class Query():
         return query
 
     @staticmethod
+    def all_non_static_playlists_count():
+        query = """
+    SELECT
+        COUNT (?pl)
+    {   ?pl a nmm:Playlist .
+        OPTIONAL { ?pl nao:hasTag ?tag } .
+        FILTER (!BOUND(nfo:belongsToContainer(?pl)))
+        FILTER (!BOUND(?tag))
+    }
+        """.replace('\n', ' ').strip()
+
+        return query
+
+    @staticmethod
     def albums(where_clause):
         query = """
     SELECT
diff --git a/gnomemusic/widgets/playlistdialog.py b/gnomemusic/widgets/playlistdialog.py
index 8998106..28116be 100644
--- a/gnomemusic/widgets/playlistdialog.py
+++ b/gnomemusic/widgets/playlistdialog.py
@@ -39,9 +39,27 @@ class PlaylistDialog():
     def __init__(self, parent):
         self.ui = Gtk.Builder()
         self.ui.add_from_resource('/org/gnome/Music/PlaylistDialog.ui')
-        self.dialog_box = self.ui.get_object('dialog1')
-        self.dialog_box.set_transient_for(parent)
+        self._add_playlist_stack = self.ui.get_object('add_playlist_stack')
+
+        self._dialog_box = self.ui.get_object('dialog')
+        self._dialog_box.set_transient_for(parent)
+        self._normal_state = self.ui.get_object('normal_state')
+        self._empty_state = self.ui.get_object('empty_state')
+        self._title_bar = self.ui.get_object('headerbar1')
+        self._dialog_box.set_titlebar(self._title_bar)
+        self._setup_dialog()
+        self.playlist = Playlists.get_default()
+
+    @log
+    def run(self):
+        return self._dialog_box.run()
+
+    @log
+    def destroy(self):
+        return self._dialog_box.destroy()
 
+    @log
+    def _setup_dialog(self):
         self.view = self.ui.get_object('treeview1')
         self.view.set_activate_on_single_click(False)
         self.selection = self.ui.get_object('treeview-selection1')
@@ -52,28 +70,40 @@ class PlaylistDialog():
         self.model = self.ui.get_object('liststore1')
         self.populate()
 
-        self.title_bar = self.ui.get_object('headerbar1')
-        self.dialog_box.set_titlebar(self.title_bar)
-
         self._cancel_button = self.ui.get_object('cancel-button')
         self._select_button = self.ui.get_object('select-button')
         self._select_button.set_sensitive(False)
         self._cancel_button.connect('clicked', self._on_cancel_button_clicked)
         self._select_button.connect('clicked', self._on_selection)
 
-        self._new_playlist_button = self.ui.get_object('new-playlist-button')
-        self._new_playlist_button.connect('clicked', self._on_editing_done)
-
-        self._new_playlist_entry = self.ui.get_object('new-playlist-entry')
-        self._new_playlist_entry.connect('changed',
-                                         self._on_new_playlist_entry_changed)
-        self._new_playlist_entry.connect('activate',
-                                         self._on_editing_done)
-        self._new_playlist_entry.connect('focus-in-event',
-                                         self._on_new_playlist_entry_focused)
-
-        self.playlist = Playlists.get_default()
-        self.playlist.connect('playlist-created', self._on_playlist_created)
+        def playlists_available_cb(available):
+            if available:
+                self._add_playlist_stack.set_visible_child(self._normal_state)
+                self._new_playlist_button = self.ui.get_object(
+                    'new-playlist-button')
+                self._new_playlist_entry = self.ui.get_object(
+                    'new-playlist-entry')
+            else:
+                self._add_playlist_stack.set_visible_child(self._empty_state)
+                self._new_playlist_button = self.ui.get_object(
+                        'create-first-playlist-button')
+                self._new_playlist_entry = self.ui.get_object(
+                        'first-playlist-entry')
+
+            self._new_playlist_button.set_sensitive(False);
+            self._new_playlist_button.connect('clicked',
+                    self._on_editing_done)
+
+            self._new_playlist_entry.connect('changed',
+                self._on_new_playlist_entry_changed)
+            self._new_playlist_entry.connect('activate',
+                                             self._on_editing_done)
+            self._new_playlist_entry.connect('focus-in-event',
+                self._on_new_playlist_entry_focused)
+
+            self.playlist.connect('playlist-created', self._on_playlist_created)
+
+        grilo.playlists_available(playlists_available_cb)
 
     @log
     def get_selected(self):
@@ -133,11 +163,11 @@ class PlaylistDialog():
 
     @log
     def _on_selection(self, select_button):
-        self.dialog_box.response(Gtk.ResponseType.ACCEPT)
+        self._dialog_box.response(Gtk.ResponseType.ACCEPT)
 
     @log
     def _on_cancel_button_clicked(self, cancel_button):
-        self.dialog_box.response(Gtk.ResponseType.REJECT)
+        self._dialog_box.response(Gtk.ResponseType.REJECT)
 
     @log
     def _on_item_activated(self, view, path, column):
@@ -147,7 +177,7 @@ class PlaylistDialog():
         if self.model.get_value(_iter, 1):
             self.view.set_cursor(path, column, True)
         else:
-            self.dialog_box.response(Gtk.ResponseType.ACCEPT)
+            self._dialog_box.response(Gtk.ResponseType.ACCEPT)
 
     @log
     def _on_selection_changed(self, selection):
@@ -172,7 +202,7 @@ class PlaylistDialog():
                                  self.view.get_columns()[0], False)
             self.view.row_activated(self.model.get_path(new_iter),
                                     self.view.get_columns()[0])
-            self.dialog_box.response(Gtk.ResponseType.ACCEPT)
+            self._dialog_box.response(Gtk.ResponseType.ACCEPT)
 
     @log
     def _on_new_playlist_entry_changed(self, editable, data=None):
diff --git a/gnomemusic/window.py b/gnomemusic/window.py
index b70788b..fb97488 100644
--- a/gnomemusic/window.py
+++ b/gnomemusic/window.py
@@ -570,13 +570,13 @@ class Window(Gtk.ApplicationWindow):
             if len(selected_tracks) < 1:
                 return
 
-            add_to_playlist = PlaylistDialog(self)
-            if add_to_playlist.dialog_box.run() == Gtk.ResponseType.ACCEPT:
+            playlist_dialog = PlaylistDialog(self)
+            if playlist_dialog.run() == Gtk.ResponseType.ACCEPT:
                 playlist.add_to_playlist(
-                    add_to_playlist.get_selected(),
+                    playlist_dialog.get_selected(),
                     selected_tracks)
             self.toolbar.set_selection_mode(False)
-            add_to_playlist.dialog_box.destroy()
+            playlist_dialog.destroy()
 
         self._stack.get_visible_child().get_selected_tracks(callback)
 


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