[gnome-music] playlistdialog: Add empty state
- From: Marinus Schraal <mschraal src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music] playlistdialog: Add empty state
- Date: Mon, 13 Feb 2017 21:05:43 +0000 (UTC)
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]