[gnome-music] Enable playlists
- From: Vadim Rutkovsky <vrutkovsky src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music] Enable playlists
- Date: Mon, 17 Feb 2014 11:01:14 +0000 (UTC)
commit 2609785e5074538d1fb2dab0f0d2cf5e5d791103
Author: Sai Suman Prayaga <suman sai14 gmail com>
Date: Mon Feb 17 11:38:17 2014 +0100
Enable playlists
configure.ac | 1 +
data/PlaylistControls.ui | 95 ++++++++
data/PlaylistDialog.ui.in | 92 ++++++++
data/SelectionToolbar.ui | 16 ++
data/application.css | 17 ++
data/gnome-music.gresource.xml | 2 +
data/headerbar.ui.in | 2 +
gnomemusic/Makefile.am | 3 +-
gnomemusic/grilo.py | 30 ++-
gnomemusic/player.py | 1 +
gnomemusic/playlists.py | 126 +++++++++++
gnomemusic/query.py | 21 ++
gnomemusic/toolbar.py | 5 +-
gnomemusic/view.py | 465 ++++++++++++++++++++++++++++++++++++++--
gnomemusic/widgets.py | 96 ++++++++-
gnomemusic/window.py | 95 ++++++++-
po/POTFILES.in | 3 +-
po/POTFILES.skip | 1 +
18 files changed, 1030 insertions(+), 41 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index bf07c41..27b806f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -44,6 +44,7 @@ AC_CONFIG_FILES([
data/Makefile
data/headerbar.ui
data/AboutDialog.ui
+ data/PlaylistDialog.ui
gnomemusic/Makefile
po/Makefile.in
libgd/Makefile
diff --git a/data/PlaylistControls.ui b/data/PlaylistControls.ui
new file mode 100644
index 0000000..a1882f4
--- /dev/null
+++ b/data/PlaylistControls.ui
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.10 -->
+ <object class="GtkMenu" id="menu1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem_play">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Play</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem_delete">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Delete</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ <object class="GtkGrid" id="grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">18</property>
+ <property name="margin_right">24</property>
+ <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">Playlist Name</property>
+ <property name="ellipsize">middle</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </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">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label">3 Songs</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="playlist_menubutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="focus_on_click">False</property>
+ <property name="popup">menu1</property>
+ <style>
+ <class name="circle-button"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">emblem-system-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/data/PlaylistDialog.ui.in b/data/PlaylistDialog.ui.in
new file mode 100644
index 0000000..d224c6d
--- /dev/null
+++ b/data/PlaylistDialog.ui.in
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.15.2 on Thu Aug 22 16:04:08 2013 -->
+<interface>
+ <!-- interface-requires gtk+ 3.10 -->
+ <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>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="can_focus">False</property>
+ </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="shadow_type">in</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>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="@GTK_OR_GD HeaderBar" id="headerbar1">
+ <property name="title" translatable="yes">Select Playlist</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="cancel-button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <style>
+ <class name="text-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="select-button">
+ <property name="label" translatable="yes">Select</property>
+ <property name="visible">True</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="text-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ <object class="GtkListStore" id="liststore1">
+ <columns>
+ <!-- column-name playlist-name -->
+ <column type="gchararray"/>
+ <!-- column-name editable -->
+ <column type="gboolean"/>
+ </columns>
+ </object>
+</interface>
diff --git a/data/SelectionToolbar.ui b/data/SelectionToolbar.ui
index bb59e08..569bc70 100644
--- a/data/SelectionToolbar.ui
+++ b/data/SelectionToolbar.ui
@@ -35,6 +35,22 @@
<property name="position">0</property>
</packing>
</child>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label" translatable="yes">Remove from Playlist</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <style>
+ <class name="text-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
</object>
</child>
</object>
diff --git a/data/application.css b/data/application.css
index 361b339..48b06f3 100644
--- a/data/application.css
+++ b/data/application.css
@@ -31,8 +31,22 @@
background-color: #77757A;
}
+.playlist-controls-white{
+ background-color: #d7dad7;
+}
+.playlist-controls-white:selected{
+ background-color: #888A85;
+}
+.playlist-controls-dark{
+ background-color: #282528;
+}
+.playlist-controls-dark:selected{
+ background-color: #77757A;
+}
+
.songs-list {
box-shadow: inset 0 -1px shade(@borders, 1.30);
+ box-shadow: inset 0 1px shade(@borders, 1.30);
background-color: @theme_bg_color;
}
@@ -127,3 +141,6 @@
color: mix (@theme_fg_color, @theme_bg_color, 0.50);
}
+.circle-button {
+ border-radius: 50%;
+}
diff --git a/data/gnome-music.gresource.xml b/data/gnome-music.gresource.xml
index 92a4560..b33c23d 100644
--- a/data/gnome-music.gresource.xml
+++ b/data/gnome-music.gresource.xml
@@ -12,5 +12,7 @@
<file preprocess="xml-stripblanks">headerbar.ui</file>
<file preprocess="xml-stripblanks">TrackWidget.ui</file>
<file preprocess="xml-stripblanks">NoMusic.ui</file>
+ <file preprocess="xml-stripblanks">PlaylistControls.ui</file>
+ <file preprocess="xml-stripblanks">PlaylistDialog.ui</file>
</gresource>
</gresources>
diff --git a/data/headerbar.ui.in b/data/headerbar.ui.in
index e60fc65..69a8d89 100644
--- a/data/headerbar.ui.in
+++ b/data/headerbar.ui.in
@@ -5,10 +5,12 @@
<section>
<item>
<attribute name="label" translatable="yes">Select All</attribute>
+ <attribute name="action">win.selectAll</attribute>
<attribute name="accel"><Primary>a</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Select None</attribute>
+ <attribute name="action">win.selectNone</attribute>
</item>
</section>
</menu>
diff --git a/gnomemusic/Makefile.am b/gnomemusic/Makefile.am
index c1c857a..6658fb7 100644
--- a/gnomemusic/Makefile.am
+++ b/gnomemusic/Makefile.am
@@ -9,7 +9,8 @@ app_PYTHON = \
notification.py \
toolbar.py \
view.py \
- grilo.py \
+ grilo.py \
+ playlists.py\
query.py \
widgets.py \
searchbar.py \
diff --git a/gnomemusic/grilo.py b/gnomemusic/grilo.py
index 16e79ce..cf852ac 100644
--- a/gnomemusic/grilo.py
+++ b/gnomemusic/grilo.py
@@ -50,23 +50,26 @@ class Grilo(GObject.GObject):
def __init__(self):
GObject.GObject.__init__(self)
-
+ self.playlist_path = GLib.build_filenamev([GLib.get_user_data_dir(),
+ "gnome-music", "playlists"])
+ if not (GLib.file_test(self.playlist_path, GLib.FileTest.IS_DIR)):
+ GLib.mkdir_with_parents(self.playlist_path, int("0755", 8))
self.options = Grl.OperationOptions()
self.options.set_flags(Grl.ResolutionFlags.FULL |
Grl.ResolutionFlags.IDLE_RELAY)
- self.registry = Grl.Registry.get_default()
- try:
- self.registry.load_all_plugins()
- except GLib.GError:
- print('Failed to load plugins.')
-
self.sources = {}
self.tracker = None
+ self.registry = Grl.Registry.get_default()
self.registry.connect('source_added', self._on_source_added)
self.registry.connect('source_removed', self._on_source_removed)
+ try:
+ self.registry.load_all_plugins()
+ except GLib.GError:
+ print('Failed to load plugins.')
+
def _on_source_added(self, pluginRegistry, mediaSource):
id = mediaSource.get_id()
if id == 'grl-tracker-source':
@@ -103,7 +106,7 @@ class Grilo(GObject.GObject):
options.set_count(count)
def _callback(source, param, item, count, data, offset):
- callback(source, param, item)
+ callback(source, param, item, count)
self.tracker.query(query, self.METADATA_KEYS, options, _callback, None)
def _search_callback(self):
@@ -121,6 +124,17 @@ class Grilo(GObject.GObject):
query = Query.get_album_for_id(album_id)
self.tracker.query(query, self.METADATA_THUMBNAIL_KEYS, options, _callback, None)
+ def get_media_from_uri(self, uri, callback):
+ options = self.options.copy()
+ query = Query.get_song_with_url(uri)
+
+ def _callback(source, param, item, count, data, error):
+ if not error:
+ callback(source, param, item)
+ return
+
+ self.tracker.query(query, self.METADATA_KEYS, options, _callback, None)
+
Grl.init(None)
grilo = Grilo()
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index 981ee52..ddb07b9 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -662,4 +662,5 @@ class SelectionToolbar():
self._ui.add_from_resource('/org/gnome/Music/SelectionToolbar.ui')
self.eventbox = self._ui.get_object('eventbox1')
self._add_to_playlist_button = self._ui.get_object('button1')
+ self._remove_from_playlist_button = self._ui.get_object('button2')
self.eventbox.set_visible(False)
diff --git a/gnomemusic/playlists.py b/gnomemusic/playlists.py
new file mode 100644
index 0000000..38e1277
--- /dev/null
+++ b/gnomemusic/playlists.py
@@ -0,0 +1,126 @@
+from gi.repository import TotemPlParser, Grl, GLib, Gio, GObject
+from gnomemusic.grilo import grilo
+
+import os
+
+
+class Playlists(GObject.GObject):
+ __gsignals__ = {
+ 'playlist-created': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
+ 'playlist-deleted': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
+ 'song-added-to-playlist': (GObject.SIGNAL_RUN_FIRST, None, (str, Grl.Media)),
+ 'song-removed-from-playlist': (GObject.SIGNAL_RUN_FIRST, None, (str, str)),
+ }
+ instance = None
+
+ @classmethod
+ def get_default(self):
+ if self.instance:
+ return self.instance
+ else:
+ self.instance = Playlists()
+ return self.instance
+
+ def __init__(self):
+ GObject.GObject.__init__(self)
+ self.playlist_dir = os.path.join(GLib.get_user_data_dir(),
+ 'gnome-music',
+ 'playlists')
+
+ def create_playlist(self, name, iterlist=None):
+ parser = TotemPlParser.Parser()
+ playlist = TotemPlParser.Playlist()
+ pl_file = Gio.file_new_for_path(self.get_path_to_playlist(name))
+ if iterlist is not None:
+ for _iter in iterlist:
+ pass
+ else:
+ _iter = playlist.append()
+ parser.save(playlist, pl_file, name, TotemPlParser.ParserType.PLS)
+ self.emit('playlist-created', name)
+ return False
+
+ def get_playlists(self):
+ playlist_files = [pl_file for pl_file in os.listdir(self.playlist_dir)
+ if os.path.isfile(os.path.join(self.playlist_dir,
+ pl_file))]
+ playlist_names = []
+ for playlist_file in playlist_files:
+ name, ext = os.path.splitext(playlist_file)
+ if ext == '.pls':
+ playlist_names.append(name)
+ return playlist_names
+
+ def add_to_playlist(self, playlist_name, uris):
+ parser = TotemPlParser.Parser()
+ playlist = TotemPlParser.Playlist()
+ pl_file = Gio.file_new_for_path(self.get_path_to_playlist(playlist_name))
+
+ def parse_callback(parser, uri, metadata, data):
+ _iter = playlist.append()
+ playlist.set_value(_iter, TotemPlParser.PARSER_FIELD_URI, uri)
+
+ def end_callback(parser, uri, data):
+ for uri in uris:
+ _iter = playlist.append()
+ playlist.set_value(_iter, TotemPlParser.PARSER_FIELD_URI, uri)
+
+ def get_callback(source, param, item):
+ self.emit('song-added-to-playlist', playlist_name, item)
+ grilo.get_media_from_uri(uri, get_callback)
+
+ parser.save(playlist, pl_file, playlist_name, TotemPlParser.ParserType.PLS)
+
+ parser.connect('entry-parsed', parse_callback, playlist)
+ parser.connect('playlist-ended', end_callback, playlist)
+ parser.parse_async(
+ GLib.filename_to_uri(self.get_path_to_playlist(playlist_name), None),
+ False, None, None, None
+ )
+
+ def remove_from_playlist(self, playlist_name, uris):
+ parser = TotemPlParser.Parser()
+ playlist = TotemPlParser.Playlist()
+ pl_file = Gio.file_new_for_path(self.get_path_to_playlist(playlist_name))
+
+ def parse_callback(parser, uri, metadata, data):
+ if uri in uris:
+ uris.remove(uri)
+ self.emit('song-removed-from-playlist', playlist_name, uri)
+ else:
+ _iter = playlist.append()
+ playlist.set_value(_iter, TotemPlParser.PARSER_FIELD_URI, uri)
+
+ def end_callback(parser, uri, data):
+ parser.save(playlist, pl_file, playlist_name, TotemPlParser.ParserType.PLS)
+
+ parser.connect('entry-parsed', parse_callback, playlist)
+ parser.connect('playlist-ended', end_callback, playlist)
+ parser.parse_async(
+ GLib.filename_to_uri(self.get_path_to_playlist(playlist_name), None),
+ False, None, None, None
+ )
+
+ def delete_playlist(self, playlist_name):
+ playlist_file = self.get_path_to_playlist(playlist_name)
+ if os.path.isfile(playlist_file):
+ os.remove(playlist_file)
+ self.emit('playlist-deleted', playlist_name)
+
+ def get_path_to_playlist(self, playlist_name):
+ return os.path.join(self.playlist_dir, "%s.pls" % playlist_name)
+
+ def parse_playlist(self, playlist_name, callback):
+ parser = TotemPlParser.Parser()
+ parser.connect('entry-parsed', self._on_entry_parsed, callback)
+ parser.parse_async(
+ GLib.filename_to_uri(self.get_path_to_playlist(playlist_name), None),
+ False, None, None, None
+ )
+
+ def _on_entry_parsed(self, parser, uri, metadata, data=None):
+ filename = GLib.filename_from_uri(uri)[0]
+ if filename and not os.path.isfile(filename):
+ return
+
+ grilo.get_media_from_uri(uri, data)
diff --git a/gnomemusic/query.py b/gnomemusic/query.py
index b350e22..7b7227e 100644
--- a/gnomemusic/query.py
+++ b/gnomemusic/query.py
@@ -275,3 +275,24 @@ class Query():
}
""".replace("\n", " ").strip() % {'album_id': album_id}
return query
+
+ @staticmethod
+ def get_song_with_url(url):
+ query = '''
+ SELECT DISTINCT
+ rdf:type(?song)
+ tracker:id(?song) AS id
+ nie:url(?song) AS url
+ nie:title(?song) AS title
+ nmm:artistName(nmm:performer(?song)) AS artist
+ nie:title(nmm:musicAlbum(?song)) AS album
+ nfo:duration(?song) AS duration
+ WHERE {
+ ?song a nmm:MusicPiece .
+ FILTER (
+ nie:url(?song) = '%(url)s'
+ )
+ }
+ '''.replace('\n', ' ').strip() % {'url': url}
+
+ return query
diff --git a/gnomemusic/toolbar.py b/gnomemusic/toolbar.py
index 6b1d700..2890ae5 100644
--- a/gnomemusic/toolbar.py
+++ b/gnomemusic/toolbar.py
@@ -48,7 +48,8 @@ class ToolbarState:
class Toolbar(GObject.GObject):
__gsignals__ = {
- 'state-changed': (GObject.SIGNAL_RUN_FIRST, None, ())
+ 'state-changed': (GObject.SIGNAL_RUN_FIRST, None, ()),
+ 'selection-mode-changed': (GObject.SIGNAL_RUN_FIRST, None, ()),
}
_selectionMode = False
_maximized = False
@@ -69,6 +70,7 @@ class Toolbar(GObject.GObject):
self._close_button = self._ui.get_object('close-button')
self._selection_menu = self._ui.get_object('selection-menu')
self._selection_menu_button = self._ui.get_object('selection-menu-button')
+ self._selection_menu_label = self._ui.get_object('selection-menu-button-label')
self._selection_menu_button.set_relief(Gtk.ReliefStyle.NONE)
if Gtk.Widget.get_default_direction() is Gtk.TextDirection.RTL:
_back_button_image = self._ui.get_object('back-button-image')
@@ -129,6 +131,7 @@ class Toolbar(GObject.GObject):
self._select_button.set_active(False)
self._select_button.show()
self._cancel_button.hide()
+ self.emit('selection-mode-changed')
self._update()
def on_back_button_clicked(self, widget):
diff --git a/gnomemusic/view.py b/gnomemusic/view.py
index f7a2dab..d1e750f 100644
--- a/gnomemusic/view.py
+++ b/gnomemusic/view.py
@@ -40,12 +40,16 @@ from gi.repository import GLib
from gi.repository import GdkPixbuf
from gi.repository import Tracker
from gi.repository import Gio
-from gettext import gettext as _
+
+from gettext import gettext as _, ngettext
from gnomemusic.grilo import grilo
+from gnomemusic.toolbar import ToolbarState
import gnomemusic.widgets as Widgets
+from gnomemusic.playlists import Playlists
from gnomemusic.query import Query
from gnomemusic.albumArtCache import AlbumArtCache as albumArtCache
tracker = Tracker.SparqlConnection.get(None)
+playlists = Playlists.get_default()
if Gtk.get_minor_version() > 8:
from gi.repository.Gtk import Stack, StackTransitionType
@@ -63,10 +67,10 @@ class ViewContainer(Stack):
countQuery = None
filter = None
- def __init__(self, title, header_bar, selection_toolbar, useStack=False):
+ def __init__(self, title, header_bar, selection_toolbar, use_sidebar=False, sidebar=None):
Stack.__init__(self,
transition_type=StackTransitionType.CROSSFADE)
- self._grid = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)
+ self._grid = Gtk.Grid(orientation=Gtk.Orientation.HORIZONTAL)
self._iconWidth = -1
self._iconHeight = 128
self._offset = 0
@@ -96,16 +100,19 @@ class ViewContainer(Stack):
self.selection_toolbar = selection_toolbar
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
box.pack_start(self.view, True, True, 0)
- if useStack:
+ if use_sidebar:
self.stack = Stack(
transition_type=StackTransitionType.SLIDE_RIGHT,
)
dummy = Gtk.Frame(visible=False)
self.stack.add_named(dummy, 'dummy')
- self.stack.add_named(box, 'artists')
+ if sidebar:
+ self.stack.add_named(sidebar, 'sidebar')
+ else:
+ self.stack.add_named(box, 'sidebar')
self.stack.set_visible_child_name('dummy')
self._grid.add(self.stack)
- else:
+ if not use_sidebar or sidebar:
self._grid.add(box)
self._cached_count = -1
@@ -136,6 +143,8 @@ class ViewContainer(Stack):
grilo.connect('ready', self._on_grilo_ready)
self.header_bar.header_bar.connect('state-changed',
self._on_state_changed)
+ self.header_bar.connect('selection-mode-changed',
+ self._on_selection_mode_changed)
self.view.connect('view-selection-changed',
self._on_view_selection_changed)
@@ -151,7 +160,8 @@ class ViewContainer(Stack):
self.view.set_selection_mode(True)
self.header_bar.set_selection_mode(True)
self.selection_toolbar.eventbox.set_visible(True)
- self.selection_toolbar._add_to_playlist_button.sensitive = False
+ self.selection_toolbar._add_to_playlist_button.set_sensitive(False)
+ self.selection_toolbar._remove_from_playlist_button.set_sensitive(False)
else:
self.view.set_selection_mode(False)
self.header_bar.set_selection_mode(False)
@@ -174,8 +184,15 @@ class ViewContainer(Stack):
def _on_view_selection_changed(self, widget):
items = self.view.get_selection()
- self.selection_toolbar\
- ._add_to_playlist_button.set_sensitive(len(items) > 0)
+ self.selection_toolbar._add_to_playlist_button.\
+ set_sensitive(len(items) > 0)
+ self.selection_toolbar._remove_from_playlist_button.\
+ set_sensitive(len(items) > 0)
+ if len(items) > 0:
+ self.header_bar._selection_menu_label.set_text(
+ ngettext(_("Selected %d item"), _("Selected %d items"), len(items)) % len(items))
+ else:
+ self.header_bar._selection_menu_label.set_text(_("Click on items to select them"))
def _populate(self, data=None):
self._init = True
@@ -184,6 +201,9 @@ class ViewContainer(Stack):
def _on_state_changed(self, widget, data=None):
pass
+ def _on_selection_mode_changed(self, widget, data=None):
+ pass
+
def _connect_view(self):
self._adjustmentValueId = self.vadjustment.connect(
'value-changed',
@@ -219,7 +239,7 @@ class ViewContainer(Stack):
if error:
self._model.set(_iter, [8, 10], [self.errorIconName, True])
- def _add_item(self, source, param, item):
+ def _add_item(self, source, param, item, remaining):
if not item:
return
self._offset += 1
@@ -280,6 +300,9 @@ class ViewContainer(Stack):
def _on_selection_mode_request(self, *args):
self.header_bar._select_button.clicked()
+ def get_selected_track_uris(self, callback):
+ callback([])
+
#Class for the Empty View
class Empty(Stack):
@@ -305,6 +328,9 @@ class Albums(ViewContainer):
self._albumWidget = Widgets.AlbumWidget(player)
self.player = player
self.add(self._albumWidget)
+ self.albums_selected = []
+ self.items_selected = []
+ self.items_selected_callback = None
def _back_button_clicked(self, widget, data=None):
self.set_visible_child(self._grid)
@@ -327,6 +353,37 @@ class Albums(ViewContainer):
if grilo.tracker:
GLib.idle_add(grilo.populate_albums, self._offset, self._add_item)
+ def get_selected_track_uris(self, callback):
+ if self.header_bar._state == ToolbarState.SINGLE:
+ uris = []
+ for path in self._albumWidget.view.get_selection():
+ _iter = self._albumWidget.model.get_iter(path)
+ uris.append(self._albumWidget.model.get_value(_iter, 5).get_url())
+ callback(uris)
+ else:
+ self.items_selected = []
+ self.items_selected_callback = callback
+ self.albums_index = 0
+ self.albums_selected = [self.filter.get_value(self.filter.get_iter(path), 5)
+ for path in self.view.get_selection()]
+ if len(self.albums_selected):
+ self._get_selected_album_songs()
+
+ def _get_selected_album_songs(self):
+ grilo.populate_album_songs(
+ self.albums_selected[self.albums_index].get_id(),
+ self._add_selected_item)
+ self.albums_index += 1
+
+ def _add_selected_item(self, source, param, item, remaining):
+ if item:
+ self.items_selected.append(item.get_url())
+ if remaining == 0:
+ if self.albums_index < len(self.albums_selected):
+ self._get_selected_album_songs()
+ else:
+ self.items_selected_callback(self.items_selected)
+
class Songs(ViewContainer):
def __init__(self, header_bar, selection_toolbar, player):
@@ -367,7 +424,7 @@ class Songs(ViewContainer):
self.iter_to_clean = child_iter.copy()
return False
- def _add_item(self, source, param, item):
+ def _add_item(self, source, param, item, remaining):
if not item:
return
self._offset += 1
@@ -478,11 +535,9 @@ class Songs(ViewContainer):
if grilo.tracker:
GLib.idle_add(grilo.populate_songs, self._offset, self._add_item)
-
-class Playlist(ViewContainer):
- def __init__(self, header_bar, selection_toolbar, player):
- ViewContainer.__init__(self, _("Playlists"), header_bar,
- selection_toolbar)
+ def get_selected_track_uris(self, callback):
+ callback([self.filter.get_value(self.filter.get_iter(path), 5).get_url()
+ for path in self.view.get_selection()])
class Artists (ViewContainer):
@@ -492,6 +547,9 @@ class Artists (ViewContainer):
self.artists_counter = 0
self.player = player
self._artists = {}
+ self.albums_selected = []
+ self.items_selected = []
+ self.items_selected_callback = None
self.countQuery = Query.ARTISTS_COUNT
self.artistAlbumsStack = Stack(
transition_type=StackTransitionType.CROSSFADE,
@@ -500,15 +558,13 @@ class Artists (ViewContainer):
shadow_type=Gtk.ShadowType.NONE,
hexpand=True
)
- self.artistAlbumsStack.add_named(self._artistAlbumsWidget, "artists")
- self.artistAlbumsStack.set_visible_child_name("artists")
+ self.artistAlbumsStack.add_named(self._artistAlbumsWidget, "sidebar")
+ self.artistAlbumsStack.set_visible_child_name("sidebar")
self.view.set_view_type(Gd.MainViewType.LIST)
self.view.set_hexpand(False)
self.view.get_style_context().add_class('artist-panel')
self.view.get_generic_view().get_selection().set_mode(
Gtk.SelectionMode.SINGLE)
- self._grid.attach(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL),
- 1, 0, 1, 1)
self._grid.attach(self.artistAlbumsStack, 2, 0, 2, 2)
self._add_list_renderers()
if (Gtk.Settings.get_default().get_property(
@@ -582,7 +638,7 @@ class Artists (ViewContainer):
self._artistAlbumsWidget = self.new_artistAlbumsWidget
GLib.idle_add(self.artistAlbumsStack.set_visible_child_name, child_name)
- def _add_item(self, source, param, item):
+ def _add_item(self, source, param, item, remaining):
if item is None:
return
self._offset += 1
@@ -613,3 +669,370 @@ class Artists (ViewContainer):
if self._last_selection is not None:
self.view.get_generic_view().get_selection().select_iter(
self._last_selection)
+
+ def get_selected_track_uris(self, callback):
+ self.items_selected = []
+ self.items_selected_callback = callback
+ self.albums_index = 0
+ self.albums_selected = []
+
+ for path in self.view.get_selection():
+ _iter = self.filter.get_iter(path)
+ artist = self.filter.get_value(_iter, 2)
+ albums = self._artists[artist.lower()]['albums']
+ if (self.filter.get_string_from_iter(_iter) !=
+ self.filter.get_string_from_iter(self._allIter)):
+ self.albums_selected.extend(albums)
+
+ if len(self.albums_selected):
+ self._get_selected_album_songs()
+
+ def _get_selected_album_songs(self):
+ grilo.populate_album_songs(
+ self.albums_selected[self.albums_index].get_id(),
+ self._add_selected_item)
+ self.albums_index += 1
+
+ def _add_selected_item(self, source, param, item, remaining):
+ if item:
+ self.items_selected.append(item.get_url())
+ if remaining == 0:
+ if self.albums_index < len(self.albums_selected):
+ self._get_selected_album_songs()
+ else:
+ self.items_selected_callback(self.items_selected)
+
+
+class Playlist(ViewContainer):
+ playlists_list = playlists.get_playlists()
+
+ def __init__(self, header_bar, selection_toolbar, player):
+ self.playlists_sidebar = Gd.MainView(
+ shadow_type=Gtk.ShadowType.NONE
+ )
+
+ ViewContainer.__init__(self, _("Playlists"), header_bar,
+ selection_toolbar, True, self.playlists_sidebar)
+
+ self.view.set_view_type(Gd.MainViewType.LIST)
+ self.view.get_generic_view().get_style_context()\
+ .add_class('songs-list')
+ self._add_list_renderers()
+
+ builder = Gtk.Builder()
+ builder.add_from_resource('/org/gnome/Music/PlaylistControls.ui')
+ self.headerbar = builder.get_object('grid')
+ self.name_label = builder.get_object('playlist_name')
+ self.songs_count_label = builder.get_object('songs_count')
+ self.menubutton = builder.get_object('playlist_menubutton')
+ self.play_menuitem = builder.get_object('menuitem_play')
+ self.play_menuitem.connect('activate', self._on_play_activate)
+ self.delete_menuitem = builder.get_object('menuitem_delete')
+ self.delete_menuitem.connect('activate', self._on_delete_activate)
+ self._grid.insert_row(0)
+ self._grid.attach(self.headerbar, 1, 0, 1, 1)
+
+ self.playlists_model = Gtk.ListStore(
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GdkPixbuf.Pixbuf,
+ GObject.TYPE_OBJECT,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_INT,
+ GObject.TYPE_STRING,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_BOOLEAN
+ )
+ self.playlists_sidebar.set_model(self.playlists_model)
+ self.playlists_sidebar.set_view_type(Gd.MainViewType.LIST)
+ self.playlists_sidebar.set_hexpand(False)
+ self.playlists_sidebar.get_style_context().add_class('artist-panel')
+ self.playlists_sidebar.get_generic_view().get_selection().set_mode(
+ Gtk.SelectionMode.SINGLE)
+ self.playlists_sidebar.connect('item-activated', self._on_playlist_activated)
+ self._grid.insert_column(0)
+ self._grid.child_set_property(self.stack, 'top-attach', 0)
+ self._grid.child_set_property(self.stack, 'height', 2)
+ self._add_sidebar_renderers()
+ if (Gtk.Settings.get_default().get_property(
+ 'gtk_application_prefer_dark_theme')):
+ self.playlists_sidebar.get_generic_view().get_style_context().\
+ add_class("artist-panel-dark")
+ else:
+ self.playlists_sidebar.get_generic_view().get_style_context().\
+ add_class("artist-panel-white")
+
+ self.monitors = []
+ self.iter_to_clean = None
+ self.iter_to_clean_model = None
+ self.current_playlist = None
+ self.songs_count = 0
+ self._update_songs_count()
+ self.player = player
+ self.player.connect('playlist-item-changed', self.update_model)
+ playlists.connect('playlist-created', self._on_playlist_created)
+ playlists.connect('song-added-to-playlist', self._on_song_added_to_playlist)
+ playlists.connect('song-removed-from-playlist', self._on_song_removed_from_playlist)
+ self.show_all()
+
+ def _add_list_renderers(self):
+ list_widget = self.view.get_generic_view()
+ cols = list_widget.get_columns()
+ cells = cols[0].get_cells()
+ cells[2].set_visible(False)
+ now_playing_symbol_renderer = Gtk.CellRendererPixbuf(xalign=1.0)
+
+ column_now_playing = Gtk.TreeViewColumn()
+ column_now_playing.set_property('fixed_width', 24)
+ column_now_playing.pack_start(now_playing_symbol_renderer, False)
+ column_now_playing.add_attribute(now_playing_symbol_renderer,
+ 'visible', 10)
+ column_now_playing.add_attribute(now_playing_symbol_renderer,
+ 'icon_name', 8)
+ list_widget.insert_column(column_now_playing, 0)
+
+ title_renderer = Gtk.CellRendererText(
+ xpad=0,
+ xalign=0.0,
+ yalign=0.5,
+ height=48,
+ ellipsize=Pango.EllipsizeMode.END
+ )
+ list_widget.add_renderer(title_renderer,
+ self._on_list_widget_title_render, None)
+ cols[0].add_attribute(title_renderer, 'text', 2)
+
+ star_renderer = Gtk.CellRendererPixbuf(
+ xpad=32,
+ icon_name=self.starIconName
+ )
+ list_widget.add_renderer(star_renderer,
+ self._on_list_widget_star_render, None)
+ cols[0].add_attribute(star_renderer, 'visible', 9)
+
+ duration_renderer = Gd.StyledTextRenderer(
+ xpad=32,
+ xalign=1.0
+ )
+ duration_renderer.add_class('dim-label')
+ list_widget.add_renderer(duration_renderer,
+ self._on_list_widget_duration_render, None)
+
+ artist_renderer = Gd.StyledTextRenderer(
+ xpad=32,
+ ellipsize=Pango.EllipsizeMode.END
+ )
+ artist_renderer.add_class('dim-label')
+ list_widget.add_renderer(artist_renderer,
+ self._on_list_widget_artist_render, None)
+ cols[0].add_attribute(artist_renderer, 'text', 3)
+
+ type_renderer = Gd.StyledTextRenderer(
+ xpad=32,
+ ellipsize=Pango.EllipsizeMode.END
+ )
+ type_renderer.add_class('dim-label')
+ list_widget.add_renderer(type_renderer,
+ self._on_list_widget_type_render, None)
+
+ def _add_sidebar_renderers(self):
+ list_widget = self.playlists_sidebar.get_generic_view()
+
+ cols = list_widget.get_columns()
+ cells = cols[0].get_cells()
+ cells[1].set_visible(False)
+ cells[2].set_visible(False)
+ type_renderer = Gd.StyledTextRenderer(
+ xpad=16,
+ ypad=16,
+ ellipsize=Pango.EllipsizeMode.END,
+ xalign=0.0,
+ width=220
+ )
+ list_widget.add_renderer(type_renderer, lambda *args: None, None)
+ cols[0].clear_attributes(type_renderer)
+ cols[0].add_attribute(type_renderer, "text", 2)
+
+ def _on_list_widget_title_render(self, col, cell, model, _iter, data):
+ pass
+
+ def _on_list_widget_star_render(self, col, cell, model, _iter, data):
+ pass
+
+ def _on_list_widget_duration_render(self, col, cell, model, _iter, data):
+ item = model.get_value(_iter, 5)
+ if item:
+ seconds = item.get_duration()
+ minutes = seconds // 60
+ seconds %= 60
+ cell.set_property('text', '%i:%02i' % (minutes, seconds))
+
+ def _on_list_widget_artist_render(self, col, cell, model, _iter, data):
+ pass
+
+ def _on_list_widget_type_render(self, coll, cell, model, _iter, data):
+ item = model.get_value(_iter, 5)
+ if item:
+ cell.set_property('text', item.get_string(Grl.METADATA_KEY_ALBUM))
+
+ def _populate(self):
+ self._init = True
+ self.populate()
+
+ def update_model(self, player, playlist, currentIter):
+ if self.iter_to_clean:
+ self.iter_to_clean_model.set_value(self.iter_to_clean, 10, False)
+ if playlist != self.filter:
+ return False
+
+ child_iter = self.filter.convert_iter_to_child_iter(currentIter)
+ self._model.set_value(child_iter, 10, True)
+ if self._model.get_value(child_iter, 8) != self.errorIconName:
+ self.iter_to_clean = child_iter.copy()
+ self.iter_to_clean_model = self._model
+ return False
+
+ def _add_playlist_item(self, item):
+ _iter = self.playlists_model.append()
+ self.playlists_model.set(_iter, [2], [item])
+
+ def _on_item_activated(self, widget, id, path):
+ _iter = self.filter.get_iter(path)
+ child_iter = self.filter.convert_iter_to_child_iter(_iter)
+ if self._model.get_value(child_iter, 8) != self.errorIconName:
+ self.player.set_playlist('Playlist', self.current_playlist, self.filter, _iter, 5)
+ self.player.set_playing(True)
+
+ def _on_item_changed(self, monitor, file1, file2, event, _iter):
+ if self._model.iter_is_valid(_iter):
+ if event == Gio.FileMonitorEvent.DELETED:
+ self._model.set(_iter, [8, 10], [self.errorIconName, True])
+
+ def _on_playlist_activated(self, widget, item_id, path):
+ _iter = self.playlists_model.get_iter(path)
+ playlist = self.playlists_model.get_value(_iter, 2)
+ self.current_playlist = playlist
+ self.name_label.set_text(playlist)
+
+ # if the active queue has been set by this playlist,
+ # use it as model, otherwise build the liststore
+ cached_playlist = self.player.running_playlist('Playlist', playlist)
+ if cached_playlist:
+ self._model = cached_playlist.get_model()
+ self.filter = cached_playlist
+ currentTrack = self.player.playlist.get_iter(self.player.currentTrack.get_path())
+ self.update_model(self.player, cached_playlist,
+ currentTrack)
+ self.view.set_model(self.filter)
+ self.songs_count = self._model.iter_n_children(None)
+ self._update_songs_count()
+ else:
+ self._model = Gtk.ListStore(
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GdkPixbuf.Pixbuf,
+ GObject.TYPE_OBJECT,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_INT,
+ GObject.TYPE_STRING,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_BOOLEAN
+ )
+ self.filter = self._model.filter_new(None)
+ self.view.set_model(self.filter)
+ playlists.parse_playlist(playlist, self._add_item)
+ self.songs_count = 0
+ self._update_songs_count()
+
+ def _add_item(self, source, param, item):
+ self._add_item_to_model(item, self._model)
+
+ def _add_item_to_model(self, item, model):
+ if not item:
+ return
+ self._offset += 1
+ item.set_title(albumArtCache.get_media_title(item))
+ artist = item.get_string(Grl.METADATA_KEY_ARTIST)\
+ or item.get_author()\
+ or _("Unknown Artist")
+ _iter = model.insert_with_valuesv(
+ -1,
+ [2, 3, 5, 8, 9, 10],
+ [albumArtCache.get_media_title(item),
+ artist, item, self.nowPlayingIconName, False, False])
+ self.player.discover_item(item, self._on_discovered, _iter)
+ g_file = Gio.file_new_for_uri(item.get_url())
+ self.monitors.append(g_file.monitor_file(Gio.FileMonitorFlags.NONE,
+ None))
+ self.monitors[(self._offset - 1)].connect('changed',
+ self._on_item_changed, _iter)
+ self.songs_count += 1
+ self._update_songs_count()
+
+ def _update_songs_count(self):
+ self.songs_count_label.set_text(
+ ngettext(_("%d Song"), _("%d Songs"), self.songs_count)
+ % self.songs_count)
+
+ def _on_selection_mode_changed(self, widget, data=None):
+ self.playlists_sidebar.set_sensitive(not self.header_bar._selectionMode)
+ self.menubutton.set_sensitive(not self.header_bar._selectionMode)
+
+ def _on_play_activate(self, menuitem, data=None):
+ _iter = self._model.get_iter_first()
+ if not _iter:
+ return
+
+ self.view.get_generic_view().get_selection().\
+ select_path(self._model.get_path(_iter))
+ self.view.emit('item-activated', '0',
+ self._model.get_path(_iter))
+
+ def _on_delete_activate(self, menuitem, data=None):
+ _iter = self.playlists_sidebar.get_generic_view().get_selection().get_selected()[1]
+ if not _iter:
+ return
+
+ playlist = self.playlists_model.get_value(_iter, 2)
+ playlists.delete_playlist(playlist)
+ self.playlists_model.remove(_iter)
+
+ def _on_playlist_created(self, playlists, name):
+ self._add_playlist_item(name)
+
+ def _on_song_added_to_playlist(self, playlists, name, item):
+ if name == self.current_playlist:
+ self._add_item_to_model(item, self._model)
+ else:
+ cached_playlist = self.player.running_playlist('Playlist', name)
+ if cached_playlist and cached_playlist != self._model:
+ self._add_item_to_model(item, cached_playlist)
+
+ def _on_song_removed_from_playlist(self, playlists, name, uri):
+ if name == self.current_playlist:
+ model = self._model
+ else:
+ cached_playlist = self.player.running_playlist('Playlist', name)
+ if cached_playlist and cached_playlist != self._model:
+ model = cached_playlist
+ else:
+ return
+
+ for row in model:
+ if row[5].get_url() == uri:
+ self._model.remove(row.iter)
+ self.songs_count -= 1
+ self._update_songs_count()
+ return
+
+ def populate(self):
+ for item in sorted(self.playlists_list):
+ self._add_playlist_item(item)
+
+ def get_selected_track_uris(self, callback):
+ callback([self.filter.get_value(self.filter.get_iter(path), 5).get_url()
+ for path in self.view.get_selection()])
diff --git a/gnomemusic/widgets.py b/gnomemusic/widgets.py
index a8ab1d1..c56da6e 100644
--- a/gnomemusic/widgets.py
+++ b/gnomemusic/widgets.py
@@ -35,11 +35,13 @@ from gi.repository import Gtk, Gd, GLib, GObject, Pango
from gi.repository import GdkPixbuf, Gio
from gi.repository import Grl
from gi.repository import Tracker
-from gettext import gettext as _
+from gettext import gettext as _, ngettext
from gnomemusic.grilo import grilo
from gnomemusic.query import Query
from gnomemusic.albumArtCache import AlbumArtCache
+from gnomemusic.playlists import Playlists
+playlist = Playlists.get_default()
tracker = Tracker.SparqlConnection.get(None)
ALBUM_ART_CACHE = AlbumArtCache.get_default()
if Gtk.Widget.get_default_direction() is not Gtk.TextDirection.RTL:
@@ -254,6 +256,11 @@ class AlbumWidget(Gtk.EventBox):
items = self.view.get_selection()
self.selection_toolbar\
._add_to_playlist_button.set_sensitive(len(items) > 0)
+ if len(items) > 0:
+ self.header_bar._selection_menu_label.set_text(
+ ngettext(_("Selected %d item"), _("Selected %d items"), len(items)) % len(items))
+ else:
+ self.header_bar._selection_menu_label.set_text(_("Click on items to select them"))
def _on_header_cancel_button_clicked(self, button):
self.view.set_selection_mode(False)
@@ -267,6 +274,7 @@ class AlbumWidget(Gtk.EventBox):
self.player.eventBox.set_visible(False)
self.selection_toolbar.eventbox.set_visible(True)
self.selection_toolbar._add_to_playlist_button.set_sensitive(False)
+ self.header_bar.header_bar.set_custom_title(self.header_bar._selection_menu_button)
else:
self.view.set_selection_mode(False)
self.header_bar.set_selection_mode(False)
@@ -279,7 +287,7 @@ class AlbumWidget(Gtk.EventBox):
if error:
self.model.set(_iter, [7, 9], [ERROR_ICON_NAME, True])
- def _on_populate_album_songs(self, source, prefs, track):
+ def _on_populate_album_songs(self, source, prefs, track, remaining):
if track:
self.tracks.append(track)
self.duration = self.duration + track.get_duration()
@@ -492,7 +500,7 @@ class AllArtistsAlbums(ArtistAlbums):
GLib.idle_add(grilo.populate_albums,
self._offset, self.add_item, 5)
- def add_item(self, source, param, item):
+ def add_item(self, source, param, item, remaining):
if item:
self._offset += 1
self.add_album(item)
@@ -537,7 +545,7 @@ class ArtistAlbumWidget(Gtk.HBox):
song_widget.now_playing_sign.show()
song_widget.can_be_played = False
- def get_songs(self, source, prefs, track):
+ def get_songs(self, source, prefs, track, remaining):
if track:
self.tracks.append(track)
else:
@@ -602,3 +610,83 @@ class ArtistAlbumWidget(Gtk.HBox):
self.player.set_playlist('Artist', self.album,
widget.model, widget._iter, 5)
self.player.set_playing(True)
+
+
+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.view = self.ui.get_object('treeview1')
+ self.selection = self.ui.get_object('treeview-selection1')
+ self._add_list_renderers()
+ self.view.connect('row-activated', self._on_item_activated)
+
+ self.model = self.ui.get_object('liststore1')
+ playlist_names = playlist.get_playlists()
+ self.populate(playlist_names)
+
+ self.title_bar = self.ui.get_object('headerbar1')
+ if Gtk.get_minor_version() > 8:
+ self.dialog_box.set_titlebar(self.title_bar)
+ else:
+ self.dialog_box.get_content_area().add(self.title_bar)
+ self.dialog_box.get_content_area().reorder_child(self.title_bar, 0)
+
+ self._cancel_button = self.ui.get_object('cancel-button')
+ self._select_button = self.ui.get_object('select-button')
+ self._cancel_button.connect('clicked', self._on_cancel_button_clicked)
+ self._select_button.connect('clicked', self._on_selection)
+
+ def get_selected(self):
+ _iter = self.selection.get_selected()[1]
+
+ if not _iter or self.model[_iter][1]:
+ return None
+
+ return self.model[_iter][0]
+
+ def _add_list_renderers(self):
+ cols = Gtk.TreeViewColumn()
+ type_renderer = Gd.StyledTextRenderer(
+ xpad=16,
+ ypad=16,
+ ellipsize=Pango.EllipsizeMode.END,
+ xalign=0.0,
+ width=220
+ )
+ type_renderer.connect('editing-started', self._on_editing_started, None)
+ cols.pack_start(type_renderer, True)
+ cols.add_attribute(type_renderer, "text", 0)
+ cols.add_attribute(type_renderer, "editable", 1)
+ self.view.append_column(cols)
+
+ def populate(self, items):
+ for playlist_name in sorted(items):
+ self.model.append([playlist_name, False])
+ add_playlist_iter = self.model.append()
+ self.model.set(add_playlist_iter, [0, 1], [_("New Playlist"), True])
+
+ def _on_selection(self, select_button):
+ self.dialog_box.response(Gtk.ResponseType.ACCEPT)
+
+ def _on_cancel_button_clicked(self, cancel_button):
+ self.dialog_box.response(Gtk.ResponseType.REJECT)
+
+ def _on_item_activated(self, view, path, column):
+ _iter = self.model.get_iter(path)
+ if self.model.get_value(_iter, 1):
+ self.view.set_cursor(path, column, True)
+
+ def _on_editing_started(self, renderer, editable, path, data=None):
+ editable.set_text('')
+ editable.connect('editing-done', self._on_editing_done, None)
+
+ def _on_editing_done(self, editable, data=None):
+ _iter = self.selection.get_selected()[1]
+ if editable.get_text() != '':
+ playlist.create_playlist(editable.get_text())
+ new_iter = self.model.insert_before(_iter)
+ self.model.set(new_iter, [0, 1], [editable.get_text(), False])
diff --git a/gnomemusic/window.py b/gnomemusic/window.py
index fa4f372..c8d4337 100644
--- a/gnomemusic/window.py
+++ b/gnomemusic/window.py
@@ -31,13 +31,16 @@
from gi.repository import Gtk, Gdk, Gio, GLib, Tracker
-from gettext import gettext as _
+from gettext import gettext as _, ngettext
from gnomemusic.toolbar import Toolbar, ToolbarState
from gnomemusic.player import Player, SelectionToolbar
from gnomemusic.query import Query
import gnomemusic.view as Views
+import gnomemusic.widgets as Widgets
+from gnomemusic.playlists import Playlists
+playlist = Playlists.get_default()
tracker = Tracker.SparqlConnection.get(None)
if Gtk.get_minor_version() > 8:
@@ -55,7 +58,12 @@ class Window(Gtk.ApplicationWindow):
self.connect('focus-in-event', self._windows_focus_cb)
self.settings = Gio.Settings.new('org.gnome.Music')
self.add_action(self.settings.create_action('repeat'))
-
+ selectAll = Gio.SimpleAction.new('selectAll', None)
+ selectAll.connect('activate', self._on_select_all)
+ self.add_action(selectAll)
+ selectNone = Gio.SimpleAction.new('selectNone', None)
+ selectNone.connect('activate', self._on_select_none)
+ self.add_action(selectNone)
self.set_size_request(887, 640)
size_setting = self.settings.get_value('window-size')
@@ -154,7 +162,7 @@ class Window(Gtk.ApplicationWindow):
self.views.append(Views.Albums(self.toolbar, self.selection_toolbar, self.player))
self.views.append(Views.Artists(self.toolbar, self.selection_toolbar, self.player))
self.views.append(Views.Songs(self.toolbar, self.selection_toolbar, self.player))
- #self.views.append(Views.Playlist(self.toolbar, self.selection_toolbar, self.player))
+ self.views.append(Views.Playlist(self.toolbar, self.selection_toolbar, self.player))
for i in self.views:
self._stack.add_titled(i, i.title, i.title)
@@ -175,6 +183,11 @@ class Window(Gtk.ApplicationWindow):
self.toolbar._select_button.set_sensitive(False)
self.toolbar._search_button.connect('toggled', self._on_search_toggled)
+ self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
+ self.selection_toolbar._add_to_playlist_button.connect(
+ 'clicked', self._on_add_to_playlist_button_clicked)
+ self.selection_toolbar._remove_from_playlist_button.connect(
+ 'clicked', self._on_remove_from_playlist_button_clicked)
self.toolbar.set_state(ToolbarState.ALBUMS)
self.toolbar.header_bar.show()
@@ -182,6 +195,38 @@ class Window(Gtk.ApplicationWindow):
self._box.show()
self.show()
+ def _on_select_all(self, action, param):
+ if self.toolbar._state != ToolbarState.SINGLE:
+ model = self._stack.get_visible_child()._model
+ else:
+ model = self._stack.get_visible_child()._albumWidget.model
+ _iter = model.get_iter_first()
+ count = 0
+ while _iter is not None:
+ model.set(_iter, [6], [True])
+ _iter = model.iter_next(_iter)
+ count = count + 1
+ if count > 0:
+ self.toolbar._selection_menu_label.set_text(
+ ngettext(_("Selected %d item"), _("Selected %d items"), count) % count)
+ self.selection_toolbar._add_to_playlist_button.set_sensitive(True)
+ self.selection_toolbar._remove_from_playlist_button.set_sensitive(True)
+ elif count == 0:
+ self.toolbar._selection_menu_label.set_text(_("Click on items to select them"))
+
+ def _on_select_none(self, action, param):
+ if self.toolbar._state != ToolbarState.SINGLE:
+ model = self._stack.get_visible_child()._model
+ else:
+ model = self._stack.get_visible_child()._albumWidget.model
+ _iter = model.get_iter_first()
+ self.selection_toolbar._add_to_playlist_button.set_sensitive(False)
+ self.selection_toolbar._remove_from_playlist_button.set_sensitive(False)
+ while _iter is not None:
+ model.set(_iter, [6], [False])
+ _iter = model.iter_next(_iter)
+ self.toolbar._selection_menu_label.set_text(_("Click on items to select them"))
+
def _on_key_press(self, widget, event):
modifiers = Gtk.accelerator_get_default_mod_mask()
@@ -202,9 +247,10 @@ class Window(Gtk.ApplicationWindow):
def _on_notify_mode(self, stack, param):
#Slide out artist list on switching to artists view
- if stack.get_visible_child() == self.views[1]:
+ if stack.get_visible_child() == self.views[1] or \
+ stack.get_visible_child() == self.views[3]:
stack.get_visible_child().stack.set_visible_child_name('dummy')
- stack.get_visible_child().stack.set_visible_child_name('artists')
+ stack.get_visible_child().stack.set_visible_child_name('sidebar')
self.toolbar.searchbar.show_bar(False)
def _toggle_view(self, btn, i):
@@ -212,3 +258,42 @@ class Window(Gtk.ApplicationWindow):
def _on_search_toggled(self, button, data=None):
self.toolbar.searchbar.show_bar(button.get_active())
+
+ def _on_selection_mode_changed(self, widget, data=None):
+ if self.toolbar._selectionMode:
+ in_playlist = self._stack.get_visible_child() == self.views[3]
+ self.selection_toolbar._add_to_playlist_button.set_visible(not in_playlist)
+ self.selection_toolbar._remove_from_playlist_button.set_visible(in_playlist)
+
+ def _on_add_to_playlist_button_clicked(self, widget):
+ if self._stack.get_visible_child() == self.views[3]:
+ return
+
+ def callback(selected_uris):
+ if len(selected_uris) < 1:
+ return
+
+ add_to_playlist = Widgets.PlaylistDialog(self)
+ if add_to_playlist.dialog_box.run() == Gtk.ResponseType.ACCEPT:
+ playlist.add_to_playlist(
+ add_to_playlist.get_selected(),
+ selected_uris)
+ self.toolbar.set_selection_mode(False)
+ add_to_playlist.dialog_box.destroy()
+
+ self._stack.get_visible_child().get_selected_track_uris(callback)
+
+ def _on_remove_from_playlist_button_clicked(self, widget):
+ if self._stack.get_visible_child() != self.views[3]:
+ return
+
+ def callback(selected_uris):
+ if len(selected_uris) < 1:
+ return
+
+ playlist.remove_from_playlist(
+ self.views[3].current_playlist,
+ selected_uris)
+ self.toolbar.set_selection_mode(False)
+
+ self._stack.get_visible_child().get_selected_track_uris(callback)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2504b6e..5e7fea1 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -25,4 +25,5 @@ gnomemusic/window.py
[type: gettext/glade]data/NoMusic.ui
[type: gettext/glade]data/headerbar.ui.in
[type: gettext/glade]data/SelectionToolbar.ui
-
+[type: gettext/glade]data/PlaylistControls.ui
+[type: gettext/glade]data/PlaylistDialog.ui.in
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 0c10c9d..c4ce26f 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1,3 +1,4 @@
data/headerbar.ui
data/AboutDialog.ui
+data/PlaylistDialog.ui
data/gnome-music.appdata.xml
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]