[gnome-music] views: Split up the views
- From: Marinus Schraal <mschraal src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music] views: Split up the views
- Date: Thu, 6 Oct 2016 21:50:55 +0000 (UTC)
commit 0ccd89f64f4b81962ebf9a628ba65f3c6af06e3a
Author: Marinus Schraal <mschraal src gnome org>
Date: Tue Oct 4 16:05:18 2016 +0200
views: Split up the views
configure.ac | 1 +
gnomemusic/Makefile.am | 3 +-
gnomemusic/view.py | 1968 ----------------------------------
gnomemusic/views/Makefile.am | 12 +
gnomemusic/views/albumsview.py | 264 +++++
gnomemusic/views/artistsview.py | 228 ++++
gnomemusic/views/baseview.py | 302 ++++++
gnomemusic/views/emptysearchview.py | 62 ++
gnomemusic/views/initialstateview.py | 74 ++
gnomemusic/views/playlistview.py | 536 +++++++++
gnomemusic/views/searchview.py | 475 ++++++++
gnomemusic/views/songsview.py | 233 ++++
gnomemusic/window.py | 22 +-
po/POTFILES.in | 9 +-
14 files changed, 2211 insertions(+), 1978 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 4a407b4..f23d189 100644
--- a/configure.ac
+++ b/configure.ac
@@ -52,6 +52,7 @@ AC_CONFIG_FILES([
help/Makefile
data/AboutDialog.ui
gnomemusic/Makefile
+ gnomemusic/views/Makefile
po/Makefile.in
libgd/Makefile
])
diff --git a/gnomemusic/Makefile.am b/gnomemusic/Makefile.am
index 03dcec2..1d3587d 100644
--- a/gnomemusic/Makefile.am
+++ b/gnomemusic/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = views
+
appdir = $(pythondir)/gnomemusic/
app_PYTHON = \
@@ -8,7 +10,6 @@ app_PYTHON = \
mpris.py \
notification.py \
toolbar.py \
- view.py \
grilo.py \
playlists.py\
utils.py \
diff --git a/gnomemusic/views/Makefile.am b/gnomemusic/views/Makefile.am
new file mode 100644
index 0000000..e64db90
--- /dev/null
+++ b/gnomemusic/views/Makefile.am
@@ -0,0 +1,12 @@
+appdir = $(pythondir)/gnomemusic/views/
+
+app_PYTHON = \
+ __init__.py \
+ albumsview.py \
+ artistsview.py \
+ baseview.py \
+ emptysearchview.py \
+ initialstateview.py \
+ playlistview.py \
+ searchview.py \
+ songsview.py
diff --git a/gnomemusic/views/__init__.py b/gnomemusic/views/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gnomemusic/views/albumsview.py b/gnomemusic/views/albumsview.py
new file mode 100644
index 0000000..9f29b19
--- /dev/null
+++ b/gnomemusic/views/albumsview.py
@@ -0,0 +1,264 @@
+# Copyright (c) 2016 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+from gettext import gettext as _
+from gi.repository import GLib, GObject, Gtk
+
+from gnomemusic import log
+from gnomemusic.albumartcache import ArtSize
+from gnomemusic.grilo import grilo
+from gnomemusic.toolbar import ToolbarState
+from gnomemusic.views.baseview import BaseView
+import gnomemusic.utils as utils
+import gnomemusic.widgets as Widgets
+
+
+class AlbumsView(BaseView):
+
+ def __repr__(self):
+ return '<AlbumsView>'
+
+ @log
+ def __init__(self, window, player):
+ BaseView.__init__(self, 'albums', _("Albums"), window, None)
+ self._albumWidget = Widgets.AlbumWidget(player, self)
+ self.player = player
+ self.add(self._albumWidget)
+ self.albums_selected = []
+ self.all_items = []
+ self.items_selected = []
+ self.items_selected_callback = None
+ self._add_list_renderers()
+
+ @log
+ def _on_changes_pending(self, data=None):
+ if (self._init and not self.header_bar._selectionMode):
+ self._offset = 0
+ self._init = True
+ GLib.idle_add(self.populate)
+ grilo.changes_pending['Albums'] = False
+
+ @log
+ def _on_selection_mode_changed(self, widget, data=None):
+ if (not self.header_bar._selectionMode
+ and grilo.changes_pending['Albums']):
+ self._on_changes_pending()
+
+ @log
+ def _setup_view(self, view_type):
+ self.view = Gtk.FlowBox(homogeneous=True,
+ hexpand=True,
+ halign=Gtk.Align.FILL,
+ valign=Gtk.Align.START,
+ selection_mode=Gtk.SelectionMode.NONE,
+ margin=18,
+ row_spacing=12,
+ column_spacing=6,
+ min_children_per_line=1,
+ max_children_per_line=25)
+
+ self.view.connect('child-activated', self._on_child_activated)
+
+ scrolledwin = Gtk.ScrolledWindow()
+ scrolledwin.add(self.view)
+ scrolledwin.show()
+
+ self._box.add(scrolledwin)
+
+ @log
+ def _back_button_clicked(self, widget, data=None):
+ self.header_bar.reset_header_title()
+ self.set_visible_child(self._grid)
+
+ @log
+ def _on_child_activated(self, widget, child, user_data=None):
+ item = child.media_item
+
+ if self.star_handler.star_renderer_click:
+ self.star_handler.star_renderer_click = False
+ return
+
+ # Toggle the selection when in selection mode
+ if self.selection_mode:
+ child.check.set_active(not child.check.get_active())
+ return
+
+ title = utils.get_media_title(item)
+ self._escaped_title = title
+ self._artist = utils.get_artist_name(item)
+
+ self._albumWidget.update(self._artist, title, item,
+ self.header_bar, self.selection_toolbar)
+
+ self.header_bar.set_state(ToolbarState.CHILD_VIEW)
+ self.header_bar.header_bar.set_title(self._escaped_title)
+ self.header_bar.header_bar.sub_title = self._artist
+ self.set_visible_child(self._albumWidget)
+
+ @log
+ def update_title(self):
+ self.header_bar.header_bar.set_title(self._escaped_title)
+ self.header_bar.header_bar.sub_title = self._artist
+
+ @log
+ def populate(self):
+ if grilo.tracker:
+ self.window._init_loading_notification()
+ GLib.idle_add(grilo.populate_albums, self._offset, self._add_item)
+
+ @log
+ def get_selected_tracks(self, callback):
+ if self.header_bar._state == ToolbarState.CHILD_VIEW:
+ items = []
+ for path in self._albumWidget.view.get_selection():
+ _iter = self._albumWidget.model.get_iter(path)
+ items.append(self._albumWidget.model.get_value(_iter, 5))
+ callback(items)
+ else:
+ self.items_selected = []
+ self.items_selected_callback = callback
+ self.albums_index = 0
+ if len(self.albums_selected):
+ self._get_selected_album_songs()
+
+ @log
+ def _add_item(self, source, param, item, remaining=0, data=None):
+ self.window.notification.set_timeout(0)
+
+ if item:
+ # Store all items to optimize 'Select All' action
+ self.all_items.append(item)
+
+ # Add to the flowbox
+ child = self._create_album_item(item)
+ self.view.add(child)
+ elif remaining == 0:
+ self.window.notification.dismiss()
+ self.view.show()
+
+ def _create_album_item(self, item):
+ artist = utils.get_artist_name(item)
+ title = utils.get_media_title(item)
+
+ builder = Gtk.Builder.new_from_resource('/org/gnome/Music/AlbumCover.ui')
+
+ child = Gtk.FlowBoxChild()
+ child.image = builder.get_object('image')
+ child.check = builder.get_object('check')
+ child.title = builder.get_object('title')
+ child.subtitle = builder.get_object('subtitle')
+ child.events = builder.get_object('events')
+ child.media_item = item
+
+ child.title.set_label(title)
+ child.subtitle.set_label(artist)
+
+ child.image.set_from_surface(self._loading_icon_surface)
+ # In the case of off-sized icons (eg. provided in the soundfile)
+ # keep the size request equal to all other icons to get proper
+ # alignment with GtkFlowBox.
+ child.image.set_property("width-request", ArtSize.medium.width)
+ child.image.set_property("height-request", ArtSize.medium.height)
+
+ child.events.connect('button-release-event',
+ self._on_album_event_triggered,
+ child)
+
+ child.check_handler_id = child.check.connect('notify::active',
+ self._on_child_toggled,
+ child)
+
+ child.check.bind_property('visible', self, 'selection_mode',
+ GObject.BindingFlags.BIDIRECTIONAL)
+
+ child.add(builder.get_object('main_box'))
+ child.show()
+
+ self.cache.lookup(item,
+ ArtSize.medium,
+ self._on_lookup_ready,
+ child)
+
+ return child
+
+ @log
+ def _on_album_event_triggered(self, evbox, event, child):
+ if event.button is 3:
+ self._on_selection_mode_request()
+ if self.selection_mode:
+ child.check.set_active(True)
+
+ def _on_lookup_ready(self, icon, child):
+ child.image.set_from_surface(icon)
+
+ @log
+ def _on_child_toggled(self, check, pspec, child):
+ if check.get_active() and not child.media_item in self.albums_selected:
+ self.albums_selected.append(child.media_item)
+ elif not check.get_active() and child.media_item in self.albums_selected:
+ self.albums_selected.remove(child.media_item)
+
+ self.update_header_from_selection(len(self.albums_selected))
+
+ @log
+ def _get_selected_album_songs(self):
+ grilo.populate_album_songs(
+ self.albums_selected[self.albums_index],
+ self._add_selected_item)
+ self.albums_index += 1
+
+ @log
+ def _add_selected_item(self, source, param, item, remaining=0, data=None):
+ if item:
+ self.items_selected.append(item)
+ if remaining == 0:
+ if self.albums_index < len(self.albums_selected):
+ self._get_selected_album_songs()
+ else:
+ self.items_selected_callback(self.items_selected)
+
+ def _toggle_all_selection(self, selected):
+ """
+ Selects or unselects all items without sending the notify::active
+ signal for performance purposes.
+ """
+ for child in self.view.get_children():
+ GObject.signal_handler_block(child.check, child.check_handler_id)
+
+ # Set the checkbutton state without emiting the signal
+ child.check.set_active(selected)
+
+ GObject.signal_handler_unblock(child.check, child.check_handler_id)
+
+ self.update_header_from_selection(len(self.albums_selected))
+
+ @log
+ def select_all(self):
+ self.albums_selected = list(self.all_items)
+ self._toggle_all_selection(True)
+
+ @log
+ def unselect_all(self):
+ self.albums_selected = []
+ self._toggle_all_selection(False)
diff --git a/gnomemusic/views/artistsview.py b/gnomemusic/views/artistsview.py
new file mode 100644
index 0000000..fbee543
--- /dev/null
+++ b/gnomemusic/views/artistsview.py
@@ -0,0 +1,228 @@
+# Copyright (c) 2016 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+from gettext import gettext as _
+from gi.repository import Gd, GLib, Gtk, Pango
+
+from gnomemusic import log
+from gnomemusic.grilo import grilo
+from gnomemusic.views.baseview import BaseView
+import gnomemusic.utils as utils
+import gnomemusic.widgets as Widgets
+
+
+class ArtistsView(BaseView):
+
+ def __repr__(self):
+ return '<ArtistsView>'
+
+ @log
+ def __init__(self, window, player):
+ BaseView.__init__(self, 'artists', _("Artists"), window,
+ Gd.MainViewType.LIST, True)
+ self.artists_counter = 0
+ self.player = player
+ self._artists = {}
+ self.albums_selected = []
+ self.items_selected = []
+ self.items_selected_callback = None
+ self.artistAlbumsStack = Gtk.Stack(
+ transition_type=Gtk.StackTransitionType.CROSSFADE,
+ )
+ self._artistAlbumsWidget = Gtk.Frame(
+ shadow_type=Gtk.ShadowType.NONE,
+ hexpand=True
+ )
+ self.artistAlbumsStack.add_named(self._artistAlbumsWidget, "sidebar")
+ self.artistAlbumsStack.set_visible_child_name("sidebar")
+ self.view.set_shadow_type(Gtk.ShadowType.IN)
+ self.view.get_style_context().add_class('side-panel')
+ self.view.set_hexpand(False)
+ self.view.get_generic_view().get_selection().set_mode(
+ Gtk.SelectionMode.SINGLE)
+ self._grid.attach(self.artistAlbumsStack, 2, 0, 2, 2)
+ self._add_list_renderers()
+ self.view.get_generic_view().get_style_context().remove_class('content-view')
+ self.show_all()
+ self.view.hide()
+
+ @log
+ def _on_changes_pending(self, data=None):
+ if (self._init and self.header_bar._selectionMode is False):
+ self.model.clear()
+ self._artists.clear()
+ self._offset = 0
+ GLib.idle_add(self._populate)
+ grilo.changes_pending['Artists'] = False
+
+ @log
+ def _populate(self, data=None):
+ self._init = True
+ self.populate()
+
+ @log
+ def _add_list_renderers(self):
+ list_widget = self.view.get_generic_view()
+
+ cols = list_widget.get_columns()
+ cells = cols[0].get_cells()
+ cells[1].set_visible(False)
+ cells[2].set_visible(False)
+ self.text_renderer = Gd.StyledTextRenderer(
+ xpad=16,
+ ypad=16,
+ ellipsize=Pango.EllipsizeMode.END,
+ xalign=0.0,
+ width=220
+ )
+ list_widget.add_renderer(self.text_renderer, lambda *args: None, None)
+ cols[0].clear_attributes(self.text_renderer)
+ cols[0].add_attribute(self.text_renderer, 'text', 2)
+
+ @log
+ def _on_item_activated(self, widget, item_id, path):
+ if self.star_handler.star_renderer_click:
+ self.star_handler.star_renderer_click = False
+ return
+
+ try:
+ _iter = self.model.get_iter(path)
+ except TypeError:
+ return
+ self._last_selection = _iter
+ artist = self.model.get_value(_iter, 2)
+ albums = self._artists[artist.casefold()]['albums']
+
+ widget = self._artists[artist.casefold()]['widget']
+ if widget:
+ if widget.model == self.player.running_playlist('Artist', widget.artist):
+ self._artistAlbumsWidget = widget.get_parent()
+ GLib.idle_add(self.artistAlbumsStack.set_visible_child,
+ self._artistAlbumsWidget)
+ return
+ elif widget.get_parent() == self._artistAlbumsWidget:
+ return
+ else:
+ widget.get_parent().destroy()
+
+ # Prepare a new artistAlbumsWidget here
+ new_artistAlbumsWidget = Gtk.Frame(
+ shadow_type=Gtk.ShadowType.NONE,
+ hexpand=True
+ )
+ self.artistAlbumsStack.add(new_artistAlbumsWidget)
+
+ artistAlbums = None
+
+ artistAlbums = Widgets.ArtistAlbums(
+ artist, albums, self.player,
+ self.header_bar, self.selection_toolbar, self.window
+ )
+ self._artists[artist.casefold()]['widget'] = artistAlbums
+ new_artistAlbumsWidget.add(artistAlbums)
+ new_artistAlbumsWidget.show()
+
+ # Replace previous widget
+ self._artistAlbumsWidget = new_artistAlbumsWidget
+ GLib.idle_add(self.artistAlbumsStack.set_visible_child, new_artistAlbumsWidget)
+
+ @log
+ def _add_item(self, source, param, item, remaining=0, data=None):
+ self.window.notification.set_timeout(0)
+ if item is None:
+ if remaining == 0:
+ self.view.set_model(self.model)
+ self.window.notification.dismiss()
+ self.view.show()
+ return
+ self._offset += 1
+ artist = utils.get_artist_name(item)
+ if not artist.casefold() in self._artists:
+ _iter = self.model.insert_with_valuesv(-1, [2], [artist])
+ self._artists[artist.casefold()] = {'iter': _iter, 'albums': [], 'widget': None}
+
+ self._artists[artist.casefold()]['albums'].append(item)
+
+ @log
+ def populate(self):
+ if grilo.tracker:
+ self.window._init_loading_notification()
+ GLib.idle_add(grilo.populate_artists, self._offset, self._add_item)
+
+ @log
+ def _on_header_bar_toggled(self, button):
+ BaseView._on_header_bar_toggled(self, button)
+
+ if button.get_active():
+ self.text_renderer.set_fixed_size(178, -1)
+ self._last_selection =\
+ self.view.get_generic_view().get_selection().get_selected()[1]
+ self.view.get_generic_view().get_selection().set_mode(
+ Gtk.SelectionMode.NONE)
+ else:
+ self.text_renderer.set_fixed_size(220, -1)
+ self.view.get_generic_view().get_selection().set_mode(
+ Gtk.SelectionMode.SINGLE)
+ if self._last_selection is not None:
+ self.view.get_generic_view().get_selection().select_iter(
+ self._last_selection)
+
+ @log
+ def _on_selection_mode_changed(self, widget, data=None):
+ self.artistAlbumsStack.set_sensitive(not self.header_bar._selectionMode)
+ if self.header_bar._selectionMode is False and grilo.changes_pending['Artists'] is True:
+ self._on_changes_pending()
+
+ @log
+ def get_selected_tracks(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.model.get_iter(path)
+ artist = self.model.get_value(_iter, 2)
+ albums = self._artists[artist.casefold()]['albums']
+ self.albums_selected.extend(albums)
+
+ if len(self.albums_selected):
+ self._get_selected_album_songs()
+
+ @log
+ def _get_selected_album_songs(self):
+ grilo.populate_album_songs(
+ self.albums_selected[self.albums_index],
+ self._add_selected_item)
+ self.albums_index += 1
+
+ @log
+ def _add_selected_item(self, source, param, item, remaining=0, data=None):
+ if item:
+ self.items_selected.append(item)
+ if remaining == 0:
+ if self.albums_index < len(self.albums_selected):
+ self._get_selected_album_songs()
+ else:
+ self.items_selected_callback(self.items_selected)
diff --git a/gnomemusic/views/baseview.py b/gnomemusic/views/baseview.py
new file mode 100644
index 0000000..8aeeab0
--- /dev/null
+++ b/gnomemusic/views/baseview.py
@@ -0,0 +1,302 @@
+# Copyright (c) 2016 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+from gettext import gettext as _, ngettext
+from gi.repository import Gd, Gdk, GdkPixbuf, GObject, Gtk
+
+from gnomemusic import log
+from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
+from gnomemusic.grilo import grilo
+import gnomemusic.widgets as Widgets
+import gnomemusic.utils as utils
+
+
+class BaseView(Gtk.Stack):
+ nowPlayingIconName = 'media-playback-start-symbolic'
+ errorIconName = 'dialog-error-symbolic'
+
+ selection_mode = GObject.Property(type=bool, default=False)
+
+ def __repr__(self):
+ return '<BaseView>'
+
+ @log
+ def __init__(self, name, title, window, view_type, use_sidebar=False, sidebar=None):
+ Gtk.Stack.__init__(self,
+ transition_type=Gtk.StackTransitionType.CROSSFADE)
+ self._grid = Gtk.Grid(orientation=Gtk.Orientation.HORIZONTAL)
+ self._offset = 0
+ self._adjustmentValueId = 0
+ self._adjustmentChangedId = 0
+ self._scrollbarVisibleId = 0
+ self.old_vsbl_range = None
+ 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_INT,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_INT
+ )
+
+ self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+
+ # Setup the main view
+ self._setup_view(view_type)
+
+ if use_sidebar:
+ self.stack = Gtk.Stack(
+ transition_type=Gtk.StackTransitionType.SLIDE_RIGHT,
+ )
+ dummy = Gtk.Frame(visible=False)
+ self.stack.add_named(dummy, 'dummy')
+ if sidebar:
+ self.stack.add_named(sidebar, 'sidebar')
+ else:
+ self.stack.add_named(self._box, 'sidebar')
+ self.stack.set_visible_child_name('dummy')
+ self._grid.add(self.stack)
+ if not use_sidebar or sidebar:
+ self._grid.add(self._box)
+
+ self.star_handler = Widgets.StarHandler(self, 9)
+ self._cursor = None
+ self.window = window
+ self.header_bar = window.toolbar
+ self.selection_toolbar = window.selection_toolbar
+ self.header_bar._select_button.connect(
+ 'toggled', self._on_header_bar_toggled)
+ self.header_bar._cancel_button.connect(
+ 'clicked', self._on_cancel_button_clicked)
+
+ self.name = name
+ self.title = title
+ self.add(self._grid)
+
+ self.show_all()
+ self.view.hide()
+ self._items = []
+
+ scale = self.get_scale_factor()
+ self.cache = AlbumArtCache(scale)
+ self._loading_icon_surface = DefaultIcon(scale).get(
+ DefaultIcon.Type.loading,
+ ArtSize.medium)
+
+
+ self._init = False
+ grilo.connect('ready', self._on_grilo_ready)
+ self.selection_socket = None
+ self.header_bar.connect('selection-mode-changed',
+ self._on_selection_mode_changed)
+
+ self._discovering_urls = {}
+ grilo.connect('changes-pending', self._on_changes_pending)
+
+ @log
+ def _on_changes_pending(self, data=None):
+ pass
+
+ @log
+ def _setup_view(self, view_type):
+ self.view = Gd.MainView(shadow_type=Gtk.ShadowType.NONE)
+ self.view.set_view_type(view_type)
+
+ self.view.click_handler = self.view.connect('item-activated', self._on_item_activated)
+ self.view.connect('selection-mode-request', self._on_selection_mode_request)
+
+ self.view.bind_property('selection-mode', self, 'selection_mode',
+ GObject.BindingFlags.BIDIRECTIONAL)
+
+ self.view.connect('view-selection-changed', self._on_view_selection_changed)
+
+ self._box.pack_start(self.view, True, True, 0)
+
+ @log
+ def _on_header_bar_toggled(self, button):
+ self.selection_mode = button.get_active()
+
+ if self.selection_mode:
+ self.header_bar.set_selection_mode(True)
+ self.player.actionbar.set_visible(False)
+ self.selection_toolbar.actionbar.set_visible(True)
+ self.selection_toolbar._add_to_playlist_button.set_sensitive(False)
+ self.selection_toolbar._remove_from_playlist_button.set_sensitive(False)
+ else:
+ self.header_bar.set_selection_mode(False)
+ self.player.actionbar.set_visible(self.player.currentTrack is not None)
+ self.selection_toolbar.actionbar.set_visible(False)
+ self.unselect_all()
+
+ @log
+ def _on_cancel_button_clicked(self, button):
+ self.view.set_selection_mode(False)
+ self.header_bar.set_selection_mode(False)
+
+ @log
+ def _on_grilo_ready(self, data=None):
+ if (self.header_bar.get_stack().get_visible_child() == self and not self._init):
+ self._populate()
+ self.header_bar.get_stack().connect('notify::visible-child',
+ self._on_headerbar_visible)
+
+ @log
+ def _on_headerbar_visible(self, widget, param):
+ if self == widget.get_visible_child() and not self._init:
+ self._populate()
+
+ @log
+ def _on_view_selection_changed(self, widget):
+ if not self.selection_mode:
+ return
+
+ items = self.view.get_selection()
+ self.update_header_from_selection(len(items))
+
+ @log
+ def update_header_from_selection(self, n_items):
+ self.selection_toolbar._add_to_playlist_button.\
+ set_sensitive(n_items > 0)
+ self.selection_toolbar._remove_from_playlist_button.\
+ set_sensitive(n_items > 0)
+ if n_items > 0:
+ self.header_bar._selection_menu_label.set_text(
+ ngettext("Selected %d item", "Selected %d items", n_items) % n_items)
+ else:
+ self.header_bar._selection_menu_label.set_text(_("Click on items to select them"))
+
+ @log
+ def _populate(self, data=None):
+ self._init = True
+ self.populate()
+
+ @log
+ def _on_selection_mode_changed(self, widget, data=None):
+ pass
+
+ @log
+ def populate(self):
+ print('populate')
+
+ @log
+ def _add_item(self, source, param, item, remaining=0, data=None):
+ self.window.notification.set_timeout(0)
+ if not item:
+ if remaining == 0:
+ self.view.set_model(self.model)
+ self.window.notification.dismiss()
+ self.view.show()
+ return
+
+ self._offset += 1
+ artist = utils.get_artist_name(item)
+ title = utils.get_media_title(item)
+
+ _iter = self.model.append(None)
+
+ loading_icon = Gdk.pixbuf_get_from_surface(
+ self._loadin_icon_surface, 0, 0,
+ self._loading_icon_surface.get_width(),
+ self._loading_icon_surface.get_height())
+
+ self.model[_iter][0, 1, 2, 3, 4, 5, 7, 9] = [
+ str(item.get_id()),
+ '',
+ title,
+ artist,
+ loading_icon,
+ item,
+ 0,
+ False
+ ]
+ self.cache.lookup(item, self._iconWidth, self._iconHeight,
+ self._on_lookup_ready, _iter)
+
+ @log
+ def _on_lookup_ready(self, surface, _iter):
+ if surface:
+ pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0,
+ surface.get_width(),
+ surface.get_height())
+
+ self.model[_iter][4] = pixbuf
+
+ @log
+ def _add_list_renderers(self):
+ pass
+
+ @log
+ def _on_item_activated(self, widget, id, path):
+ pass
+
+ @log
+ def _on_selection_mode_request(self, *args):
+ self.header_bar._select_button.clicked()
+
+ @log
+ def get_selected_tracks(self, callback):
+ callback([])
+
+ def _on_list_widget_star_render(self, col, cell, model, _iter, data):
+ pass
+
+ @log
+ def _set_selection(self, value, parent=None):
+ count = 0
+ _iter = self.model.iter_children(parent)
+ while _iter is not None:
+ if self.model.iter_has_child(_iter):
+ count += self._set_selection(value, _iter)
+ if self.model[_iter][5]:
+ self.model[_iter][6] = value
+ count += 1
+ _iter = self.model.iter_next(_iter)
+ return count
+
+ @log
+ def select_all(self):
+ """Select all the available tracks."""
+ count = self._set_selection(True)
+
+ if count > 0:
+ self.selection_toolbar._add_to_playlist_button.set_sensitive(True)
+ self.selection_toolbar._remove_from_playlist_button.set_sensitive(True)
+
+ self.update_header_from_selection(count)
+ self.view.queue_draw()
+
+ @log
+ def unselect_all(self):
+ """Unselects all the selected tracks."""
+ self._set_selection(False)
+ self.selection_toolbar._add_to_playlist_button.set_sensitive(False)
+ self.selection_toolbar._remove_from_playlist_button.set_sensitive(False)
+ self.header_bar._selection_menu_label.set_text(_("Click on items to select them"))
+ self.queue_draw()
diff --git a/gnomemusic/views/emptysearchview.py b/gnomemusic/views/emptysearchview.py
new file mode 100644
index 0000000..1d29e8e
--- /dev/null
+++ b/gnomemusic/views/emptysearchview.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2016 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+from gettext import gettext as _
+from gi.repository import Gd, Gtk
+
+from gnomemusic import log
+from gnomemusic.views.baseview import BaseView
+
+
+class EmptySearchView(BaseView):
+
+ def __repr__(self):
+ return '<EmptySearchView>'
+
+ @log
+ def __init__(self, window, player):
+ BaseView.__init__(self, 'emptysearch', None, window, Gd.MainViewType.LIST)
+ self._artistAlbumsWidget = None
+ self._albumWidget = None
+ self.player = player
+
+ builder = Gtk.Builder()
+ builder.add_from_resource('/org/gnome/Music/NoMusic.ui')
+ widget = builder.get_object('container')
+ widget.set_vexpand(True)
+ widget.set_hexpand(True)
+ widget.get_children()[1].get_children()[1].set_text(_("Try a different search"))
+ widget.show_all()
+ self._box.add(widget)
+
+ @log
+ def _back_button_clicked(self, widget, data=None):
+ self.header_bar.searchbar.show_bar(True, False)
+ if self.get_visible_child() == self._artistAlbumsWidget:
+ self._artistAlbumsWidget.destroy()
+ self._artistAlbumsWidget = None
+ elif self.get_visible_child() == self._grid:
+ self.window.views[0].set_visible_child(self.window.views[0]._grid)
+ self.window.toolbar.set_state(ToolbarState.CHILD_VIEW)
+ self.set_visible_child(self._grid)
diff --git a/gnomemusic/views/initialstateview.py b/gnomemusic/views/initialstateview.py
new file mode 100644
index 0000000..8f2efc6
--- /dev/null
+++ b/gnomemusic/views/initialstateview.py
@@ -0,0 +1,74 @@
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+from gettext import gettext as _
+from gi.repository import Gtk
+
+from gnomemusic import log
+from gnomemusic.albumartcache import ArtSize
+from gnomemusic.query import Query
+
+
+class Empty(Gtk.Stack):
+
+ def __repr__(self):
+ return '<Empty>'
+
+ @log
+ def __init__(self, window, player):
+ Gtk.Stack.__init__(self,
+ transition_type=Gtk.StackTransitionType.CROSSFADE)
+ self.builder = Gtk.Builder()
+ self.builder.add_from_resource('/org/gnome/Music/NoMusic.ui')
+ widget = self.builder.get_object('container')
+ self.update_empty_state_link()
+ self.add(widget)
+ self.show_all()
+
+ def update_empty_state_link(self):
+ label = self.builder.get_object('empty-state-label')
+ href_text = '<a href="%s">%s</a>' % (Query.MUSIC_URI,
+ _("Music folder"))
+ label.set_label(label.get_label() % href_text)
+
+
+class InitialStateView(Empty):
+
+ def __repr__(self):
+ return '<InitialStateView>'
+
+ @log
+ def __init__(self, window, player):
+ Empty.__init__(self, window, player)
+
+ # Update image
+ icon = self.builder.get_object('icon')
+ icon.set_margin_bottom(32)
+ icon.set_opacity(1)
+ icon.set_from_resource('/org/gnome/Music/initial-state.png')
+ icon.set_size_request(ArtSize.large.width, ArtSize.large.height)
+
+ # Update label
+ label = self.builder.get_object('label')
+ label.set_label(_("Hey DJ"))
+ label.set_opacity(1)
+ label.set_margin_bottom(18)
diff --git a/gnomemusic/views/playlistview.py b/gnomemusic/views/playlistview.py
new file mode 100644
index 0000000..fd40bcc
--- /dev/null
+++ b/gnomemusic/views/playlistview.py
@@ -0,0 +1,536 @@
+# Copyright (c) 2016 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+from gettext import gettext as _, ngettext
+from gi.repository import Gd, GdkPixbuf, Gio, GLib, GObject, Gtk, Pango
+
+from gnomemusic import log
+from gnomemusic.grilo import grilo
+from gnomemusic.player import DiscoveryStatus
+from gnomemusic.playlists import Playlists, StaticPlaylists
+from gnomemusic.views.baseview import BaseView
+import gnomemusic.utils as utils
+
+playlists = Playlists.get_default()
+
+
+class PlaylistView(BaseView):
+ __gsignals__ = {
+ 'playlists-loaded': (GObject.SignalFlags.RUN_FIRST, None, ()),
+ 'playlist-songs-loaded': (GObject.SignalFlags.RUN_FIRST, None, ()),
+ }
+
+ def __repr__(self):
+ return '<PlaylistView>'
+
+ @log
+ def __init__(self, window, player):
+ self.playlists_sidebar = Gd.MainView()
+
+ BaseView.__init__(self, 'playlists', _("Playlists"), window,
+ Gd.MainViewType.LIST, True, self.playlists_sidebar)
+
+ self.view.get_generic_view().get_style_context()\
+ .add_class('songs-list')
+ self._add_list_renderers()
+ self.view.get_generic_view().get_style_context().remove_class('content-view')
+
+ 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')
+ playlistPlayAction = Gio.SimpleAction.new('playlist_play', None)
+ playlistPlayAction.connect('activate', self._on_play_activate)
+ window.add_action(playlistPlayAction)
+ self.playlistDeleteAction = Gio.SimpleAction.new('playlist_delete', None)
+ self.playlistDeleteAction.connect('activate', self._on_delete_activate)
+ window.add_action(self.playlistDeleteAction)
+ self._grid.insert_row(0)
+ self._grid.attach(self.headerbar, 1, 0, 1, 1)
+
+ self.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_INT,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_INT
+ )
+
+ self.playlists_sidebar.set_view_type(Gd.MainViewType.LIST)
+ self.playlists_sidebar.set_model(self.playlists_model)
+ self.playlists_sidebar.set_hexpand(False)
+ self.playlists_sidebar.get_style_context().add_class('side-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()
+ self.playlists_sidebar.get_generic_view().get_style_context().remove_class('content-view')
+
+ self.iter_to_clean = None
+ self.iter_to_clean_model = None
+ self.current_playlist = None
+ self.current_playlist_index = None
+ self.pl_todelete = None
+ self.pl_todelete_index = None
+ self.really_delete = True
+ self.songs_count = 0
+ self.window = window
+ 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('playlist-updated', self.on_playlist_update)
+ 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()
+
+ @log
+ def _on_changes_pending(self, data=None):
+ #playlists.update_all_static_playlists()
+ pass
+
+ @log
+ 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(xpad=0,
+ xalign=0.5,
+ yalign=0.5)
+
+ column_now_playing = Gtk.TreeViewColumn()
+ column_now_playing.set_fixed_width(48)
+ column_now_playing.pack_start(now_playing_symbol_renderer, False)
+ column_now_playing.set_cell_data_func(now_playing_symbol_renderer,
+ self._on_list_widget_icon_render, None)
+ 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)
+
+ self.star_handler.add_star_renderers(list_widget, cols)
+
+ 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)
+
+ @log
+ 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):
+ if not model.iter_is_valid(_iter):
+ return
+
+ 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):
+ if not model.iter_is_valid(_iter):
+ return
+
+ item = model.get_value(_iter, 5)
+ if item:
+ cell.set_property('text', item.get_album() or _("Unknown Album"))
+
+ def _on_list_widget_icon_render(self, col, cell, model, _iter, data):
+ if not self.player.currentTrackUri:
+ cell.set_visible(False)
+ return
+
+ if not model.iter_is_valid(_iter):
+ return
+
+ if model.get_value(_iter, 11) == DiscoveryStatus.FAILED:
+ cell.set_property('icon-name', self.errorIconName)
+ cell.set_visible(True)
+ elif model.get_value(_iter, 5).get_url() == self.player.currentTrackUri:
+ cell.set_property('icon-name', self.nowPlayingIconName)
+ cell.set_visible(True)
+ else:
+ cell.set_visible(False)
+
+ @log
+ def _populate(self):
+ self._init = True
+ self.window._init_loading_notification()
+ self.populate()
+
+ @log
+ 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.model:
+ return False
+
+ self.model.set_value(currentIter, 10, True)
+ if self.model.get_value(currentIter, 8) != self.errorIconName:
+ self.iter_to_clean = currentIter.copy()
+ self.iter_to_clean_model = self.model
+
+ return False
+
+ @log
+ def _add_playlist_item(self, source, param, item, remaining=0, data=None):
+ self._add_playlist_item_to_model(item)
+
+ @log
+ def _add_playlist_item_to_model(self, item, index=None):
+ self.window.notification.set_timeout(0)
+ if index is None:
+ index = -1
+ if not item:
+ self.window.notification.dismiss()
+ self.emit('playlists-loaded')
+ return
+ _iter = self.playlists_model.insert_with_valuesv(
+ index,
+ [2, 5],
+ [utils.get_media_title(item), item])
+ if self.playlists_model.iter_n_children(None) == 1:
+ _iter = self.playlists_model.get_iter_first()
+ selection = self.playlists_sidebar.get_generic_view().get_selection()
+ selection.select_iter(_iter)
+ self.playlists_sidebar.emit('item-activated', '0',
+ self.playlists_model.get_path(_iter))
+
+ @log
+ def _on_item_activated(self, widget, id, path):
+ if self.star_handler.star_renderer_click:
+ self.star_handler.star_renderer_click = False
+ return
+
+ try:
+ _iter = self.model.get_iter(path)
+ except TypeError:
+ return
+ if self.model.get_value(_iter, 8) != self.errorIconName:
+ self.player.set_playlist(
+ 'Playlist', self.current_playlist.get_id(),
+ self.model, _iter, 5, 11
+ )
+ self.player.set_playing(True)
+
+ @log
+ def on_playlist_update(self, widget, playlist_id):
+ _iter = self.playlists_model.get_iter_first()
+ while _iter:
+ playlist = self.playlists_model.get_value(_iter, 5)
+ if str(playlist_id) == playlist.get_id() and self.current_playlist == playlist:
+ path = self.playlists_model.get_path(_iter)
+ GLib.idle_add(self._on_playlist_activated, None, None, path)
+ break
+ _iter = self.playlists_model.iter_next(_iter)
+
+ @log
+ def activate_playlist(self, playlist_id):
+
+ def find_and_activate_playlist():
+ for playlist in self.playlists_model:
+ if playlist[5].get_id() == playlist_id:
+ selection = self.playlists_sidebar.get_generic_view().get_selection()
+ if selection.iter_is_selected(playlist.iter):
+ self._on_play_activate(None)
+ else:
+ selection.select_iter(playlist.iter)
+ handler = 0
+
+ def songs_loaded_callback(view):
+ self.disconnect(handler)
+ self._on_play_activate(None)
+
+ handler = self.connect('playlist-songs-loaded', songs_loaded_callback)
+ self.playlists_sidebar.emit('item-activated', '0', playlist.path)
+
+ return
+
+ if self._init:
+ find_and_activate_playlist()
+ else:
+ handler = 0
+
+ def playlists_loaded_callback(view):
+ self.disconnect(handler)
+ def_handler = 0
+
+ def songs_loaded_callback(view):
+ self.disconnect(def_handler)
+ find_and_activate_playlist()
+
+ # Skip load of default playlist
+ def_handler = self.connect('playlist-songs-loaded', songs_loaded_callback)
+
+ handler = self.connect('playlists-loaded', playlists_loaded_callback)
+
+ self._populate()
+
+ @log
+ def remove_playlist(self):
+ if not self.current_playlist_is_protected():
+ self._on_delete_activate(None)
+
+ @log
+ def _on_playlist_activated(self, widget, item_id, path):
+ _iter = self.playlists_model.get_iter(path)
+ playlist_name = self.playlists_model.get_value(_iter, 2)
+ playlist = self.playlists_model.get_value(_iter, 5)
+
+ self.current_playlist = playlist
+ self.name_label.set_text(playlist_name)
+ self.current_playlist_index = int(path.to_string())
+
+ # if the active queue has been set by this playlist,
+ # use it as model, otherwise build the liststore
+ self.view.set_model(None)
+ self.model.clear()
+ self.songs_count = 0
+ GLib.idle_add(grilo.populate_playlist_songs, playlist, self._add_item)
+
+ # disable delete button if current playlist is a smart playlist
+ if self.current_playlist_is_protected():
+ self.playlistDeleteAction.set_enabled(False)
+ else:
+ self.playlistDeleteAction.set_enabled(True)
+
+ @log
+ def _add_item(self, source, param, item, remaining=0, data=None):
+ self._add_item_to_model(item, self.model)
+ if remaining == 0:
+ self.view.set_model(self.model)
+
+ @log
+ def _add_item_to_model(self, item, model):
+ if not item:
+ self._update_songs_count()
+ if self.player.playlist:
+ self.player._validate_next_track()
+ self.emit('playlist-songs-loaded')
+ return
+ self._offset += 1
+ title = utils.get_media_title(item)
+ item.set_title(title)
+ artist = item.get_artist() or _("Unknown Artist")
+ model.insert_with_valuesv(
+ -1,
+ [2, 3, 5, 9],
+ [title, artist, item, bool(item.get_lyrics())])
+ self.songs_count += 1
+
+ @log
+ def _update_songs_count(self):
+ self.songs_count_label.set_text(
+ ngettext("%d Song", "%d Songs", self.songs_count)
+ % self.songs_count)
+
+ @log
+ 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)
+
+ @log
+ 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))
+
+ @log
+ def current_playlist_is_protected(self):
+ current_playlist_id = self.current_playlist.get_id()
+ if current_playlist_id in StaticPlaylists.get_protected_ids():
+ return True
+ else:
+ return False
+
+ @log
+ def stage_playlist_for_deletion(self):
+ self.model.clear()
+ self.pl_todelete_index = self.current_playlist_index
+ _iter = self.playlists_sidebar.get_generic_view().get_selection().get_selected()[1]
+ self.pl_todelete = self.playlists_model.get_value(_iter, 5)
+
+ if not _iter:
+ return
+
+ iter_next = self.playlists_model.iter_next(_iter)\
+ or self.playlists_model.iter_previous(_iter)
+ self.playlists_model.remove(_iter)
+
+ if iter_next:
+ selection = self.playlists_sidebar.get_generic_view().get_selection()
+ selection.select_iter(iter_next)
+ self.playlists_sidebar.emit('item-activated', '0',
+ self.playlists_model.get_path(iter_next))
+
+ @log
+ def undo_playlist_deletion(self):
+ self._add_playlist_item_to_model(self.pl_todelete, self.pl_todelete_index)
+
+ @log
+ def _on_delete_activate(self, menuitem, data=None):
+ self.window._init_playlist_removal_notification()
+ self.stage_playlist_for_deletion()
+
+ @log
+ def _on_playlist_created(self, playlists, item):
+ self._add_playlist_item_to_model(item)
+ if self.playlists_model.iter_n_children(None) == 1:
+ _iter = self.playlists_model.get_iter_first()
+ selection = self.playlists_sidebar.get_generic_view().get_selection()
+ selection.select_iter(_iter)
+ self.playlists_sidebar.emit('item-activated', '0',
+ self.playlists_model.get_path(_iter))
+
+ @log
+ def _on_song_added_to_playlist(self, playlists, playlist, item):
+ if self.current_playlist and \
+ playlist.get_id() == self.current_playlist.get_id():
+ self._add_item_to_model(item, self.model)
+
+ @log
+ def _on_song_removed_from_playlist(self, playlists, playlist, item):
+ if self.current_playlist and \
+ playlist.get_id() == self.current_playlist.get_id():
+ model = self.model
+ else:
+ return
+
+ update_playing_track = False
+ for row in model:
+ if row[5].get_id() == item.get_id():
+ # Is the removed track now being played?
+ if self.current_playlist and \
+ playlist.get_id() == self.current_playlist.get_id():
+ if self.player.currentTrack is not None and self.player.currentTrack.valid():
+ currentTrackpath = self.player.currentTrack.get_path().to_string()
+ if row.path is not None and row.path.to_string() == currentTrackpath:
+ update_playing_track = True
+
+ nextIter = model.iter_next(row.iter)
+ model.remove(row.iter)
+
+ # Reload the model and switch to next song
+ if update_playing_track:
+ if nextIter is None:
+ # Get first track if next track is not valid
+ nextIter = model.get_iter_first()
+ if nextIter is None:
+ # Last track was removed
+ return
+
+ self.iter_to_clean = None
+ self.update_model(self.player, model, nextIter)
+ self.player.set_playlist('Playlist', playlist.get_id(), model, nextIter, 5, 11)
+ self.player.set_playing(True)
+
+ # Update songs count
+ self.songs_count -= 1
+ self._update_songs_count()
+ return
+
+ @log
+ def populate(self):
+ if grilo.tracker:
+ self.playlists_model.clear()
+ GLib.idle_add(grilo.populate_playlists, self._offset,
+ self._add_playlist_item)
+
+ @log
+ def get_selected_tracks(self, callback):
+ callback([self.model.get_value(self.model.get_iter(path), 5)
+ for path in self.view.get_selection()])
diff --git a/gnomemusic/views/searchview.py b/gnomemusic/views/searchview.py
new file mode 100644
index 0000000..8805990
--- /dev/null
+++ b/gnomemusic/views/searchview.py
@@ -0,0 +1,475 @@
+# Copyright (c) 2016 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+from gettext import gettext as _
+from gi.repository import Gd, Gdk, GdkPixbuf, GObject, Grl, Gtk, Pango
+
+from gnomemusic.albumartcache import DefaultIcon, ArtSize
+from gnomemusic.grilo import grilo
+from gnomemusic import log
+from gnomemusic.player import DiscoveryStatus
+from gnomemusic.playlists import Playlists
+from gnomemusic.query import Query
+from gnomemusic.toolbar import ToolbarState
+from gnomemusic.views.baseview import BaseView
+import gnomemusic.utils as utils
+import gnomemusic.widgets as Widgets
+
+playlists = Playlists.get_default()
+
+
+class SearchView(BaseView):
+ __gsignals__ = {
+ 'no-music-found': (GObject.SignalFlags.RUN_FIRST, None, ())
+ }
+
+ def __repr__(self):
+ return '<SearchView>'
+
+ @log
+ def __init__(self, window, player):
+ BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST)
+ self._items = {}
+ self.isStarred = None
+ self.iter_to_clean = None
+
+ scale = self.get_scale_factor()
+ loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading,
+ ArtSize.small)
+ no_albumart_surface = DefaultIcon(scale).get(DefaultIcon.Type.music,
+ ArtSize.small)
+ self._loading_icon = Gdk.pixbuf_get_from_surface(
+ loading_icon_surface,
+ 0,
+ 0,
+ loading_icon_surface.get_width(),
+ loading_icon_surface.get_height())
+ self._no_albumart_icon = Gdk.pixbuf_get_from_surface(
+ no_albumart_surface,
+ 0,
+ 0,
+ no_albumart_surface.get_width(),
+ no_albumart_surface.get_height())
+
+ self._add_list_renderers()
+ self.player = player
+ self.head_iters = [None, None, None, None]
+ self.songs_model = self.model
+ self.previous_view = None
+ self.connect('no-music-found', self._no_music_found_callback)
+
+ self.albums_selected = []
+ self._albums = {}
+ self._albumWidget = Widgets.AlbumWidget(player, self)
+ self.add(self._albumWidget)
+
+ self.artists_albums_selected = []
+ self._artists = {}
+ self._artistAlbumsWidget = None
+
+ self.view.get_generic_view().set_show_expanders(False)
+ self.items_selected = []
+ self.items_selected_callback = None
+
+ self.found_items_number = None
+
+ @log
+ def _no_music_found_callback(self, view):
+ self.window._stack.set_visible_child_name('emptysearch')
+ emptysearch = self.window._stack.get_child_by_name('emptysearch')
+ emptysearch._artistAlbumsWidget = self._artistAlbumsWidget
+
+ @log
+ def _back_button_clicked(self, widget, data=None):
+ self.header_bar.searchbar.show_bar(True, False)
+ if self.get_visible_child() == self._artistAlbumsWidget:
+ self._artistAlbumsWidget.destroy()
+ self._artistAlbumsWidget = None
+ elif self.get_visible_child() == self._grid:
+ self.window.views[0].set_visible_child(self.window.views[0]._grid)
+ self.set_visible_child(self._grid)
+ self.window.toolbar.set_state(ToolbarState.MAIN)
+
+ @log
+ def _on_item_activated(self, widget, id, path):
+ if self.star_handler.star_renderer_click:
+ self.star_handler.star_renderer_click = False
+ return
+
+ try:
+ child_path = self.filter_model.convert_path_to_child_path(path)
+ except TypeError:
+ return
+ _iter = self.model.get_iter(child_path)
+ if self.model[_iter][11] == 'album':
+ title = self.model.get_value(_iter, 2)
+ artist = self.model.get_value(_iter, 3)
+ item = self.model.get_value(_iter, 5)
+ self._albumWidget.update(artist, title, item,
+ self.header_bar, self.selection_toolbar)
+ self.header_bar.set_state(ToolbarState.SEARCH_VIEW)
+ title = utils.get_media_title(item)
+ self.header_bar.header_bar.set_title(title)
+ self.header_bar.header_bar.sub_title = artist
+ self.set_visible_child(self._albumWidget)
+ self.header_bar.searchbar.show_bar(False)
+ elif self.model[_iter][11] == 'artist':
+ artist = self.model.get_value(_iter, 2)
+ albums = self._artists[artist.casefold()]['albums']
+
+ self._artistAlbumsWidget = Widgets.ArtistAlbums(
+ artist, albums, self.player,
+ self.header_bar, self.selection_toolbar, self.window, True
+ )
+ self.add(self._artistAlbumsWidget)
+ self._artistAlbumsWidget.show()
+
+ self.header_bar.set_state(ToolbarState.SEARCH_VIEW)
+ self.header_bar.header_bar.set_title(artist)
+ self.set_visible_child(self._artistAlbumsWidget)
+ self.header_bar.searchbar.show_bar(False)
+ elif self.model[_iter][11] == 'song':
+ if self.model.get_value(_iter, 12) != DiscoveryStatus.FAILED:
+ child_iter = self.songs_model.convert_child_iter_to_iter(_iter)[1]
+ self.player.set_playlist('Search Results', None, self.songs_model, child_iter, 5, 12)
+ self.player.set_playing(True)
+ else: # Headers
+ if self.view.get_generic_view().row_expanded(path):
+ self.view.get_generic_view().collapse_row(path)
+ else:
+ self.view.get_generic_view().expand_row(path, False)
+
+ @log
+ def _on_selection_mode_changed(self, widget, data=None):
+ if self._artistAlbumsWidget is not None and self.get_visible_child() == self._artistAlbumsWidget:
+ self._artistAlbumsWidget.set_selection_mode(self.header_bar._selectionMode)
+
+ @log
+ def _add_search_item(self, source, param, item, remaining=0, data=None):
+ if not item:
+ if grilo._search_callback_counter == 0 and grilo.search_source:
+ self.emit('no-music-found')
+ return
+
+ if data != self.model:
+ return
+
+ artist = utils.get_artist_name(item)
+ album = item.get_album() or _("Unknown Album")
+
+ key = '%s-%s' % (artist, album)
+ if key not in self._albums:
+ self._albums[key] = Grl.Media()
+ self._albums[key].set_title(album)
+ self._albums[key].add_artist(artist)
+ self._albums[key].set_source(source.get_id())
+ self._albums[key].tracks = []
+ self._add_item(source, None, self._albums[key], 0, [self.model, 'album'])
+ self._add_item(source, None, self._albums[key], 0, [self.model, 'artist'])
+
+ self._albums[key].tracks.append(item)
+ self._add_item(source, None, item, 0, [self.model, 'song'])
+
+ @log
+ def _add_item(self, source, param, item, remaining=0, data=None):
+ self.window.notification.set_timeout(0)
+ if data is None:
+ return
+
+ model, category = data
+
+ self.found_items_number = (
+ self.model.iter_n_children(self.head_iters[0]) +
+ self.model.iter_n_children(self.head_iters[1]) +
+ self.model.iter_n_children(self.head_iters[2]) +
+ self.model.iter_n_children(self.head_iters[3]))
+
+ if category == 'song' and self.found_items_number == 0 and remaining == 0:
+ if grilo.search_source:
+ self.emit('no-music-found')
+
+ # We need to remember the view before the search view
+ if self.window.curr_view != self.window.views[5] and \
+ self.window.prev_view != self.window.views[5]:
+ self.previous_view = self.window.prev_view
+
+ if remaining == 0:
+ self.window.notification.dismiss()
+ self.view.show()
+
+ if not item or model != self.model:
+ return
+
+ self._offset += 1
+ title = utils.get_media_title(item)
+ item.set_title(title)
+ artist = utils.get_artist_name(item)
+
+ group = 3
+ try:
+ group = {'album': 0, 'artist': 1, 'song': 2}[category]
+ except:
+ pass
+
+ # FIXME: HiDPI icon lookups return a surface that can't be
+ # scaled by GdkPixbuf, so it results in a * scale factor sized
+ # icon for the search view.
+ _iter = None
+ if category == 'album':
+ _iter = self.model.insert_with_values(
+ self.head_iters[group], -1,
+ [0, 2, 3, 4, 5, 9, 11],
+ [str(item.get_id()), title, artist,
+ self._loading_icon, item, 2, category])
+ self.cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter)
+ elif category == 'song':
+ _iter = self.model.insert_with_values(
+ self.head_iters[group], -1,
+ [0, 2, 3, 4, 5, 9, 11],
+ [str(item.get_id()), title, artist,
+ self._no_albumart_icon, item,
+ 2 if source.get_id() != 'grl-tracker-source' \
+ else bool(item.get_lyrics()), category])
+ else:
+ if not artist.casefold() in self._artists:
+ _iter = self.model.insert_with_values(
+ self.head_iters[group], -1,
+ [0, 2, 4, 5, 9, 11],
+ [str(item.get_id()), artist,
+ self._loading_icon, item, 2, category])
+ self.cache.lookup(item, ArtSize.small, self._on_lookup_ready,
+ _iter)
+ self._artists[artist.casefold()] = {'iter': _iter, 'albums': []}
+
+ self._artists[artist.casefold()]['albums'].append(item)
+
+ if self.model.iter_n_children(self.head_iters[group]) == 1:
+ path = self.model.get_path(self.head_iters[group])
+ path = self.filter_model.convert_child_path_to_path(path)
+ self.view.get_generic_view().expand_row(path, False)
+
+ @log
+ def _add_list_renderers(self):
+ list_widget = self.view.get_generic_view()
+ list_widget.set_halign(Gtk.Align.CENTER)
+ list_widget.set_size_request(530, -1)
+ cols = list_widget.get_columns()
+
+ title_renderer = Gtk.CellRendererText(
+ xpad=12,
+ xalign=0.0,
+ yalign=0.5,
+ height=32,
+ ellipsize=Pango.EllipsizeMode.END,
+ weight=Pango.Weight.BOLD
+ )
+ list_widget.add_renderer(title_renderer,
+ self._on_list_widget_title_render, None)
+ cols[0].add_attribute(title_renderer, 'text', 2)
+
+ self.star_handler.add_star_renderers(list_widget, cols, hidden=False)
+
+ cells = cols[0].get_cells()
+ cols[0].reorder(cells[0], -1)
+ cols[0].set_cell_data_func(cells[0], self._on_list_widget_selection_render, None)
+
+ def _on_list_widget_selection_render(self, col, cell, model, _iter, data):
+ cell.set_visible(self.view.get_selection_mode() and model.iter_parent(_iter) is not None)
+
+ def _on_list_widget_title_render(self, col, cell, model, _iter, data):
+ cells = col.get_cells()
+ cells[0].set_visible(model.iter_parent(_iter) is not None)
+ cells[1].set_visible(model.iter_parent(_iter) is not None)
+ cells[2].set_visible(model.iter_parent(_iter) is None)
+
+ @log
+ def populate(self):
+ self._init = True
+ self.window._init_loading_notification()
+ self.header_bar.set_state(ToolbarState.MAIN)
+
+ @log
+ def get_selected_tracks(self, callback):
+ if self.get_visible_child() == self._albumWidget:
+ items = []
+ for path in self._albumWidget.view.get_selection():
+ _iter = self._albumWidget.model.get_iter(path)
+ items.append(self._albumWidget.model.get_value(_iter, 5))
+ callback(items)
+ elif self.get_visible_child() == self._artistAlbumsWidget:
+ items = []
+ for row in self._artistAlbumsWidget.model:
+ if row[6]:
+ items.append(row[5])
+ callback(items)
+ else:
+ self.items_selected = []
+ self.items_selected_callback = callback
+ self._get_selected_albums()
+
+ @log
+ def _get_selected_albums(self):
+ self.albums_index = 0
+ self.albums_selected = [self.model[child_path][5]
+ for child_path in [self.filter_model.convert_path_to_child_path(path)
+ for path in self.view.get_selection()]
+ if self.model[child_path][11] == 'album']
+ if len(self.albums_selected):
+ self._get_selected_albums_songs()
+ else:
+ self._get_selected_artists()
+
+ @log
+ def _get_selected_albums_songs(self):
+ grilo.populate_album_songs(
+ self.albums_selected[self.albums_index],
+ self._add_selected_albums_songs)
+ self.albums_index += 1
+
+ @log
+ def _add_selected_albums_songs(self, source, param, item, remaining=0, data=None):
+ if item:
+ self.items_selected.append(item)
+ if remaining == 0:
+ if self.albums_index < len(self.albums_selected):
+ self._get_selected_albums_songs()
+ else:
+ self._get_selected_artists()
+
+ @log
+ def _get_selected_artists(self):
+ self.artists_albums_index = 0
+ self.artists_selected = [self._artists[self.model[child_path][2].casefold()]
+ for child_path in [self.filter_model.convert_path_to_child_path(path)
+ for path in self.view.get_selection()]
+ if self.model[child_path][11] == 'artist']
+
+ self.artists_albums_selected = []
+ for artist in self.artists_selected:
+ self.artists_albums_selected.extend(artist['albums'])
+
+ if len(self.artists_albums_selected):
+ self._get_selected_artists_albums_songs()
+ else:
+ self._get_selected_songs()
+
+ @log
+ def _get_selected_artists_albums_songs(self):
+ grilo.populate_album_songs(
+ self.artists_albums_selected[self.artists_albums_index],
+ self._add_selected_artists_albums_songs)
+ self.artists_albums_index += 1
+
+ @log
+ def _add_selected_artists_albums_songs(self, source, param, item, remaining=0, data=None):
+ if item:
+ self.items_selected.append(item)
+ if remaining == 0:
+ if self.artists_albums_index < len(self.artists_albums_selected):
+ self._get_selected_artists_albums_songs()
+ else:
+ self._get_selected_songs()
+
+ @log
+ def _get_selected_songs(self):
+ self.items_selected.extend([self.model[child_path][5]
+ for child_path in [self.filter_model.convert_path_to_child_path(path)
+ for path in self.view.get_selection()]
+ if self.model[child_path][11] == 'song'])
+ self.items_selected_callback(self.items_selected)
+
+ @log
+ def _filter_visible_func(self, model, _iter, data=None):
+ return model.iter_parent(_iter) is not None or model.iter_has_child(_iter)
+
+ @log
+ def _on_grilo_ready(self, data=None):
+ playlists.fetch_or_create_static_playlists()
+
+ @log
+ def set_search_text(self, search_term, fields_filter):
+ query_matcher = {
+ 'album': {
+ 'search_all': Query.get_albums_with_any_match,
+ 'search_artist': Query.get_albums_with_artist_match,
+ 'search_album': Query.get_albums_with_album_match,
+ 'search_track': Query.get_albums_with_track_match,
+ },
+ 'artist': {
+ 'search_all': Query.get_artists_with_any_match,
+ 'search_artist': Query.get_artists_with_artist_match,
+ 'search_album': Query.get_artists_with_album_match,
+ 'search_track': Query.get_artists_with_track_match,
+ },
+ 'song': {
+ 'search_all': Query.get_songs_with_any_match,
+ 'search_artist': Query.get_songs_with_artist_match,
+ 'search_album': Query.get_songs_with_album_match,
+ 'search_track': Query.get_songs_with_track_match,
+ },
+ }
+
+ self.model = Gtk.TreeStore(
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING, # item title or header text
+ GObject.TYPE_STRING, # artist for albums and songs
+ GdkPixbuf.Pixbuf, # album art
+ GObject.TYPE_OBJECT, # item
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_INT,
+ GObject.TYPE_STRING,
+ GObject.TYPE_INT,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_STRING, # type
+ GObject.TYPE_INT
+ )
+ self.filter_model = self.model.filter_new(None)
+ self.filter_model.set_visible_func(self._filter_visible_func)
+ self.view.set_model(self.filter_model)
+
+ self._albums = {}
+ self._artists = {}
+
+ if search_term == "":
+ return
+
+ albums_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Albums"), 2])
+ artists_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Artists"), 2])
+ songs_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Songs"), 2])
+ playlists_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Playlists"), 2])
+
+ self.head_iters = [albums_iter, artists_iter, songs_iter, playlists_iter]
+ self.songs_model = self.model.filter_new(self.model.get_path(songs_iter))
+
+ # Use queries for Tracker
+ if not grilo.search_source or \
+ grilo.search_source.get_id() == 'grl-tracker-source':
+ for category in ('album', 'artist', 'song'):
+ query = query_matcher[category][fields_filter](search_term)
+ grilo.populate_custom_query(query, self._add_item, -1, [self.model, category])
+ if not grilo.search_source or \
+ grilo.search_source.get_id() != 'grl-tracker-source':
+ # nope, can't do - reverting to Search
+ grilo.search(search_term, self._add_search_item, self.model)
diff --git a/gnomemusic/views/songsview.py b/gnomemusic/views/songsview.py
new file mode 100644
index 0000000..daaae05
--- /dev/null
+++ b/gnomemusic/views/songsview.py
@@ -0,0 +1,233 @@
+# Copyright (c) 2016 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+from gettext import gettext as _
+from gi.repository import Gd, GLib, Gtk, Pango
+
+from gnomemusic import log
+from gnomemusic.albumartcache import ArtSize
+from gnomemusic.grilo import grilo
+from gnomemusic.player import DiscoveryStatus
+from gnomemusic.views.baseview import BaseView
+import gnomemusic.utils as utils
+
+
+class SongsView(BaseView):
+
+ def __repr__(self):
+ return '<SongsView>'
+
+ @log
+ def __init__(self, window, player):
+ BaseView.__init__(self, 'songs', _("Songs"), window, Gd.MainViewType.LIST)
+ self._items = {}
+ self.isStarred = None
+ self.iter_to_clean = None
+ self.view.get_generic_view().get_style_context()\
+ .add_class('songs-list')
+ self._iconHeight = 32
+ self._iconWidth = 32
+ self._add_list_renderers()
+ self.view.get_generic_view().get_style_context().remove_class('content-view')
+ self.player = player
+ self.player.connect('playlist-item-changed', self.update_model)
+
+ @log
+ def _on_changes_pending(self, data=None):
+ if (self._init and self.header_bar._selectionMode is False):
+ self.model.clear()
+ self._offset = 0
+ GLib.idle_add(self.populate)
+ grilo.changes_pending['Songs'] = False
+
+ @log
+ def _on_selection_mode_changed(self, widget, data=None):
+ if self.header_bar._selectionMode is False and grilo.changes_pending['Songs'] is True:
+ self._on_changes_pending()
+
+ @log
+ def _on_item_activated(self, widget, id, path):
+ if self.star_handler.star_renderer_click:
+ self.star_handler.star_renderer_click = False
+ return
+
+ try:
+ _iter = self.model.get_iter(path)
+ except TypeError:
+ return
+ if self.model.get_value(_iter, 8) != self.errorIconName:
+ self.player.set_playlist('Songs', None, self.model, _iter, 5, 11)
+ self.player.set_playing(True)
+
+ @log
+ def update_model(self, player, playlist, currentIter):
+ if self.iter_to_clean:
+ self.model.set_value(self.iter_to_clean, 10, False)
+ if playlist != self.model:
+ return False
+
+ self.model.set_value(currentIter, 10, True)
+ path = self.model.get_path(currentIter)
+ self.view.get_generic_view().scroll_to_path(path)
+ if self.model.get_value(currentIter, 8) != self.errorIconName:
+ self.iter_to_clean = currentIter.copy()
+
+ return False
+
+ def _add_item(self, source, param, item, remaining=0, data=None):
+ self.window.notification.set_timeout(0)
+ if not item:
+ if remaining == 0:
+ self.view.set_model(self.model)
+ self.window.notification.dismiss()
+ self.view.show()
+ return
+ self._offset += 1
+ item.set_title(utils.get_media_title(item))
+ artist = utils.get_artist_name(item)
+ if item.get_url() is None:
+ return
+ self.model.insert_with_valuesv(
+ -1,
+ [2, 3, 5, 9],
+ [utils.get_media_title(item),
+ artist, item, bool(item.get_lyrics())])
+ # TODO: change "bool(item.get_lyrics())" --> item.get_favourite() once query works properly
+
+ @log
+ def _add_list_renderers(self):
+ list_widget = self.view.get_generic_view()
+ list_widget.set_halign(Gtk.Align.CENTER)
+ cols = list_widget.get_columns()
+ cells = cols[0].get_cells()
+ cells[2].set_visible(False)
+ now_playing_symbol_renderer = Gtk.CellRendererPixbuf(xpad=0,
+ xalign=0.5,
+ yalign=0.5)
+
+ column_now_playing = Gtk.TreeViewColumn()
+ column_now_playing.set_fixed_width(48)
+ column_now_playing.pack_start(now_playing_symbol_renderer, False)
+ column_now_playing.set_cell_data_func(now_playing_symbol_renderer,
+ self._on_list_widget_icon_render, None)
+ list_widget.insert_column(column_now_playing, 0)
+
+ title_renderer = Gtk.CellRendererText(
+ xpad=0,
+ xalign=0.0,
+ yalign=0.5,
+ height=48,
+ width=300,
+ 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)
+
+ self.star_handler.add_star_renderers(list_widget, cols)
+
+ duration_renderer = Gd.StyledTextRenderer(
+ xpad=32,
+ xalign=1.0
+ )
+ duration_renderer.add_class('dim-label')
+
+ col = Gtk.TreeViewColumn()
+ col.pack_start(duration_renderer, False)
+ col.set_cell_data_func(duration_renderer,
+ self._on_list_widget_duration_render, None)
+ list_widget.append_column(col)
+
+ artist_renderer = Gd.StyledTextRenderer(
+ xpad=32,
+ width=300,
+ ellipsize=Pango.EllipsizeMode.END
+ )
+ artist_renderer.add_class('dim-label')
+
+ col = Gtk.TreeViewColumn()
+ col.set_expand(True)
+ col.pack_start(artist_renderer, True)
+ col.set_cell_data_func(artist_renderer,
+ self._on_list_widget_artist_render, None)
+ col.add_attribute(artist_renderer, 'text', 3)
+ list_widget.append_column(col)
+
+ type_renderer = Gd.StyledTextRenderer(
+ xpad=32,
+ width=300,
+ ellipsize=Pango.EllipsizeMode.END
+ )
+ type_renderer.add_class('dim-label')
+
+ col.pack_end(type_renderer, True)
+ col.set_cell_data_func(type_renderer,
+ self._on_list_widget_type_render, None)
+
+ def _on_list_widget_title_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_album() or _("Unknown Album"))
+
+ def _on_list_widget_icon_render(self, col, cell, model, _iter, data):
+ if not self.player.currentTrackUri:
+ cell.set_visible(False)
+ return
+
+ if model.get_value(_iter, 11) == DiscoveryStatus.FAILED:
+ cell.set_property('icon-name', self.errorIconName)
+ cell.set_visible(True)
+ elif model.get_value(_iter, 5).get_url() == self.player.currentTrackUri:
+ cell.set_property('icon-name', self.nowPlayingIconName)
+ cell.set_visible(True)
+ else:
+ cell.set_visible(False)
+
+ @log
+ def populate(self):
+ self._init = True
+ if grilo.tracker:
+ self.window._init_loading_notification()
+ GLib.idle_add(grilo.populate_songs, self._offset, self._add_item)
+
+ @log
+ def get_selected_tracks(self, callback):
+ callback([self.model.get_value(self.model.get_iter(path), 5)
+ for path in self.view.get_selection()])
+
diff --git a/gnomemusic/window.py b/gnomemusic/window.py
index 9f78ca9..cdea156 100644
--- a/gnomemusic/window.py
+++ b/gnomemusic/window.py
@@ -34,15 +34,21 @@ gi.require_version('Gd', '1.0')
from gi.repository import Gtk, Gdk, Gio, GLib, Gd
from gettext import gettext as _, ngettext
+from gnomemusic import log
from gnomemusic import TrackerWrapper
from gnomemusic.toolbar import Toolbar, ToolbarState
from gnomemusic.player import Player, SelectionToolbar, RepeatType
from gnomemusic.query import Query
-import gnomemusic.view as Views
+from gnomemusic.views.albumsview import AlbumsView
+from gnomemusic.views.artistsview import ArtistsView
+from gnomemusic.views.emptysearchview import EmptySearchView
+from gnomemusic.views.searchview import SearchView
+from gnomemusic.views.songsview import SongsView
+from gnomemusic.views.playlistview import PlaylistView
import gnomemusic.widgets as Widgets
from gnomemusic.playlists import Playlists
from gnomemusic.grilo import grilo
-from gnomemusic import log
+
import logging
logger = logging.getLogger(__name__)
@@ -264,12 +270,12 @@ class Window(Gtk.ApplicationWindow):
self.connect('destroy', self._notify_mode_disconnect)
self._key_press_event_id = self.connect('key_press_event', self._on_key_press)
- self.views.append(Views.Albums(self, self.player))
- self.views.append(Views.Artists(self, self.player))
- self.views.append(Views.Songs(self, self.player))
- self.views.append(Views.Playlist(self, self.player))
- self.views.append(Views.Search(self, self.player))
- self.views.append(Views.EmptySearch(self, self.player))
+ self.views.append(AlbumsView(self, self.player))
+ self.views.append(ArtistsView(self, self.player))
+ self.views.append(SongsView(self, self.player))
+ self.views.append(PlaylistView(self, self.player))
+ self.views.append(SearchView(self, self.player))
+ self.views.append(EmptySearchView(self, self.player))
for i in self.views:
if i.title:
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 75b27bc..3b77d1f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -16,7 +16,14 @@ gnomemusic/query.py
gnomemusic/searchbar.py
gnomemusic/toolbar.py
gnomemusic/utils.py
-gnomemusic/view.py
+gnomemusic/views/albumsview.py
+gnomemusic/views/artistsview.py
+gnomemusic/views/baseview.py
+gnomemusic/views/emptysearchview.py
+gnomemusic/views/initialstateview.py
+gnomemusic/views/playlistview.py
+gnomemusic/views/searchview.py
+gnomemusic/views/songsview.py
gnomemusic/widgets.py
gnomemusic/window.py
[type: gettext/glade]data/AboutDialog.ui.in
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]